diff --git a/package-lock.json b/package-lock.json
index dc9b1d1..978c71c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,6 +22,7 @@
         "@testing-library/react": "^13.1.1",
         "@testing-library/user-event": "^14.1.1",
         "@types/chai": "^4.3.1",
+        "@types/chrome-remote-interface": "^0.31.4",
         "@types/eslint": "^8.4.1",
         "@types/lodash.throttle": "^4.1.7",
         "@types/mocha": "^9.1.0",
@@ -36,6 +37,7 @@
         "@web/test-runner": "^0.13.27",
         "@web/test-runner-playwright": "^0.8.8",
         "chai": "^4.3.6",
+        "chrome-remote-interface": "^0.31.2",
         "concurrently": "^7.1.0",
         "dotenv": "^16.0.0",
         "esbuild": "^0.14.36",
@@ -3181,6 +3183,21 @@
       "integrity": "sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==",
       "dev": true
     },
+    "node_modules/@types/chrome-remote-interface": {
+      "version": "0.31.4",
+      "resolved": "https://registry.npmjs.org/@types/chrome-remote-interface/-/chrome-remote-interface-0.31.4.tgz",
+      "integrity": "sha512-DJHDwimNqCgAyG5gFmr6Y265pe967u3mnkeMVc0iHuf04PHzTgFypA2AjxSvtkM/pogqWxvfRYXy9Wa5Dj0U1g==",
+      "dev": true,
+      "dependencies": {
+        "devtools-protocol": "0.0.927104"
+      }
+    },
+    "node_modules/@types/chrome-remote-interface/node_modules/devtools-protocol": {
+      "version": "0.0.927104",
+      "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.927104.tgz",
+      "integrity": "sha512-5jfffjSuTOv0Lz53wTNNTcCUV8rv7d82AhYcapj28bC2B5tDxEZzVb7k51cNxZP2KHw24QE+sW7ZuSeD9NfMpA==",
+      "dev": true
+    },
     "node_modules/@types/co-body": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/@types/co-body/-/co-body-6.1.0.tgz",
@@ -4985,6 +5002,25 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/chrome-remote-interface": {
+      "version": "0.31.2",
+      "resolved": "https://registry.npmjs.org/chrome-remote-interface/-/chrome-remote-interface-0.31.2.tgz",
+      "integrity": "sha512-vpdJoI9cDRNAfV5oB2ulwXDltvu3Ov9PTblnV48VXcF4zUx1p4xvCLssc5AZ/WLYp4003YxJqLEi8FagPw2vTQ==",
+      "dev": true,
+      "dependencies": {
+        "commander": "2.11.x",
+        "ws": "^7.2.0"
+      },
+      "bin": {
+        "chrome-remote-interface": "bin/client.js"
+      }
+    },
+    "node_modules/chrome-remote-interface/node_modules/commander": {
+      "version": "2.11.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
+      "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==",
+      "dev": true
+    },
     "node_modules/ci-info": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
@@ -16872,6 +16908,23 @@
       "integrity": "sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==",
       "dev": true
     },
+    "@types/chrome-remote-interface": {
+      "version": "0.31.4",
+      "resolved": "https://registry.npmjs.org/@types/chrome-remote-interface/-/chrome-remote-interface-0.31.4.tgz",
+      "integrity": "sha512-DJHDwimNqCgAyG5gFmr6Y265pe967u3mnkeMVc0iHuf04PHzTgFypA2AjxSvtkM/pogqWxvfRYXy9Wa5Dj0U1g==",
+      "dev": true,
+      "requires": {
+        "devtools-protocol": "0.0.927104"
+      },
+      "dependencies": {
+        "devtools-protocol": {
+          "version": "0.0.927104",
+          "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.927104.tgz",
+          "integrity": "sha512-5jfffjSuTOv0Lz53wTNNTcCUV8rv7d82AhYcapj28bC2B5tDxEZzVb7k51cNxZP2KHw24QE+sW7ZuSeD9NfMpA==",
+          "dev": true
+        }
+      }
+    },
     "@types/co-body": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/@types/co-body/-/co-body-6.1.0.tgz",
@@ -18277,6 +18330,24 @@
         }
       }
     },
+    "chrome-remote-interface": {
+      "version": "0.31.2",
+      "resolved": "https://registry.npmjs.org/chrome-remote-interface/-/chrome-remote-interface-0.31.2.tgz",
+      "integrity": "sha512-vpdJoI9cDRNAfV5oB2ulwXDltvu3Ov9PTblnV48VXcF4zUx1p4xvCLssc5AZ/WLYp4003YxJqLEi8FagPw2vTQ==",
+      "dev": true,
+      "requires": {
+        "commander": "2.11.x",
+        "ws": "^7.2.0"
+      },
+      "dependencies": {
+        "commander": {
+          "version": "2.11.0",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
+          "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==",
+          "dev": true
+        }
+      }
+    },
     "ci-info": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
diff --git a/package.json b/package.json
index aba0dcf..4df689c 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
     "@testing-library/react": "^13.1.1",
     "@testing-library/user-event": "^14.1.1",
     "@types/chai": "^4.3.1",
+    "@types/chrome-remote-interface": "^0.31.4",
     "@types/eslint": "^8.4.1",
     "@types/lodash.throttle": "^4.1.7",
     "@types/mocha": "^9.1.0",
@@ -56,6 +57,7 @@
     "@web/test-runner": "^0.13.27",
     "@web/test-runner-playwright": "^0.8.8",
     "chai": "^4.3.6",
+    "chrome-remote-interface": "^0.31.2",
     "concurrently": "^7.1.0",
     "dotenv": "^16.0.0",
     "esbuild": "^0.14.36",
diff --git a/packages/annotate/package.json b/packages/annotate/package.json
index 9690049..84b1309 100644
--- a/packages/annotate/package.json
+++ b/packages/annotate/package.json
@@ -12,7 +12,7 @@
 		"clean": "rm -rf src/*.js src/**/*.js src/*.d.ts src/**/*.d.ts build report",
 		"emit": "tsc -p tsconfig-notestfiles.json --declaration --emitDeclarationOnly --declarationMap --outDir build",
 		"build": "node .esbuildrc.js",
-		"test": "mocha-esbuild **/*.small.test.ts",
+		"test": "mocha-esbuild **/*.test.ts",
 		"eslint": "eslint --ignore-path .gitignore --ext .ts --fix .",
 		"types": "tsc --noEmit",
 		"prettier": "prettier --ignore-path .gitignore --write .",
diff --git a/packages/e2e/.gitignore b/packages/e2e/.gitignore
new file mode 100644
index 0000000..8b50adf
--- /dev/null
+++ b/packages/e2e/.gitignore
@@ -0,0 +1,2 @@
+remote-profile
+*.js
diff --git a/packages/e2e/index.html b/packages/e2e/index.html
index 7047ff9..abc33be 100644
--- a/packages/e2e/index.html
+++ b/packages/e2e/index.html
@@ -338,10 +338,9 @@ <h6>Non igitur bene.</h6>
 					nos admirabilia dicamus.
 				</p>
 			</div>
-		<script type="module">
-			import { TextNodesFromDOM, Match, annotateDOM } from '../annotate/build';
-
-			const insensitive = new Map([
+			<script type="module">
+			import { TextNodesFromDOM, Match, annotateDOM } from '../annotate/build/index.js';
+			const ipsumCI = new Map([
 				['123', ['Quamquam', 'Quonam, inquit, modo']],
 				['456', ['Moriatur', 'arbitrantur', 'Ut pulsi recurrant']],
 				['789', ['vacuitatem doloris non propter utilitatem solum']],
@@ -358,6 +357,66 @@ <h6>Non igitur bene.</h6>
 			textNodesFromDOM.watchDOM((ns) => annotateDOM(ns, match));
 			textNodesFromDOM.watchScroll((ns) => annotateDOM(ns, match));
 		</script>
+<!--	use the below to explore profiling and memory in the chrome devtools - change the type to 'module' and disable the above script-->
+		<script type="text/plain">
+			import { TextNodesFromDOM, Match, annotateDOM } from '../annotate/build/index.js';
+			function getRandomInt(min, max) {
+				return Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min)) + Math.ceil(min)); //The maximum is exclusive and the minimum is inclusive
+			}
+
+			function getRndString(length) {
+				let result           = '';
+				const characters       = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 -';
+				const charactersLength = characters.length;
+				for ( let i = 0; i < length; i++ ) {
+					result += characters[Math.floor(Math.random() * charactersLength)];
+				}
+				return result;
+			}
+
+			function createListForMap(len){
+				return Array(len).fill('').map((v, i) => [i.toString(10), [getRndString(getRandomInt(4, 15))]])
+			}
+
+			// cs5+ci5 = retained size 10972 bytes
+			// const ipsumCS = new Map(createListForMap(5))
+			// const ipsumCI = new Map(createListForMap(5))
+
+			// cs50+ci50 = retained size 84924 bytes
+			// const ipsumCS = new Map(createListForMap(50))
+			// const ipsumCI = new Map(createListForMap(50))
+
+			// cs500+ci500 = retained size 790872 bytes
+			// const ipsumCS = new Map(createListForMap(500))
+			// const ipsumCI = new Map(createListForMap(500))
+
+			// cs5000+ci5000 = retained size 7118240 bytes
+			// const ipsumCS = new Map(createListForMap(5000))
+			// const ipsumCI = new Map(createListForMap(5000))
+
+			// cs9999+ci1 = retained size 6844308 bytes
+			const ipsumCS = new Map(createListForMap(9999))
+			const ipsumCI = new Map(createListForMap(1))
+
+			// cs5000+ci5000 is (7118240 - 6844308)*100/6844308 larger than cs9999+ci1
+
+			setTimeout(() => {
+				// const ipsumCI = new Map([
+				// 	['123', ['Quamquam', 'Quonam, inquit, modo']],
+				// 	['456', ['Moriatur', 'arbitrantur', 'Ut pulsi recurrant']],
+				// 	['789', ['vacuitatem doloris non propter utilitatem solum']],
+				// 	['101112', ['pain', 'dolori']]
+				// ]);
+				// const ipsumCS = new Map([['321', ['Aristonem']], ['654', ['Chryippo']], ['978', ['Socratica']], ['121110', ['Platonis']]]);
+				const opts = { tag: 'x-annotate' };
+				const match = new Match(ipsumCI, ipsumCS, opts);
+				const textNodesFromDOM = new TextNodesFromDOM(document.body, [opts.tag.toUpperCase()]);
+
+				annotateDOM(textNodesFromDOM.walk(document.body), match);
+				textNodesFromDOM.watchDOM((ns) => annotateDOM(ns, match));
+				textNodesFromDOM.watchScroll((ns) => annotateDOM(ns, match));
+			}, 10000)
+		</script>
 		<script>
 			// This is to test the mutation observer works
 			setTimeout(() => {
diff --git a/packages/e2e/package.json b/packages/e2e/package.json
new file mode 100644
index 0000000..c60f0f0
--- /dev/null
+++ b/packages/e2e/package.json
@@ -0,0 +1,9 @@
+{
+  "type": "module",
+  "scripts": {
+    "start": "/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --remote-debugging-port=9222 --user-data-dir=remote-profile",
+    "run": "cd .. && python3 -m http.server 9222",
+    "test": "node index.mjs",
+    "perf": "tsc && node perf.js"
+  }
+}
diff --git a/packages/e2e/perf.ts b/packages/e2e/perf.ts
new file mode 100644
index 0000000..29e9155
--- /dev/null
+++ b/packages/e2e/perf.ts
@@ -0,0 +1,86 @@
+import CDP from "chrome-remote-interface";
+import ProtocolApi from "devtools-protocol/types/protocol-proxy-api";
+
+// workaround for type declaration issues in cdp lib
+type ForceClientT = {
+  Profiler: ProtocolApi.ProfilerApi;
+  Page: ProtocolApi.PageApi;
+  Tracing: ProtocolApi.TracingApi;
+  Network: ProtocolApi.NetworkApi;
+};
+
+async function trace(url: string) {
+  let client: ForceClientT;
+  const response: any[] = [];
+  let profile: any;
+  try {
+    /**
+     * Establish a client to the chrome devtools opened with remote debugging port
+     * http://127.0.0.1:9222/
+     */
+    client = (await CDP({ port: 9222 })) as unknown as ForceClientT;
+    const { Network, Page, Profiler } = client;
+    await Promise.all([
+      Network.enable({}),
+      Page.enable(),
+      Profiler.enable(),
+      Profiler.setSamplingInterval({ interval: 100 }),
+    ]);
+    await Profiler.start();
+    /**
+     * Register a callback to intercept all the requests
+     */
+    Network.on("responseReceived", (params) => response.push(params.response));
+
+    //Navigate to the url
+    await Page.navigate({ url });
+
+    //Wait for the page to load
+    await Page.on("loadEventFired", (params) => {});
+    profile = await Profiler.stop().then((p) => p.profile);
+  } catch (err) {
+    console.error(err);
+  }
+  // finally {
+  //   if (client) {
+  //     await client.close();
+  //   }
+  // }
+  // @ts-ignore
+  client.close();
+  return { response, profile };
+}
+
+function groupBy(arr, key) {
+  return arr.reduce((groups, item) => {
+    const val = item[key];
+    groups[val] = groups[val] || [];
+    groups[val].push(item);
+    return groups;
+  }, {});
+}
+
+/**
+ * Get all the response, group and count them by mime-type.
+ */
+const fn = async () => {
+  const { response } = await trace("https://github.com");
+  const groupedByType = groupBy(response, "mimeType");
+  const countByType = Object.keys(groupedByType).map((k) => {
+    const count = groupedByType[k].length;
+    return {
+      type: k,
+      count,
+    };
+  });
+
+  console.log(countByType);
+};
+
+const getProfile = async () => {
+  const { profile } = await trace("http://localhost:9222/e2e");
+  console.log("|> profile ===> ", profile);
+};
+
+// fn();
+getProfile();
diff --git a/packages/e2e/tsconfig.json b/packages/e2e/tsconfig.json
new file mode 100644
index 0000000..bebe7f8
--- /dev/null
+++ b/packages/e2e/tsconfig.json
@@ -0,0 +1,19 @@
+{
+	"include": ["./perf.ts"],
+	"compilerOptions": {
+		"useDefineForClassFields": true,
+		"lib": ["esnext"],
+		"target": "ESNext",
+		"module": "ESNext",
+		"moduleResolution": "node",
+		"resolveJsonModule": true,
+		"allowSyntheticDefaultImports": true,
+		"esModuleInterop": true,
+		"skipLibCheck": true,
+		"forceConsistentCasingInFileNames": true,
+		"strict": true,
+		"sourceMap": false,
+		"noImplicitAny": false,
+		"typeRoots": ["./node_modules/@types", "../../node_modules/@types"]
+	}
+}