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"] + } +}