diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd76d27..aefdf3e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Create a report to help us improve title: '' labels: '' assignees: '' - --- **Describe the bug** @@ -12,6 +11,7 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' @@ -21,9 +21,10 @@ Steps to reproduce the behavior: A clear and concise description of what you expected to happen. **Environment** - - Browser with version [e.g. chrome 22, firefox 70.0.1] -- Devtools version [e.g. 1.3.1, built from master] -- Svelte version [e.g. 3.15.0] + +- Browser with version [e.g. chrome 22, firefox 70.0.1] +- Devtools version [e.g. 1.3.1, built from master] +- Svelte version [e.g. 3.15.0] **Additional context** Add any other context about the problem here. diff --git a/screenshot.png b/.github/assets/screenshot-1.1.0.png similarity index 100% rename from screenshot.png rename to .github/assets/screenshot-1.1.0.png diff --git a/.github/assets/screenshot-2.0.0.png b/.github/assets/screenshot-2.0.0.png new file mode 100644 index 0000000..71857f8 Binary files /dev/null and b/.github/assets/screenshot-2.0.0.png differ diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml new file mode 100644 index 0000000..1db49f5 --- /dev/null +++ b/.github/workflows/quality.yaml @@ -0,0 +1,39 @@ +name: QA + +on: + push: + branches: [master] + pull_request: + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: pnpm/action-setup@v2 + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: pnpm + + - run: pnpm install + - run: pnpm check + + # test: + # runs-on: ${{ matrix.os }} + # strategy: + # matrix: + # node-version: [16, 18, 20] + # os: [ubuntu-latest, windows-latest, macOS-latest] + + # steps: + # - uses: actions/checkout@v3 + # - uses: pnpm/action-setup@v2 + # - uses: actions/setup-node@v3 + # with: + # node-version: ${{ matrix.node-version }} + # cache: pnpm + + # - run: pnpm install + # - run: pnpm test diff --git a/.gitignore b/.gitignore index d695b30..5a599fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,9 @@ -.cache node_modules -dest/devtools/styles.css -dest/devtools/styles.css.map -dest/devtools/bundle.js -dest/background.js -dest/privilegedContent.js -dest/icon-*.png -credentials -test/public/bundle.js -test/public/styles.css -test/public/styles.css.map +/build + +.DS_Store +.cache +.env + +# generated files +static/courier.js diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..c424244 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,28 @@ +{ + "printWidth": 100, + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "useTabs": true, + "overrides": [ + { + "files": ["pnpm-lock.yaml"], + "options": { + "rangeEnd": 0 + } + }, + + { + "files": ["*.md"], + "options": { + "tabWidth": 4 + } + }, + { + "files": ["*.svelte"], + "options": { + "plugins": ["prettier-plugin-svelte"] + } + } + ] +} diff --git a/README.md b/README.md index 931f534..268b1b1 100644 --- a/README.md +++ b/README.md @@ -1,100 +1,36 @@ # Svelte DevTools -[![Mozilla Add-on](https://img.shields.io/amo/users/svelte-devtools?color=red&label=Firefox)](https://addons.mozilla.org/en-US/firefox/addon/svelte-devtools/) [![Chrome Web Store](https://img.shields.io/chrome-web-store/users/ckolcbmkjpjmangdbmnkpjigpkddpogn?color=blue&label=Chrome)](https://chrome.google.com/webstore/detail/svelte-devtools/ckolcbmkjpjmangdbmnkpjigpkddpogn) -Install from the [Firefox addon page](https://addons.mozilla.org/en-US/firefox/addon/svelte-devtools/) or the -[Chrome addon page](https://chrome.google.com/webstore/detail/svelte-devtools/ckolcbmkjpjmangdbmnkpjigpkddpogn) +[![Chrome Web Store](https://img.shields.io/chrome-web-store/users/kfidecgcdjjfpeckbblhmfkhmlgecoff?color=blue&label=Chrome)](https://chrome.google.com/webstore/detail/svelte-devtools/kfidecgcdjjfpeckbblhmfkhmlgecoff) -**Svelte devtools is actively maintained. If you have any problems or feature requests feel free to create an issue.** - -Svelte Devtools is a Firefox and Chrome extension for the Svelte javascript framework. It allows you to inspect the Svelte state and component hierarchies in the Developer Tools. +Svelte DevTools is a Chrome extension for the [Svelte](https://svelte.dev/) framework. It allows you to inspect the Svelte state and component hierarchies in the Developer Tools. After installing you will see a new tab in Developer Tools. This tab displays a tree of Svelte components, HTMLx blocks, and DOM elements that were rendered on the page. By selecting one of the nodes in the tree, you can inspect and edit its current state in the panel to the right. -**Requires svelte version 3.12.0 or above** - -![1.1.0 Screenshot](https://raw.githubusercontent.com/RedHatter/svelte-devtools/master/screenshot.png "1.1.0 Screenshot") +![2.0.0 Screenshot](./.github/assets/screenshot-2.0.0.png '2.0.0 Screenshot') -## Enabling dev mode +## Requirements -In order for svelte-devtools to comunicate with your application bundle the svelte compiler must have the `dev` option set to `true`. +The `svelte-devtools` extension requires your Svelte application to be compiled with the `dev` option set to `true`. If you're using [SvelteKit](https://kit.svelte.dev/), this is done automatically, outside of that you will need to set it manually. -### Template -By default the [svelte template](https://github.com/sveltejs/template) will set `dev: true` when running `npm run dev` and `false` otherwise. +This extension officially supports Svelte 4.0 and above. -### Rollup -Below is a minimalist rollup config with `dev: true` set. -``` -// rollup.config.js -import * as fs from 'fs'; -import svelte from 'rollup-plugin-svelte'; +## Development -export default { - input: 'src/main.js', - output: { - file: 'public/bundle.js', - format: 'iife' - }, - plugins: [ - svelte({ - compilerOptions: { - dev: true - } - }) - ] -} -``` - -### Webpack -Below is the relevant snipet from a `webpack.config.js` with `dev: true` set. -``` - ... - module: { - rules: [ - ... - { - test: /\.(html|svelte)$/, - exclude: /node_modules/, - use: { - loader: 'svelte-loader', - options: { - dev: true, - }, - }, - }, - ... - ] - }, - ... -``` +Clone this repository, setup and run the build script -## Build from source - -### Firefox - -Clone this repository and run the package script. -``` -git clone https://github.com/RedHatter/svelte-devtools.git +```sh +git clone https://github.com/sveltejs/svelte-devtools.git cd svelte-devtools -npm install -npm run package:firefox +pnpm install +pnpm build ``` -This should build the codebase and output a zip file under `web-ext-artifacts`. -Unsigned addons can't be install in firefox permanently but addons can be installed temporarily. -1. Navigate to `about:debugging`. -2. Click "Load Temporary Add-on" and choose the generated zip file. +This will build the codebase and output all the required files in the `build` directory. To load the extension for development, follow these steps: -### Chrome +1. Navigate to the extensions settings page +2. Turn on the 'Developer mode' switch +3. Click 'Load Unpacked' and select the `build` directory -Clone this repository and run the package script. -``` -git clone https://github.com/RedHatter/svelte-devtools.git -cd svelte-devtools -npm install -npm run package:chrome -``` -This should build the codebase and output a zip file under `web-ext-artifacts`. +## Acknowledgements -1. Navigate to `chrome://extensions/`. -2. Turn on developer mode using the 'Developer mode' switch in the upper right hand corner of the page. -3. Click 'Load Unpacked' and select the `dest` directory. +- This extension was initially created and developed by [RedHatter](https://github.com/RedHatter) diff --git a/dest/devtools/check.svg b/dest/devtools/check.svg deleted file mode 100644 index ec45a2a..0000000 --- a/dest/devtools/check.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/dest/devtools/index.html b/dest/devtools/index.html deleted file mode 100644 index 6488fd1..0000000 --- a/dest/devtools/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/dest/devtools/index.js b/dest/devtools/index.js deleted file mode 100644 index 87eff54..0000000 --- a/dest/devtools/index.js +++ /dev/null @@ -1,14 +0,0 @@ -chrome.devtools.panels.create( - 'Svelte', - chrome.devtools.panels.themeName == 'dark' - ? '/devtools/svelte-logo-dark.svg' - : '/devtools/svelte-logo-light.svg', - '/devtools/panel.html', - panel => - panel.onShown.addListener(() => - chrome.devtools.inspectedWindow.eval( - 'if (window.__svelte_devtools_select_element) window.__svelte_devtools_select_element($0)', - (result, err) => err && console.error(err) - ) - ) -) diff --git a/dest/devtools/panel.html b/dest/devtools/panel.html deleted file mode 100644 index 85f7b2d..0000000 --- a/dest/devtools/panel.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/dest/manifest.json b/dest/manifest.json deleted file mode 100644 index 981025a..0000000 --- a/dest/manifest.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "manifest_version": 2, - "name": "Svelte Devtools", - "version": "1.3.0", - "description": "Browser devtools extension for debugging Svelte applications.", - "icons": { - "16": "icon-16.png", - "24": "icon-24.png", - "48": "icon-48.png", - "96": "icon-96.png", - "128": "icon-128.png" - }, - "permissions": ["tabs", ""], - "background": { - "scripts": ["background.js"] - }, - "devtools_page": "devtools/index.html", - "web_accessible_resources": ["privilegedContent.js"] -} diff --git a/index.html b/index.html new file mode 100644 index 0000000..396be6e --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + + + +
+ + + diff --git a/package.json b/package.json index a039c72..79a5b25 100644 --- a/package.json +++ b/package.json @@ -1,37 +1,27 @@ { - "name": "svelte-devtools", - "version": "1.3.0", - "description": "Browser devtools extension for debugging Svelte applications.", - "repository": "github:RedHatter/svelte-devtools", - "license": "MIT", - "scripts": { - "build:firefox": "cross-env TARGET=firefox rollup -c", - "build:chrome": "cross-env TARGET=chrome rollup -c", - "build:icon": "node ./scripts/buildIcons.mjs", - "dev": "http-serve test/public -p 8940 & web-ext run -s dest -u http://127.0.0.1:8940 -u about:debugging && kill $!", - "package:firefox": "yarpm run build:firefox && web-ext build -s dest", - "package:chrome": "yarpm run build:chrome && web-ext build -s dest" - }, - "devDependencies": { - "cross-env": "^7.0.3", - "http-serve": "^1.0.1", - "postcss": "^8.2.2", - "postcss-sorting": "^6.0.0", - "prettier": "^2.2.1", - "prettier-plugin-svelte": "^1.4.2", - "rollup": "^2.35.1", - "rollup-plugin-css-only": "^3.1.0", - "rollup-plugin-jscc": "^2.0.0", - "rollup-plugin-node-resolve": "^5.2.0", - "rollup-plugin-svelte": "^7.0.0", - "rollup-plugin-transform-input": "git+https://github.com/RedHatter/rollup-plugin-transform-input.git", - "svelte": "^3.20.1", - "svelte-listener": "git+https://github.com/RedHatter/svelte-listener.git", - "svgexport": "^0.4.1", - "web-ext": "^5.4.1", - "yarpm": "^0.2.1" - }, - "engines": { - "node": ">=11.14.0" - } + "private": true, + "type": "module", + "scripts": { + "dev:app": "vite build --watch", + "dev:scripts": "rollup -cw", + "build": "vite build && rollup -c", + "format": "prettier -w . --plugin=prettier-plugin-svelte --plugin=prettier-plugin-sort-package-json", + "check": "pnpm check:style && pnpm check:svelte", + "check:style": "prettier -c . --plugin=prettier-plugin-svelte --plugin=prettier-plugin-sort-package-json", + "check:svelte": "svelte-check --tsconfig ./tsconfig.json" + }, + "packageManager": "pnpm@8.7.0", + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^2.4.5", + "@types/chrome": "^0.0.245", + "@types/node": "^20.6.0", + "prettier": "^3.0.3", + "prettier-plugin-sort-package-json": "^0.2.0", + "prettier-plugin-svelte": "^3.0.3", + "rollup": "^3.29.1", + "svelte": "^4.2.0", + "svelte-check": "^3.5.1", + "typescript": "^5.2.2", + "vite": "^4.4.9" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..642cb82 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1033 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +devDependencies: + '@sveltejs/vite-plugin-svelte': + specifier: ^2.4.5 + version: 2.4.5(svelte@4.2.0)(vite@4.4.9) + '@types/chrome': + specifier: ^0.0.245 + version: 0.0.245 + '@types/node': + specifier: ^20.6.0 + version: 20.6.0 + prettier: + specifier: ^3.0.3 + version: 3.0.3 + prettier-plugin-sort-package-json: + specifier: ^0.2.0 + version: 0.2.0(prettier@3.0.3) + prettier-plugin-svelte: + specifier: ^3.0.3 + version: 3.0.3(prettier@3.0.3)(svelte@4.2.0) + rollup: + specifier: ^3.29.1 + version: 3.29.1 + svelte: + specifier: ^4.2.0 + version: 4.2.0 + svelte-check: + specifier: ^3.5.1 + version: 3.5.1(svelte@4.2.0) + typescript: + specifier: ^5.2.2 + version: 5.2.2 + vite: + specifier: ^4.4.9 + version: 4.4.9(@types/node@20.6.0) + +packages: + + /@ampproject/remapping@2.2.1: + resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.19 + dev: true + + /@esbuild/android-arm64@0.18.20: + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.18.20: + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.18.20: + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.18.20: + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.18.20: + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.18.20: + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.18.20: + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.18.20: + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.18.20: + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.18.20: + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.18.20: + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.18.20: + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.18.20: + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.18.20: + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.18.20: + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.18.20: + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.18.20: + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.18.20: + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.18.20: + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.18.20: + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.18.20: + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.18.20: + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@jridgewell/gen-mapping@0.3.3: + resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.19 + dev: true + + /@jridgewell/resolve-uri@3.1.1: + resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/set-array@1.1.2: + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: true + + /@jridgewell/trace-mapping@0.3.19: + resolution: {integrity: sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==} + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + dev: true + + /@sveltejs/vite-plugin-svelte-inspector@1.0.4(@sveltejs/vite-plugin-svelte@2.4.5)(svelte@4.2.0)(vite@4.4.9): + resolution: {integrity: sha512-zjiuZ3yydBtwpF3bj0kQNV0YXe+iKE545QGZVTaylW3eAzFr+pJ/cwK8lZEaRp4JtaJXhD5DyWAV4AxLh6DgaQ==} + engines: {node: ^14.18.0 || >= 16} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^2.2.0 + svelte: ^3.54.0 || ^4.0.0 + vite: ^4.0.0 + dependencies: + '@sveltejs/vite-plugin-svelte': 2.4.5(svelte@4.2.0)(vite@4.4.9) + debug: 4.3.4 + svelte: 4.2.0 + vite: 4.4.9(@types/node@20.6.0) + transitivePeerDependencies: + - supports-color + dev: true + + /@sveltejs/vite-plugin-svelte@2.4.5(svelte@4.2.0)(vite@4.4.9): + resolution: {integrity: sha512-UJKsFNwhzCVuiZd06jM/psscyNJNDwjQC+qIeb7GBJK9iWeQCcIyfcPWDvbCudfcJggY9jtxJeeaZH7uny93FQ==} + engines: {node: ^14.18.0 || >= 16} + peerDependencies: + svelte: ^3.54.0 || ^4.0.0 + vite: ^4.0.0 + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 1.0.4(@sveltejs/vite-plugin-svelte@2.4.5)(svelte@4.2.0)(vite@4.4.9) + debug: 4.3.4 + deepmerge: 4.3.1 + kleur: 4.1.5 + magic-string: 0.30.3 + svelte: 4.2.0 + svelte-hmr: 0.15.3(svelte@4.2.0) + vite: 4.4.9(@types/node@20.6.0) + vitefu: 0.2.4(vite@4.4.9) + transitivePeerDependencies: + - supports-color + dev: true + + /@types/chrome@0.0.245: + resolution: {integrity: sha512-bBdONkLO8wMbJK6iG0Q8ShFuh67Grnod+5OpClJaa8MuKJXP/Kjl3f8wRYeMOnQ0Q8HDgpUlopu8bvl9siR8/A==} + dependencies: + '@types/filesystem': 0.0.32 + '@types/har-format': 1.2.12 + dev: true + + /@types/estree@1.0.1: + resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} + dev: true + + /@types/filesystem@0.0.32: + resolution: {integrity: sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==} + dependencies: + '@types/filewriter': 0.0.29 + dev: true + + /@types/filewriter@0.0.29: + resolution: {integrity: sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ==} + dev: true + + /@types/har-format@1.2.12: + resolution: {integrity: sha512-P20p/YBrqUBmzD6KhIQ8EiY4/RRzlekL4eCvfQnulFPfjmiGxKIoyCeI7qam5I7oKH3P8EU4ptEi0EfyGoLysw==} + dev: true + + /@types/node@20.6.0: + resolution: {integrity: sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg==} + dev: true + + /@types/pug@2.0.6: + resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==} + dev: true + + /acorn@8.10.0: + resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + dependencies: + dequal: 2.0.3 + dev: true + + /axobject-query@3.2.1: + resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} + dependencies: + dequal: 2.0.3 + dev: true + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + dev: true + + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /code-red@1.0.4: + resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + '@types/estree': 1.0.1 + acorn: 8.10.0 + estree-walker: 3.0.3 + periscopic: 3.1.0 + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + dev: true + + /css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.0.2 + dev: true + + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: true + + /deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + dev: true + + /dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + dev: true + + /detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + dev: true + + /es6-promise@3.3.1: + resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} + dev: true + + /esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + dev: true + + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + dependencies: + '@types/estree': 1.0.1 + dev: true + + /fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fastq@1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + dependencies: + reusify: 1.0.4 + dev: true + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: true + + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true + + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-reference@3.0.2: + resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + dependencies: + '@types/estree': 1.0.1 + dev: true + + /kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + dev: true + + /locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + dev: true + + /magic-string@0.27.0: + resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /magic-string@0.30.3: + resolution: {integrity: sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + dev: true + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + + /min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + dev: true + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: true + + /mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + dependencies: + minimist: 1.2.8 + dev: true + + /mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + dev: true + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /nanoid@3.3.6: + resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /periscopic@3.1.0: + resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + dependencies: + '@types/estree': 1.0.1 + estree-walker: 3.0.3 + is-reference: 3.0.2 + dev: true + + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: true + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.6 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + + /prettier-plugin-sort-package-json@0.2.0(prettier@3.0.3): + resolution: {integrity: sha512-jg+CfEHpmXyMJxoBSQh+IObmCxqt7KyHOuXGQm9D4heeyipEhlTJZfiS9SlfhBKrtf/yA8WZwHmynUG9xfF/rA==} + peerDependencies: + prettier: ^3.0.0 + dependencies: + prettier: 3.0.3 + dev: true + + /prettier-plugin-svelte@3.0.3(prettier@3.0.3)(svelte@4.2.0): + resolution: {integrity: sha512-dLhieh4obJEK1hnZ6koxF+tMUrZbV5YGvRpf2+OADyanjya5j0z1Llo8iGwiHmFWZVG/hLEw/AJD5chXd9r3XA==} + peerDependencies: + prettier: ^3.0.0 + svelte: ^3.2.0 || ^4.0.0-next.0 + dependencies: + prettier: 3.0.3 + svelte: 4.2.0 + dev: true + + /prettier@3.0.3: + resolution: {integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==} + engines: {node: '>=14'} + hasBin: true + dev: true + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + + /rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /rollup@3.29.1: + resolution: {integrity: sha512-c+ebvQz0VIH4KhhCpDsI+Bik0eT8ZFEVZEYw0cGMVqIP8zc+gnwl7iXCamTw7vzv2MeuZFZfdx5JJIq+ehzDlg==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + dependencies: + mri: 1.2.0 + dev: true + + /sander@0.5.1: + resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} + dependencies: + es6-promise: 3.3.1 + graceful-fs: 4.2.11 + mkdirp: 0.5.6 + rimraf: 2.7.1 + dev: true + + /sorcery@0.11.0: + resolution: {integrity: sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==} + hasBin: true + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + buffer-crc32: 0.2.13 + minimist: 1.2.8 + sander: 0.5.1 + dev: true + + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: true + + /strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + dependencies: + min-indent: 1.0.1 + dev: true + + /svelte-check@3.5.1(svelte@4.2.0): + resolution: {integrity: sha512-+Zb4iHxAhdUtcUg/WJPRjlS1RJalIsWAe9Mz6G1zyznSs7dDkT7VUBdXc3q7Iwg49O/VrZgyJRvOJkjuBfKjFA==} + hasBin: true + peerDependencies: + svelte: ^3.55.0 || ^4.0.0-next.0 || ^4.0.0 + dependencies: + '@jridgewell/trace-mapping': 0.3.19 + chokidar: 3.5.3 + fast-glob: 3.3.1 + import-fresh: 3.3.0 + picocolors: 1.0.0 + sade: 1.8.1 + svelte: 4.2.0 + svelte-preprocess: 5.0.4(svelte@4.2.0)(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - '@babel/core' + - coffeescript + - less + - postcss + - postcss-load-config + - pug + - sass + - stylus + - sugarss + dev: true + + /svelte-hmr@0.15.3(svelte@4.2.0): + resolution: {integrity: sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==} + engines: {node: ^12.20 || ^14.13.1 || >= 16} + peerDependencies: + svelte: ^3.19.0 || ^4.0.0 + dependencies: + svelte: 4.2.0 + dev: true + + /svelte-preprocess@5.0.4(svelte@4.2.0)(typescript@5.2.2): + resolution: {integrity: sha512-ABia2QegosxOGsVlsSBJvoWeXy1wUKSfF7SWJdTjLAbx/Y3SrVevvvbFNQqrSJw89+lNSsM58SipmZJ5SRi5iw==} + engines: {node: '>= 14.10.0'} + requiresBuild: true + peerDependencies: + '@babel/core': ^7.10.2 + coffeescript: ^2.5.1 + less: ^3.11.3 || ^4.0.0 + postcss: ^7 || ^8 + postcss-load-config: ^2.1.0 || ^3.0.0 || ^4.0.0 + pug: ^3.0.0 + sass: ^1.26.8 + stylus: ^0.55.0 + sugarss: ^2.0.0 || ^3.0.0 || ^4.0.0 + svelte: ^3.23.0 || ^4.0.0-next.0 || ^4.0.0 + typescript: '>=3.9.5 || ^4.0.0 || ^5.0.0' + peerDependenciesMeta: + '@babel/core': + optional: true + coffeescript: + optional: true + less: + optional: true + postcss: + optional: true + postcss-load-config: + optional: true + pug: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + typescript: + optional: true + dependencies: + '@types/pug': 2.0.6 + detect-indent: 6.1.0 + magic-string: 0.27.0 + sorcery: 0.11.0 + strip-indent: 3.0.0 + svelte: 4.2.0 + typescript: 5.2.2 + dev: true + + /svelte@4.2.0: + resolution: {integrity: sha512-kVsdPjDbLrv74SmLSUzAsBGquMs4MPgWGkGLpH+PjOYnFOziAvENVzgJmyOCV2gntxE32aNm8/sqNKD6LbIpeQ==} + engines: {node: '>=16'} + dependencies: + '@ampproject/remapping': 2.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.19 + acorn: 8.10.0 + aria-query: 5.3.0 + axobject-query: 3.2.1 + code-red: 1.0.4 + css-tree: 2.3.1 + estree-walker: 3.0.3 + is-reference: 3.0.2 + locate-character: 3.0.0 + magic-string: 0.30.3 + periscopic: 3.1.0 + dev: true + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /typescript@5.2.2: + resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /vite@4.4.9(@types/node@20.6.0): + resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.6.0 + esbuild: 0.18.20 + postcss: 8.4.31 + rollup: 3.29.1 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /vitefu@0.2.4(vite@4.4.9): + resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 + peerDependenciesMeta: + vite: + optional: true + dependencies: + vite: 4.4.9(@types/node@20.6.0) + dev: true + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true diff --git a/rollup.config.js b/rollup.config.js index 8afd883..cd765eb 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,91 +1,23 @@ -import * as fs from 'fs' -import svelte from 'rollup-plugin-svelte' -import resolve from 'rollup-plugin-node-resolve' -import css from 'rollup-plugin-css-only' -import jscc from 'rollup-plugin-jscc' +import { defineConfig } from 'rollup'; -import format from './scripts/format.mjs' - -export default [{ - input: 'src/index.js', - external: ['chrome'], - output: { - file: 'dest/devtools/bundle.js', - name: 'App', - format: 'iife', - globals: { - chrome: 'chrome' - } - }, - plugins: [ - format(), - jscc({ - asloader: false, - extensions: ['css', 'js', 'svelte'] - }), - svelte({ - preprocess: { - markup: input => { - const code = input.content - .replace(/(>|})\s+(?![^]*?<\/(?:script|style)>|[^<]*?>|[^{]*?})/g, '$1') - .replace(/(?]*?|{[^}]*?)\s+(<|{)(?![^]*<\/(?:script|style)>)/g, '$1') - return { code } - } - }, - }), - resolve(), - css({ output: 'styles.css' }), - ] -}, { - input: 'src/background.js', - output: { - file: 'dest/background.js' - }, - plugins: [ - format(), - jscc({ - asloader: false, - extensions: ['css', 'js', 'svelte'] - }), - ] -},{ - input: 'src/client/index.js', - output: { - file: 'dest/privilegedContent.js', - name: 'SvelteDevtools', - format: 'iife', - banner: `if (!window.tag) { - window.tag = document.createElement('script') - window.tag.text = \``, - footer: `\` - if (window.profilerEnabled) window.tag.text = window.tag.text.replace('let profilerEnabled = false;', '\$&\\nstartProfiler();') - document.children[0].append(window.tag) - const port = chrome.runtime.connect() - port.onMessage.addListener(window.postMessage.bind(window)) - window.addEventListener( - 'message', - e => e.source == window && port.postMessage(e.data), - false - ) - window.addEventListener('unload', () => port.postMessage({ type: 'clear' })) -}` - }, - plugins: [ resolve() ] -}, { - input: 'test/src/index.js', - output: { - file: 'test/public/bundle.js', - name: 'App', - format: 'iife' - }, - plugins: [ - format(), - svelte({ - compilerOptions: { - dev: true - } - }), - resolve(), - css({ output: 'styles.css' }) - ] -}] +export default defineConfig([ + { + input: 'static/background.js', + output: { + file: 'build/background.js', + }, + }, + { + input: 'static/sensor.js', + output: { + file: 'build/sensor.js', + }, + }, + { + input: 'src/client/index.js', + output: [ + { file: 'static/courier.js', format: 'iife' }, + { file: 'build/courier.js', format: 'iife' }, + ], + }, +]); diff --git a/scripts/buildIcons.mjs b/scripts/buildIcons.mjs deleted file mode 100644 index 8d84326..0000000 --- a/scripts/buildIcons.mjs +++ /dev/null @@ -1,8 +0,0 @@ -import svgexport from 'svgexport' - -svgexport.render([{ - 'input' : ['src/svelte-logo.svg'], - 'output': [16, 24, 48, 96, 128].map( - size => [`dest/icon-${size}.png`, `${size}:`] - ) -}]); diff --git a/scripts/format.mjs b/scripts/format.mjs deleted file mode 100644 index 60ebb0e..0000000 --- a/scripts/format.mjs +++ /dev/null @@ -1,57 +0,0 @@ -import fs from 'fs' -import transform from 'rollup-plugin-transform-input' -import prettier from 'prettier' -import postcss from 'postcss' -import sorting from 'postcss-sorting' - -function formatContents(filepath, source) { - return prettier.format(source, { - filepath, - arrowParens: 'avoid', - semi: false, - singleQuote: true, - pluginSearchDirs: ['.'], - }) -} - -function formatCss(filepath, source, zen) { - return filepath && - (filepath.endsWith('.svelte') || filepath.endsWith('.html')) - ? source - .replace( - /(.+?)<\/style>/gs, - (_, p1, p2) => `${formatCss(null, p2, zen)}` - ) - : postcss([sorting({ 'properties-order': zen })]).process(source, { - from: filepath, - to: filepath, - }).css -} - -export default function (options) { - const zen = fs.readFileSync('zen', 'utf8').split('\n') - - return { - ...transform({ - ...options, - async transform(source, filepath) { - try { - await fs.promises.access(filepath) - - let formatted = formatContents(filepath, source) - if ( - filepath.endsWith('.svelte') || - filepath.endsWith('.html') || - filepath.endsWith('.css') - ) - formatted = formatCss(filepath, formatted, zen) - - return formatted - } catch (err) { - return source - } - }, - }), - name: 'format', - } -} diff --git a/src/App.svelte b/src/App.svelte deleted file mode 100644 index 95df913..0000000 --- a/src/App.svelte +++ /dev/null @@ -1,57 +0,0 @@ - - - - -{#if $profilerEnabled} -
- -
-{:else if $rootNodes.length} -
- - - - - - -
    ($hoveredNodeId = null)}> - {#each $rootNodes as node (node.id)} - - {/each} -
- -
- -{:else} - -{/if} diff --git a/src/Breadcrumbs.svelte b/src/Breadcrumbs.svelte deleted file mode 100644 index f24ff44..0000000 --- a/src/Breadcrumbs.svelte +++ /dev/null @@ -1,115 +0,0 @@ - - - - -{#if breadcrumbList.length > 1} -
    - {#if shorttend} -
  • - … -
    -
  • - {/if} - {#each breadcrumbList as node} - {#if $visibility[node.type]} -
  • ($selectedNode = node)} - on:mouseover={e => ($hoveredNodeId = node.id)} - class:selected={node.id == $selectedNode.id}> - {node.tagName} -
    -
  • - {/if} - {/each} -
-{/if} diff --git a/src/ConnectMessage.svelte b/src/ConnectMessage.svelte deleted file mode 100644 index dbe5804..0000000 --- a/src/ConnectMessage.svelte +++ /dev/null @@ -1,55 +0,0 @@ - - - - -
-

- To connect to  - Svelte -  perform a hard refresh (ctrl+F5) or  - click here - . -

-

Not working? Did you...

-
    -
  • Use Svelte version 3.12.0 or above?
  • -
  • Build with dev mode enabled?
  • -
-
diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..6472580 --- /dev/null +++ b/src/app.css @@ -0,0 +1,108 @@ +:root { + tab-size: 2; + + --background: rgb(255, 255, 255); + --color: rgb(74, 74, 79); + + --t-duration: 240ms; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + height: 100%; +} + +body { + display: flex; + margin: 0; + height: 100%; + background: var(--background); + color: var(--color); + font-family: monospace; +} +body.dark { + --color: rgb(177, 177, 179); + --background: rgb(36, 36, 36); + scrollbar-color: rgb(115, 115, 115) rgb(60, 60, 61); +} + +/* dark mode scrollbar */ +body.dark ::-webkit-scrollbar { + width: 0.75rem; + height: 0.5rem; + background-color: transparent; +} +body.dark ::-webkit-scrollbar-thumb { + background-color: rgb(51, 51, 51); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 0.25rem; +} + +/* basic resets */ +ul { + margin: 0; + padding: 0; + list-style: none; +} + +/* expandable arrows */ +.expandable::before { + content: ''; + position: absolute; + top: 0; + left: calc(var(--indent, 6px) - 6px); + + border-top: 0.375rem solid rgba(135, 135, 137, 0.9); + border-right: 0.25rem solid transparent; + border-left: 0.25rem solid transparent; + transition-duration: var(--t-duration); + transform: translate3d(0%, calc(50% + 0.25rem + var(--y-pad, 0px)), 0) rotate(-90deg); + /* transform: translate3d(-150%, calc(50% + 0.25rem + var(--y-pad, 0px)), 0) rotate(-90deg); */ +} +.expandable.expanded::before { + transform: translate3d(0%, calc(50% + 0.25rem + var(--y-pad, 0px)), 0) rotate(0deg); + /* transform: translate3d(-150%, calc(50% + 0.25rem + var(--y-pad, 0px)), 0) rotate(0deg); */ +} + +/* tooltip pseudo-elements */ +[data-tooltip]:hover::after, +[data-tooltip]:hover::before { + opacity: 1; + pointer-events: auto; +} +[data-tooltip]::before { + content: ''; + opacity: 0; + pointer-events: none; + + position: absolute; + bottom: -0.2rem; + left: 2.5rem; + border-right: 0.5rem solid transparent; + border-bottom: 0.5rem solid rgb(48, 64, 81); + border-left: 0.5rem solid transparent; + transition: opacity 0.2s; +} +[data-tooltip]::after { + content: attr(data-tooltip); + opacity: 0; + pointer-events: none; + z-index: 1; + position: absolute; + bottom: 0; + left: 0; + + display: flex; + padding: 0.25rem 0.375rem; + border-radius: 0.25rem; + transition: opacity 0.2s; + transform: translateY(100%); + + background-color: rgb(48, 64, 81); + color: white; +} diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000..9d15652 --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,129 @@ +/// +/// + +interface SvelteDevInternal { + version: string; +} + +declare global { + type SvelteComponentDetail = { + id: string; + options: { + $$inline?: boolean; + hydrate?: boolean; + target?: Element; + props?: Record; + }; + tagName: string; + component: { + $$: { + fragment: { + c(): void; + d(detaching: boolean): void; + h(): void; + l(nodes: any[]): void; + m(target: Node, anchor: Node): void; + p(changed: boolean, ctx: any): void; + }; + }; + $$events_def?: {}; + $$prop_def?: {}; + $$slot_def?: {}; + $capture_state(): any; + }; + }; + + type SvelteBlockDetail = { + id: number; + source: string; + type: + | 'anchor' + | 'block' + | 'catch' + | 'component' + | 'each' + | 'element' + | 'else' + | 'if' + | 'iteration' + | 'key' + | 'pending' + | 'slot' + | 'text' + | 'then'; + + detail?: any; + tagName?: string; + + children: SvelteBlockDetail[]; + /** `type: 'element' | 'component'` */ + parent?: SvelteBlockDetail; + /** like `parent` but `type: 'component'` */ + container?: SvelteBlockDetail; + + block: SvelteComponentDetail['component']['$$']['fragment']; + ctx: Array; // TODO: do we need this typed? + }; + + type SvelteListenerDetail = { + node: Node & { + __listeners?: Omit[]; + }; + event: string; + handler: EventListenerOrEventListenerObject; + modifiers: Array<'capture' | 'preventDefault' | 'stopPropagation' | 'stopImmediatePropagation'>; + }; + + interface DocumentEventMap { + SvelteRegisterComponent: CustomEvent; + + SvelteRegisterBlock: CustomEvent; + + SvelteDOMInsert: CustomEvent; + SvelteDOMRemove: CustomEvent; + + SvelteDOMAddEventListener: CustomEvent; + SvelteDOMRemoveEventListener: CustomEvent; + + SvelteDOMSetAttribute: CustomEvent< + SvelteDevInternal & { + node: Element; + attribute: string; + value?: string; + } + >; + SvelteDOMRemoveAttribute: CustomEvent< + SvelteDevInternal & { + node: Element; + attribute: string; + } + >; + SvelteDOMSetProperty: CustomEvent< + SvelteDevInternal & { + node: Element; + property: string; + value?: any; + } + >; + SvelteDOMSetDataset: CustomEvent< + SvelteDevInternal & { + node: HTMLElement; + property: string; + value?: any; + } + >; + SvelteDOMSetData: CustomEvent< + SvelteDevInternal & { + node: Text; + data: unknown; + } + >; + + SvelteDevTools: CusomEvent<{ + type: string; + payload: string; + }>; + } +} + +export {}; diff --git a/src/background.js b/src/background.js deleted file mode 100644 index 5e8631d..0000000 --- a/src/background.js +++ /dev/null @@ -1,79 +0,0 @@ -const toolsPorts = new Map() -const pagePorts = new Map() - -chrome.runtime.onConnect.addListener(port => { - if (port.sender.url == chrome.runtime.getURL('/devtools/panel.html')) { - port.onMessage.addListener(handleToolsMessage) - } else { - port.onMessage.addListener(handlePageMessage) - port.onDisconnect.addListener(port => pagePorts.delete(port.sender.tab.id)) - pagePorts.set(port.sender.tab.id, port) - } -}) - -const profilerEnabledList = [] - -function handleToolsMessage(msg, port) { - switch (msg.type) { - case 'init': - setup(msg.tabId, port) - break - case 'reload': - chrome.tabs.reload(msg.tabId, { bypassCache: true }) - break - default: - const page = pagePorts.get(msg.tabId) - if (page) page.postMessage(msg) - break - } - - switch (msg.type) { - case 'startProfiler': - profilerEnabledList.push(msg.tabId) - break - case 'startProfiler': - const i = profilerEnabledList.indexOf(msg.tabId) - if (i != -1) profilerEnabledList.slice(i, 1) - break - } -} - -function handlePageMessage(msg, port) { - const tools = toolsPorts.get(port.sender.tab.id) - if (tools) tools.postMessage(msg) -} - -function attachScript(tabId, changed) { - if ( - !toolsPorts.has(tabId) || - changed.status != 'loading' || - // #if process.env.TARGET === 'firefox' - !changed.url - // #else - false - // #endif - ) - return - - chrome.tabs.executeScript(tabId, { - code: `window.profilerEnabled = ${profilerEnabledList.includes(tabId)}`, - runAt: 'document_start', - }) - chrome.tabs.executeScript(tabId, { - file: '/privilegedContent.js', - runAt: 'document_start', - }) -} - -function setup(tabId, port) { - toolsPorts.set(tabId, port) - port.onDisconnect.addListener(() => { - toolsPorts.delete(tabId) - pagePorts.delete(tabId) - const i = profilerEnabledList.indexOf(tabId) - if (i != -1) profilerEnabledList.slice(i, 1) - chrome.tabs.onUpdated.removeListener(attachScript) - }) - - chrome.tabs.onUpdated.addListener(attachScript) -} diff --git a/src/base.css b/src/base.css deleted file mode 100644 index 7a52255..0000000 --- a/src/base.css +++ /dev/null @@ -1,83 +0,0 @@ -html { - height: 100%; - font-size: 12px; -} - -body { - display: flex; - margin: 0; - height: 100%; - color: rgb(74, 74, 79); - /* #if process.env.TARGET === 'firefox' - font-size: 11px; - /* #endif */ - font-family: monospace; -} - -body.dark { - /* #if process.env.TARGET === 'chrome' - background-color: rgb(36, 36, 36); - /* #else */ - background-color: rgb(42, 42, 46); - /* #endif */ - color: rgb(177, 177, 179); - scrollbar-color: rgb(115, 115, 115) rgb(60, 60, 61); -} - -body.dark ::-webkit-scrollbar { - width: 14px; - height: 14px; - background-color: transparent; - box-shadow: inset 0 0 1px rgba(255, 255, 255, 0.5); -} - -body.dark ::-webkit-scrollbar-thumb { - background-color: rgb(51, 51, 51); - box-shadow: inset 0 0 1px rgba(255, 255, 255, 0.5); -} - -ul { - margin: 0; - padding: 0; - list-style: none; -} - -[data-tooltip]:hover::after, -[data-tooltip]:hover::before { - opacity: 1; - pointer-events: auto; -} - -[data-tooltip]::after { - position: absolute; - bottom: -0.167rem /* -2px */; - left: 0; - z-index: 1; - display: block; - padding: 0.5rem /* 6px */ 1.333rem /* 16px */; - border-radius: 0.417rem /* 5px */; - background-color: rgb(48, 64, 81); - color: white; - content: attr(data-tooltip); - white-space: pre; - opacity: 0; - transition: opacity 0.2s; - transform: translateY(100%); - pointer-events: none; -} - -[data-tooltip]::before { - position: absolute; - bottom: -0.167rem /* -2px */; - left: 2.5rem /* 30px */; - display: block; - width: 0; - height: 0; - border-right: 0.417rem /* 5px */ solid transparent; - border-bottom: 0.417rem /* 5px */ solid rgb(48, 64, 81); - border-left: 0.417rem /* 5px */ solid transparent; - content: ''; - opacity: 0; - transition: opacity 0.2s; - pointer-events: none; -} diff --git a/src/client/highlight.js b/src/client/highlight.js index 3c2abab..fde12ef 100644 --- a/src/client/highlight.js +++ b/src/client/highlight.js @@ -1,149 +1,51 @@ const dom = { - area: document.createElement('div'), - x: document.createElement('div'), - y: document.createElement('div') -} - -Object.assign(dom.area.style, { - position: 'fixed', - backgroundColor: 'rgba(0, 136, 204, 0.2)', - zIndex: '2147483647', - pointerEvents: 'none' -}) - -Object.assign(dom.x.style, { - position: 'fixed', - borderStyle: 'dashed', - borderColor: 'rgb(0, 136, 204)', - borderWidth: '1px 0', - zIndex: '2147483647', - left: '0', - width: '100vw', - pointerEvents: 'none' -}) - -Object.assign(dom.y.style, { - position: 'fixed', - borderStyle: 'dashed', - borderColor: 'rgb(0, 136, 204)', - borderWidth: '0 1px', - zIndex: '2147483647', - top: '0', - height: '100vh', - pointerEvents: 'none' -}) - -function getOffset(element) { - const styles = getComputedStyle(element) - const margin = { - top: Math.max(parseInt(styles.marginTop), 0), - right: Math.max(parseInt(styles.marginRight), 0), - bottom: Math.max(parseInt(styles.marginBottom), 0), - left: Math.max(parseInt(styles.marginLeft), 0) - } - - const rect = { - width: element.offsetWidth + margin.right + margin.left, - height: element.offsetHeight + margin.top + margin.bottom, - top: element.offsetTop - margin.top, - left: element.offsetLeft - margin.left - } - - let parent = element - while ( - (parent = - parent.offsetParent || parent.ownerDocument.defaultView.frameElement) - ) { - rect.top += parent.offsetTop - rect.left += parent.offsetLeft - } - - parent = element - while ( - (parent = - parent.parentElement || parent.ownerDocument.defaultView.frameElement) - ) { - rect.top -= parent.scrollTop - rect.left -= parent.scrollLeft - } - - rect.right = rect.left + rect.width - rect.bottom = rect.top + rect.height - - return rect -} - -function getBoundingRect(node) { - if (node.type == 'element') return getOffset(node.detail) - - const union = { - top: Infinity, - left: Infinity, - bottom: -Infinity, - right: -Infinity - } - - for (const child of node.children) { - const rect = getBoundingRect(child) - if (rect.top < union.top) union.top = rect.top - if (rect.left < union.left) union.left = rect.left - if (rect.bottom > union.bottom) union.bottom = rect.bottom - if (rect.right > union.right) union.right = rect.right - } - - union.width = union.right - union.left - union.height = union.bottom - union.top - - return union -} + area: document.createElement('div'), + x: document.createElement('div'), + y: document.createElement('div'), +}; +/** @param {Pick} [node] */ export function highlight(node) { - if (!node) { - dom.area.remove() - dom.x.remove() - dom.y.remove() - return - } - - const box = getBoundingRect(node) - Object.assign(dom.area.style, { - top: box.top + 'px', - left: box.left + 'px', - width: box.width + 'px', - height: box.height + 'px' - }) - document.body.append(dom.area) - - Object.assign(dom.x.style, { - top: box.top + 'px', - height: box.height - 2 + 'px' - }) - document.body.append(dom.x) - - Object.assign(dom.y.style, { - left: box.left + 'px', - width: box.width - 2 + 'px' - }) - document.body.append(dom.y) -} - -let target = null -function handleMousemove(e) { - target = e.target - highlight({ type: 'element', detail: target }) -} - -function handleClick() { - stopPicker() - window.__svelte_devtools_select_element(target) -} - -export function stopPicker() { - document.removeEventListener('mousemove', handleMousemove, true) - highlight(null) -} - -export function startPicker() { - document.addEventListener('mousemove', handleMousemove, true) - document.addEventListener('click', handleClick, { capture: true, once: true }) + if (!node || node.type !== 'element' || !node.detail) { + dom.area.remove(); + dom.x.remove(); + dom.y.remove(); + return; + } + + const { clientWidth, scrollHeight } = document.documentElement; + const style = window.getComputedStyle(node.detail); + const rect = node.detail.getBoundingClientRect(); + + // TODO: handle sticky position + const position = style.position === 'fixed' ? 'fixed' : 'absolute'; + const offset = style.position !== 'fixed' ? window.scrollY : 0; + + dom.area.style.setProperty('z-index', '65536'); + dom.area.style.setProperty('background-color', 'rgba(0, 136, 204, 0.2)'); + dom.area.style.setProperty('position', position); + dom.area.style.setProperty('top', `${offset + rect.top}px`); + dom.area.style.setProperty('left', `${rect.left}px`); + dom.area.style.setProperty('width', `${rect.width}px`); + dom.area.style.setProperty('height', `${rect.height}px`); + + dom.x.style.setProperty('z-index', '65536'); + dom.x.style.setProperty('border', '0px dashed rgb(0, 136, 204)'); + dom.x.style.setProperty('border-width', '1px 0px'); + dom.x.style.setProperty('position', position); + dom.x.style.setProperty('top', `${offset + rect.top}px`); + dom.x.style.setProperty('width', `${clientWidth}px`); + dom.x.style.setProperty('height', `${rect.height}px`); + + dom.y.style.setProperty('z-index', '65536'); + dom.y.style.setProperty('border', '0px dashed rgb(0, 136, 204)'); + dom.y.style.setProperty('border-width', '0px 1px'); + dom.y.style.setProperty('position', 'absolute'); + dom.y.style.setProperty('left', `${rect.left}px`); + dom.y.style.setProperty('width', `${rect.width}px`); + dom.y.style.setProperty('height', `${scrollHeight}px`); + + document.body.appendChild(dom.area); + document.body.appendChild(dom.x); + document.body.appendChild(dom.y); } diff --git a/src/client/index.js b/src/client/index.js index 760044e..8d3b8d0 100644 --- a/src/client/index.js +++ b/src/client/index.js @@ -1,203 +1,206 @@ -import { - getNode, - addNodeListener, - startProfiler, - stopProfiler, - getSvelteVersion -} from 'svelte-listener' -import { highlight, startPicker, stopPicker } from './highlight.js' - -window.__svelte_devtools_inject_state = function(id, key, value) { - let component = getNode(id).detail - component.$inject_state({ [key]: value }) -} - -window.__svelte_devtools_select_element = function(element) { - let node = getNode(element) - if (node) window.postMessage({ type: 'inspect', node: serializeNode(node) }) -} - -window.addEventListener('message', e => handleMessage(e.data), false) - -function handleMessage(msg) { - const node = getNode(msg.nodeId) - - switch (msg.type) { - case 'setSelected': - if (node) window.$s = node.detail - break - - case 'setHover': - highlight(node) - break - - case 'startPicker': - startPicker() - break - - case 'stopPicker': - stopPicker() - break - - case 'startProfiler': - startProfiler() - break - - case 'stopProfiler': - stopProfiler() - break - } -} - +import { highlight } from './highlight.js'; +import { addListener } from './listener.js'; +// import { profiler } from './profiler.js'; +import { getNode } from './svelte.js'; + +// @ts-ignore - possibly find an alternative +window.__svelte_devtools_inject_state = function (id, key, value) { + const { detail: component } = getNode(id) || {}; + component && component.$inject_state({ [key]: value }); +}; + +// @ts-ignore - possibly find an alternative +window.__svelte_devtools_select_element = function (element) { + const node = getNode(element); + if (node) send('inspect', { node: serialize(node) }); +}; + +window.addEventListener('message', ({ data, source }) => { + // only accept messages from our application or script + if (source !== window || data?.source !== 'svelte-devtools') return; + + if (data.type === 'ext/select') { + const node = getNode(data.payload); + // @ts-expect-error - saved for `inspect()` + if (node) window.$n = node.detail; + } else if (data.type === 'ext/highlight') { + const node = getNode(data.payload); + return highlight(node); + } + + // --- TODO: cleanup/implement below --- + + // case 'ext/inspect': { + // console.log(data.payload, data.payload instanceof HTMLElement); + // /** @param {MouseEvent} event */ + // const move = ({ target }) => highlight({ type: 'element', detail: target }); + // if (data.payload instanceof HTMLElement) { + // const node = getNode(data.payload); + // if (node) window.postMessage({ type: 'inspect', node: serialize(node) }); + // } else if (data.payload === 'start') { + // document.addEventListener('mousemove', move, true); + // document.addEventListener( + // 'click', + // ({ target }) => { + // document.removeEventListener('mousemove', move, true); + // const node = getNode(/** @type {Node} */ (target)); + // if (node) window.postMessage({ type: 'inspect', node: serialize(node) }); + // }, + // { capture: true, once: true }, + // ); + // return; + // } + // document.removeEventListener('mousemove', move, true); + // return highlight(undefined); + // } + + // case 'ext/profiler': { + // return data.payload ? startProfiler() : stopProfiler(); + // } + + // switch (data.type) { + + // case 'startPicker': + // startPicker(); + // break; + + // case 'stopPicker': + // stopPicker(); + // break; + + // case 'startProfiler': + // profiler.start(); + // break; + + // case 'stopProfiler': + // profiler.stop(); + // break; +}); + +/** + * @param {unknown} value + * @returns {any} + */ function clone(value, seen = new Map()) { - switch (typeof value) { - case 'function': - return { __isFunction: true, source: value.toString(), name: value.name } - case 'symbol': - return { __isSymbol: true, name: value.toString() } - case 'object': - if (value === window || value === null) return null - if (Array.isArray(value)) return value.map(o => clone(o, seen)) - if (seen.has(value)) return {} - - const o = {} - seen.set(value, o) - - for (const [key, v] of Object.entries(value)) { - o[key] = clone(v, seen) - } - - return o - default: - return value - } -} - -function gte(major, minor, patch) { - const version = (getSvelteVersion() || '0.0.0') - .split('.') - .map(n => parseInt(n)) - return ( - version[0] > major || - (version[0] == major && - (version[1] > minor || (version[1] == minor && version[2] >= patch))) - ) + switch (typeof value) { + case 'function': + return { __is: 'function', source: value.toString(), name: value.name }; + case 'symbol': + return { __is: 'symbol', name: value.toString() }; + case 'object': { + if (value === window || value === null) return null; + if (Array.isArray(value)) return value.map((o) => clone(o, seen)); + if (seen.has(value)) return {}; + + /** @type {Record} */ + const o = {}; + seen.set(value, o); + for (const [key, v] of Object.entries(value)) { + o[key] = clone(v, seen); + } + return o; + } + default: + return value; + } } -let _shouldUseCapture = null -function shouldUseCapture() { - return _shouldUseCapture == null - ? (_shouldUseCapture = gte(3, 19, 2)) - : _shouldUseCapture +/** @param {SvelteBlockDetail} node */ +function serialize(node) { + const res = /** @type {SvelteBlockDetail} */ ({ + id: node.id, + type: node.type, + tagName: node.tagName, + detail: {}, + }); + switch (node.type) { + case 'component': { + const { $$: internal = {} } = node.detail; + const ctx = clone(node.detail.$capture_state?.() || {}); + const bindings = Object.values(internal.bound || {}).map( + /** @param {Function} f */ (f) => f.name, + ); + const props = Object.keys(internal.props || {}).flatMap((key) => { + const value = ctx[key]; + delete ctx[key]; // deduplicate for ctx + if (value === undefined) return []; + + const bounded = bindings.some((f) => f.includes(key)); + return { key, value, bounded }; + }); + + res.detail = { + attributes: props, + listeners: Object.entries(internal.callbacks || {}).flatMap(([event, value]) => + value.map(/** @param {Function} f */ (f) => ({ event, handler: f.toString() })), + ), + ctx: Object.entries(ctx).map(([key, value]) => ({ key, value })), + }; + break; + } + + case 'element': { + /** @type {Attr[]} from {NamedNodeMap} */ + const attributes = Array.from(node.detail.attributes || []); + + /** @type {NonNullable} */ + const listeners = node.detail.__listeners || []; + + res.detail = { + attributes: attributes.map(({ name: key, value }) => ({ key, value })), + listeners: listeners.map((o) => ({ ...o, handler: o.handler.toString() })), + }; + break; + } + + case 'text': { + res.detail = { + nodeValue: node.detail.nodeValue, + }; + break; + } + + case 'iteration': + case 'block': { + const { ctx, source } = node.detail; + const cloned = Object.entries(clone(ctx)); + res.detail = { + ctx: cloned.map(([key, value]) => ({ key, value })), + source: source.slice(source.indexOf('{'), source.indexOf('}') + 1), + }; + break; + } + } + + return res; } -function serializeNode(node) { - const serialized = { - id: node.id, - type: node.type, - tagName: node.tagName - } - switch (node.type) { - case 'component': { - if (!node.detail.$$) { - serialized.detail = {} - break - } - - const internal = node.detail.$$ - const props = Array.isArray(internal.props) - ? internal.props // Svelte < 3.13.0 stored props names as an array - : Object.keys(internal.props) - let ctx = clone( - shouldUseCapture() ? node.detail.$capture_state() : internal.ctx - ) - if (ctx === undefined) ctx = {} - - serialized.detail = { - attributes: props.flatMap(key => { - const value = ctx[key] - delete ctx[key] - return value === undefined - ? [] - : { key, value, isBound: key in internal.bound } - }), - listeners: Object.entries(internal.callbacks).flatMap( - ([event, value]) => value.map(o => ({ event, handler: o.toString() })) - ), - ctx: Object.entries(ctx).map(([key, value]) => ({ key, value })) - } - break - } - - case 'element': { - const element = node.detail - serialized.detail = { - attributes: Array.from(element.attributes).map(attr => ({ - key: attr.name, - value: attr.value - })), - listeners: element.__listeners - ? element.__listeners.map(o => ({ - ...o, - handler: o.handler.toString() - })) - : [] - } - - break - } - - case 'text': { - serialized.detail = { - nodeValue: node.detail.nodeValue - } - break - } - - case 'iteration': - case 'block': { - const { ctx, source } = node.detail - serialized.detail = { - ctx: Object.entries(clone(ctx)).map(([key, value]) => ({ - key, - value - })), - source: source.substring(source.indexOf('{'), source.indexOf('}') + 1) - } - } - } - - return serialized +/** + * @param {string} type + * @param {Record} [payload] + */ +function send(type, payload) { + window.postMessage({ source: 'svelte-devtools', type, payload }); } -addNodeListener({ - add(node, anchor) { - window.postMessage({ - target: node.parent ? node.parent.id : null, - anchor: anchor ? anchor.id : null, - type: 'addNode', - node: serializeNode(node) - }) - }, - - remove(node) { - window.postMessage({ - type: 'removeNode', - node: serializeNode(node) - }) - }, - - update(node) { - window.postMessage({ - type: 'updateNode', - node: serializeNode(node) - }) - }, - - profile(frame) { - window.postMessage({ - type: 'updateProfile', - frame - }) - } -}) +addListener({ + add(node, anchor) { + send('courier/node:add', { + node: serialize(node), + target: node.parent?.id ?? null, + anchor: anchor?.id ?? null, + }); + }, + + remove(node) { + send('courier/node:remove', { node: serialize(node) }); + }, + + update(node) { + send('courier/node:update', { node: serialize(node) }); + }, + + profile(/** frame */) { + // send('courier/profile:update', { frame }); + }, +}); diff --git a/src/client/listener.js b/src/client/listener.js new file mode 100644 index 0000000..c8557ff --- /dev/null +++ b/src/client/listener.js @@ -0,0 +1,39 @@ +export const listeners = { + /** + * @param {SvelteBlockDetail} node + * @param {SvelteBlockDetail} [anchor] + */ + add(node, anchor) { + nodes.forEach(({ add }) => add(node, anchor)); + }, + + /** @param {SvelteBlockDetail} node */ + update(node) { + node && nodes.forEach(({ update }) => update(node)); + }, + + /** @param {SvelteBlockDetail} node */ + remove(node) { + nodes.forEach(({ remove }) => remove(node)); + }, + + /** @param {any} frame */ + profile(frame) { + nodes.forEach(({ profile }) => profile(frame)); + }, +}; + +/** @type {typeof listeners[]} */ +const nodes = []; + +/** @param {typeof nodes[number]} listener */ +export function addListener(listener) { + nodes.push(listener); +} + +/** @param {typeof nodes[number]} listener */ +export function removeListener(listener) { + const index = nodes.indexOf(listener); + if (index === -1) return false; + return !!nodes.splice(index, 1); +} diff --git a/src/client/profiler.js b/src/client/profiler.js new file mode 100644 index 0000000..858311f --- /dev/null +++ b/src/client/profiler.js @@ -0,0 +1,57 @@ +// import { listeners } from './listener.js'; + +let main = { + type: 'top', + start: -1, + end: -1, + children: /** @type {any[]} */ ([]), +}; +// let current = main; + +export const profiler = { + enabled: false, + + start() { + main = { + type: 'top', + start: performance.now(), + end: -1, + children: [], + }; + // current = main; + this.enabled = true; + }, + + stop() { + main.end = performance.now(); + this.enabled = false; + }, + + clear() { + main.children = []; + main.start = performance.now(); + }, + + // update(node, type, fn, ...args) { + // if (!this.enabled) return fn(...args); + + // const parentFrame = current; + // current = { + // type, + // node: node.id, + // start: performance.now(), + // children: [], + // }; + // parentFrame.children.push(current); + // fn(...args); + // current.end = performance.now(); + // current.duration = current.end - current.start; + // current = parentFrame; + + // if (current.type === 'top') { + // main.duration = main.children[main.children.length - 1].end - main.children[0].start; + // } + + // listeners.profile(main); + // }, +}; diff --git a/src/client/svelte.js b/src/client/svelte.js new file mode 100644 index 0000000..7183cb3 --- /dev/null +++ b/src/client/svelte.js @@ -0,0 +1,277 @@ +import { listeners } from './listener.js'; +// import { profiler } from './profiler.js'; + +/** @type {undefined | SvelteBlockDetail} */ +let current_block; +let pointer = 0; + +/** @param {number | Node} id */ +export function getNode(id) { + return nodes.map.get(id); +} + +const nodes = { + /** @type {SvelteBlockDetail[]} */ + root: [], + + /** @type {Map} */ + map: new Map(), + + /** @param {{ node: SvelteBlockDetail; target?: Node; anchor?: Node }} opts */ + add({ node, target: source, anchor }) { + this.map.set(node.id, node); + this.map.set(node.detail, node); + + let target = source && this.map.get(source); + if (!target || target.container != node.container) { + target = node.container; + } + node.parent = target; + + const sibling = this.map.get(anchor); + if (target) { + const index = target.children.findIndex((n) => n === sibling); + if (index === -1) target.children.push(node); + else target.children.splice(index, 0, node); + } else { + this.root.push(node); + } + + listeners.add(node, sibling); + }, + + /** @param {SvelteBlockDetail} node */ + remove(node) { + if (!node) return; + + this.map.delete(node.id); + this.map.delete(node.detail); + + if (node.parent) { + node.parent.children = node.parent.children.filter((n) => n !== node); + node.parent = undefined; + } + + listeners.remove(node); + }, +}; + +document.addEventListener('SvelteRegisterComponent', ({ detail }) => { + const { component, tagName } = detail; + + const node = nodes.map.get(component.$$.fragment); + if (node) { + nodes.map.delete(component.$$.fragment); + + node.detail = component; + node.tagName = tagName; + + listeners.update(node); + } else { + // @ts-expect-error - component special case + nodes.map.set(component.$$.fragment, { + type: 'component', + detail: component, + tagName, + }); + } +}); + +/** @type {any} */ +let last_promise; +document.addEventListener('SvelteRegisterBlock', ({ detail }) => { + const { type, id, block, ...rest } = detail; + const current_node_id = pointer++; + + if (block.m) { + const original = block.m; + block.m = (target, anchor) => { + const parent = current_block; + + // @ts-expect-error - only the necessities + const node = /** @type {SvelteBlockDetail} */ ({ + id: current_node_id, + type: 'block', + detail: rest, + tagName: type === 'pending' ? 'await' : type, + container: parent, + children: [], + }); + + switch (type) { + case 'then': + case 'catch': + if (!node.container) node.container = last_promise; + break; + + case 'slot': + node.type = 'slot'; + break; + + case 'component': { + const component = nodes.map.get(block); + if (component) { + nodes.map.delete(block); + Object.assign(node, component); + } else { + node.type = 'component'; + node.tagName = 'Unknown'; + node.detail = {}; + nodes.map.set(block, node); + } + + Promise.resolve().then(() => { + const invalidate = node.detail.$$?.bound || {}; + Object.keys(invalidate).length && listeners.update(node); + }); + break; + } + } + + if (type === 'each') { + let group = parent && nodes.map.get(parent.id + id); + if (!group) { + // @ts-expect-error - each block fallback + group = /** @type {SvelteBlockDetail} */ ({ + version: '', + id: pointer++, + type: 'block', + tagName: 'each', + container: parent, + children: [], + detail: { + ctx: {}, + source: detail.source, + }, + }); + parent && nodes.map.set(parent.id + id, group); + nodes.add({ node: group, target, anchor }); + } + + node.container = group; + node.type = 'iteration'; + + // @ts-expect-error - overloaded nodes + nodes.add({ node, target: group, anchor }); + } else { + nodes.add({ node, target, anchor }); + } + + current_block = node; + + // profiler.update(node, 'mount', original, target, anchor); + original(target, anchor); + + current_block = parent; + }; + } + + if (block.p) { + const original = block.p; + block.p = (changed, ctx) => { + const parent = current_block; + current_block = nodes.map.get(current_node_id); + current_block && listeners.update(current_block); + + // profiler.update(current_block, 'patch', original, changed, ctx); + original(changed, ctx); + + current_block = parent; + }; + } + + if (block.d) { + const original = block.d; + block.d = (detaching) => { + const node = nodes.map.get(current_node_id); + if (node) { + if (node.tagName === 'await') { + last_promise = node.container; + } + nodes.remove(node); + } + + // profiler.update(node, 'detach', original, detaching); + original(detaching); + }; + } +}); + +document.addEventListener('SvelteDOMInsert', ({ detail }) => { + deep_insert(detail); // { node, target, anchor } + + /** @param {Omit} opts */ + function deep_insert({ node: element, target, anchor }) { + const type = + element.nodeType === Node.ELEMENT_NODE + ? 'element' + : element.nodeValue && element.nodeValue !== ' ' + ? 'text' + : 'anchor'; + + nodes.add({ + anchor, + target, + // @ts-expect-error - missing properties are irrelevant + node: { + id: pointer++, + type, + detail: element, + tagName: element.nodeName.toLowerCase(), + container: current_block, + children: [], + }, + }); + + element.childNodes.forEach((child) => { + !nodes.map.has(child) && deep_insert({ node: child, target: element }); + }); + } +}); + +document.addEventListener('SvelteDOMRemove', ({ detail }) => { + const node = nodes.map.get(detail.node); + if (node) nodes.remove(node); +}); + +document.addEventListener('SvelteDOMAddEventListener', ({ detail }) => { + const { node, ...rest } = detail; + node.__listeners = node.__listeners || []; + node.__listeners.push(rest); +}); + +document.addEventListener('SvelteDOMRemoveEventListener', ({ detail }) => { + const { node, event, handler, modifiers } = detail; + if (!node.__listeners || node.__listeners.length) return; + node.__listeners = node.__listeners.filter( + (l) => l.event !== event || l.handler !== handler || l.modifiers !== modifiers, + ); +}); + +document.addEventListener('SvelteDOMSetData', ({ detail }) => { + const node = nodes.map.get(detail.node); + if (!node) return; + if (node.type === 'anchor') node.type = 'text'; + listeners.update(node); +}); + +document.addEventListener('SvelteDOMSetProperty', ({ detail }) => { + const node = nodes.map.get(detail.node); + if (!node) return; + if (node.type === 'anchor') node.type = 'text'; + listeners.update(node); +}); + +document.addEventListener('SvelteDOMSetAttribute', ({ detail }) => { + const node = nodes.map.get(detail.node); + if (!node) return; + if (node.type === 'anchor') node.type = 'text'; + listeners.update(node); +}); + +document.addEventListener('SvelteDOMRemoveAttribute', ({ detail }) => { + const node = nodes.map.get(detail.node); + if (!node) return; + if (node.type === 'anchor') node.type = 'text'; + listeners.update(node); +}); diff --git a/src/entry.ts b/src/entry.ts new file mode 100644 index 0000000..6775fb8 --- /dev/null +++ b/src/entry.ts @@ -0,0 +1,12 @@ +import './app.css'; +import App from './routes/+layout.svelte'; + +if (chrome.devtools.panels.themeName === 'dark') { + document.body.classList.add('dark'); +} else { + document.body.classList.remove('dark'); +} + +export default new App({ + target: document.querySelector('#app')!, +}); diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 3fe2db7..0000000 --- a/src/index.js +++ /dev/null @@ -1,14 +0,0 @@ -import './base.css' -import { devtools } from 'chrome' -import App from './App.svelte' - -function setDarkMode(theme) { - if (theme == 'dark') document.body.classList.add('dark') - else document.body.classList.remove('dark') -} - -setDarkMode(devtools.panels.themeName) -if (devtools.panels.onThemeChanged) - devtools.panels.onThemeChanged.addListener(setDarkMode) - -new App({ target: document.body }) diff --git a/src/lib/components/Button.svelte b/src/lib/components/Button.svelte new file mode 100644 index 0000000..f6038e0 --- /dev/null +++ b/src/lib/components/Button.svelte @@ -0,0 +1,77 @@ + + + + + diff --git a/src/lib/components/Divider.svelte b/src/lib/components/Divider.svelte new file mode 100644 index 0000000..abd5d57 --- /dev/null +++ b/src/lib/components/Divider.svelte @@ -0,0 +1,26 @@ + + +
+ + diff --git a/src/lib/components/Indexer.svelte b/src/lib/components/Indexer.svelte new file mode 100644 index 0000000..4c69cb8 --- /dev/null +++ b/src/lib/components/Indexer.svelte @@ -0,0 +1,32 @@ + + +

+ {#if i === -1 || $query.length < 2} + {text} + {:else} + {#if i !== 0}{text.slice(0, i)}{/if} + {text.slice(i, i + $query.length)} + {#if i + $query.length < text.length} + {text.slice(i + $query.length)} + {/if} + {/if} +

+ + diff --git a/src/lib/components/Relative.svelte b/src/lib/components/Relative.svelte new file mode 100644 index 0000000..38bd0dc --- /dev/null +++ b/src/lib/components/Relative.svelte @@ -0,0 +1,12 @@ +
+ +
+ + diff --git a/src/lib/components/Resizable.svelte b/src/lib/components/Resizable.svelte new file mode 100644 index 0000000..bb63298 --- /dev/null +++ b/src/lib/components/Resizable.svelte @@ -0,0 +1,78 @@ + + + (resizing = false)} + on:mousemove={({ pageX: x, pageY: y }) => { + if (!resizing) return; + size = axis === 'x' ? window.innerWidth - x : window.innerHeight - y; + }} +/> + + + + diff --git a/src/lib/components/Toolbar.svelte b/src/lib/components/Toolbar.svelte new file mode 100644 index 0000000..54e4e6f --- /dev/null +++ b/src/lib/components/Toolbar.svelte @@ -0,0 +1,18 @@ + + + diff --git a/src/lib/nodes/Block.svelte b/src/lib/nodes/Block.svelte new file mode 100644 index 0000000..85310f0 --- /dev/null +++ b/src/lib/nodes/Block.svelte @@ -0,0 +1,38 @@ + + + + +{#if expanded} + + +
+ +
+{/if} + + diff --git a/src/lib/nodes/Element.svelte b/src/lib/nodes/Element.svelte new file mode 100644 index 0000000..3c4c03e --- /dev/null +++ b/src/lib/nodes/Element.svelte @@ -0,0 +1,98 @@ + + +{#if empty} +
+ < + + + + +  /> +
+{:else} + + {#if expanded} + +
+ </ + + + + > +
+ {/if} +{/if} + + diff --git a/src/lib/nodes/ElementAttributes.svelte b/src/lib/nodes/ElementAttributes.svelte new file mode 100644 index 0000000..c76a41e --- /dev/null +++ b/src/lib/nodes/ElementAttributes.svelte @@ -0,0 +1,64 @@ + + +{#each attributes as { key, value, bounded, flash } (key)} + {@const prefix = bounded ? 'bind:' : ''} + +   + + + + + = + + + + +{/each} + +{#each listeners as { event, handler, modifiers }} + {@const suffix = modifiers?.length ? `|${modifiers.join('|')}` : ''} + +   + + + +{/each} + + diff --git a/src/lib/nodes/Ellipsis.svelte b/src/lib/nodes/Ellipsis.svelte new file mode 100644 index 0000000..9edc0e8 --- /dev/null +++ b/src/lib/nodes/Ellipsis.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/nodes/Iteration.svelte b/src/lib/nodes/Iteration.svelte new file mode 100644 index 0000000..d838ec8 --- /dev/null +++ b/src/lib/nodes/Iteration.svelte @@ -0,0 +1,17 @@ + + + + + +{#if expanded} + +{/if} diff --git a/src/lib/nodes/Node.svelte b/src/lib/nodes/Node.svelte new file mode 100644 index 0000000..3133ad4 --- /dev/null +++ b/src/lib/nodes/Node.svelte @@ -0,0 +1,161 @@ + + +{#if $visibility[node.type]} + + +
  • (flash = false)} + on:click|stopPropagation={() => selected.set(node)} + on:mousemove|stopPropagation={() => { + if ($hovered?.id === node.id) return; + background.send('ext/highlight', node.id); + hovered.set(node); + }} + > + {#if node.type === 'component' || node.type === 'element'} + +
      + {#each node.children as child (child.id)} + + {/each} +
    +
    + {:else if node.type === 'block'} + +
      + {#each node.children as child (child.id)} + + {/each} +
    +
    + {:else if node.type === 'iteration'} + +
      + {#each node.children as child (child.id)} + + {/each} +
    +
    + {:else if node.type === 'slot'} + +
      + {#each node.children as child (child.id)} + + {/each} +
    +
    + {:else if node.type === 'text'} +
    + +
    + {:else if node.type === 'anchor'} +
    #anchor
    + {/if} +
  • +{:else} + {#each node.children as child (child.id)} + + {/each} +{/if} + + diff --git a/src/lib/nodes/Slot.svelte b/src/lib/nodes/Slot.svelte new file mode 100644 index 0000000..ff8073e --- /dev/null +++ b/src/lib/nodes/Slot.svelte @@ -0,0 +1,24 @@ + + + + +{#if expanded} + + +
    + `} color="#c586c0" /> +
    +{/if} diff --git a/src/lib/panel/Editable.svelte b/src/lib/panel/Editable.svelte new file mode 100644 index 0000000..8ae7566 --- /dev/null +++ b/src/lib/panel/Editable.svelte @@ -0,0 +1,69 @@ + + +{#if editing} + { + editing = false; + // @ts-expect-error - target and value exists + const updated = target.value; + value = JSON.parse(updated); + dispatch('change', updated); + }} + on:keydown={({ key, target }) => { + if (key !== 'Enter') return; + editing = false; + // @ts-expect-error - target and value exists + const updated = target.value; + value = JSON.parse(updated); + dispatch('change', updated); + }} + /> +{:else} + + + (editing = !readonly)}> + {JSON.stringify(value)} + +{/if} + + diff --git a/src/lib/panel/Expandable.svelte b/src/lib/panel/Expandable.svelte new file mode 100644 index 0000000..c3a3d71 --- /dev/null +++ b/src/lib/panel/Expandable.svelte @@ -0,0 +1,141 @@ + + + + +
  • (expanded = !expanded)} +> + {key}: +   + + {#if type === 'string'} + + {:else if value == null || value !== value} + + {:else if type === 'number' || type === 'boolean'} + + {:else if Array.isArray(value)} + Array [{value.length || ''}] + + {#if value.length && expanded} +
      + {#each value as v, key} + dispatch('change', stringify(value, key, e.detail))} + /> + {/each} +
    + {/if} + {:else if type === 'object'} + {#if value.__is === 'function'} + function {value.name || ''}() + {#if expanded}
    {value.source}
    {/if} + {:else if value.__is === 'symbol'} + {value.name || 'Symbol()'} + {:else if Object.keys(value).length} + Object {…} + {#if expanded} +
      + {#each Object.entries(value) as [key, v] (key)} + dispatch('change', stringify(value, key, e.detail))} + /> + {/each} +
    + {/if} + {:else} + Object { } + {/if} + {/if} +
  • + + diff --git a/src/lib/panel/PropertyList.svelte b/src/lib/panel/PropertyList.svelte new file mode 100644 index 0000000..b4618d1 --- /dev/null +++ b/src/lib/panel/PropertyList.svelte @@ -0,0 +1,45 @@ + + +{#if entries.length} +
      + {#each entries as { key, value } (key)} + change(key, e.detail)} + /> + {/each} +
    +{:else} +
    None
    +{/if} + + diff --git a/src/lib/runtime.ts b/src/lib/runtime.ts new file mode 100644 index 0000000..993ed27 --- /dev/null +++ b/src/lib/runtime.ts @@ -0,0 +1,115 @@ +import { type DebugNode, hovered, root, selected } from './store'; + +const tabId = chrome.devtools.inspectedWindow.tabId; +const port = chrome.runtime.connect({ name: `${tabId}` }); + +port.postMessage({ source: 'svelte-devtools', tabId, type: 'ext/init' }); + +export const background = { + send(type: `${'ext' | 'page'}/${string}`, payload?: any) { + port.postMessage({ source: 'svelte-devtools', tabId, type, payload }); + }, +}; + +const nodes = new Map(); + +function resolveEventBubble(node: any) { + if (!node.detail || !node.detail.listeners) return; + + for (const listener of node.detail.listeners) { + if (!listener.handler.includes('bubble($$self, event)')) continue; + + listener.handler = () => { + let target = node; + while ((target = target.parent)) if (target.type === 'component') break; + + const listeners = target.detail.listeners; + if (!listeners) return null; + + const parentListener = listeners.find((o: any) => o.event === listener.event); + if (!parentListener) return null; + + const handler = parentListener.handler; + if (!handler) return null; + + return `// From parent\n${handler}`; + }; + } +} + +port.onMessage.addListener(({ type, payload }) => { + switch (type) { + case 'ext/clear': { + selected.set(undefined); + hovered.set(undefined); + return root.set([]); + } + + case 'ext/inspect': { + const current = nodes.get(payload.node.id); + return selected.set(current); + } + + case 'courier/node:add': { + const { node, target, anchor } = payload as { + node: DebugNode; + target: null | number; + anchor: null | number; + }; + + node.children = []; + node.expanded = false; + node.invalidate = () => {}; + resolveEventBubble(node); + + const parent = nodes.get(target); + nodes.set(node.id, node); + if (!parent) return root.update((n) => [...n, node]); + + const index = parent.children.findIndex((n) => n.id === anchor); + if (index === -1) parent.children.push(node); + else parent.children.splice(index, 0, node); + + return (node.parent = parent).invalidate(); + } + + case 'courier/node:remove': { + const node = payload.node as SvelteBlockDetail; + const current = nodes.get(node.id); + if (current) nodes.delete(current.id); + if (!current?.parent) return; + + const index = current.parent.children.findIndex((o) => o.id === current.id); + current.parent.children.splice(index, 1); + return current.parent.invalidate(); + } + + case 'courier/node:update': { + const node = payload.node as SvelteBlockDetail; + const current = nodes.get(node.id); + if (!current) return; + Object.assign(current, payload.node); + resolveEventBubble(current); + + selected.update((o) => o); + return current.invalidate(); + } + + // case 'courier/profile:update': { + // resolveFrame(frame); + // profileFrame.set(frame); + // break; + + // function resolveFrame(frame) { + // frame.children.forEach(resolveFrame); + + // if (!frame.node) return; + + // frame.node = nodes.get(frame.node) || { + // type: 'Unknown', + // tagName: 'Unknown', + // }; + // } + // } + } +}); diff --git a/src/lib/store.ts b/src/lib/store.ts new file mode 100644 index 0000000..3f0e6e9 --- /dev/null +++ b/src/lib/store.ts @@ -0,0 +1,56 @@ +import { writable } from 'svelte/store'; + +type Overwrite = Omit & B; + +export type DebugNode = Overwrite< + SvelteBlockDetail, + { + invalidate(): void; + expanded: boolean; + detail: { + attributes?: Array<{ + key: string; + value: string; + bounded?: boolean; + flash?: boolean; + }>; + listeners?: Array<{ + event: any; + handler: any; + modifiers: any; + }>; + ctx: any; + source: string; + nodeValue: string; + }; + + tagName: string; + parent: DebugNode; + children: DebugNode[]; + dom?: HTMLLIElement; + } +>; +export const selected = writable(undefined); +export const hovered = writable(undefined); +export const root = writable([]); +export const visibility = writable<{ [key: string]: boolean }>({ + component: true, + element: true, + block: true, + iteration: true, + slot: true, + text: true, + anchor: false, +}); + +export const query = writable(''); + +export type Profiler = { + type: 'mount' | 'patch' | 'detach'; + node: DebugNode; + duration: number; + start: number; + end: number; + children: Profiler[]; +}; +export const profileFrame = writable(undefined); diff --git a/src/nodes/Anchor.svelte b/src/nodes/Anchor.svelte deleted file mode 100644 index 462f652..0000000 --- a/src/nodes/Anchor.svelte +++ /dev/null @@ -1,15 +0,0 @@ - - - - -
    #anchor
    diff --git a/src/nodes/Block.svelte b/src/nodes/Block.svelte deleted file mode 100644 index b9b7381..0000000 --- a/src/nodes/Block.svelte +++ /dev/null @@ -1,55 +0,0 @@ - - - - -
    (collapsed = !collapsed)}> - - {#if source} - {source} - {:else} - {# - - } - {/if} - {#if collapsed} - …{/ - - } - {/if} -
    -{#if !collapsed} - -
    - {/ - - } -
    -{/if} diff --git a/src/nodes/Collapse.svelte b/src/nodes/Collapse.svelte deleted file mode 100644 index da360e5..0000000 --- a/src/nodes/Collapse.svelte +++ /dev/null @@ -1,53 +0,0 @@ - - - - - (collapsed = !collapsed)} /> diff --git a/src/nodes/Element.svelte b/src/nodes/Element.svelte deleted file mode 100644 index 5a53bf3..0000000 --- a/src/nodes/Element.svelte +++ /dev/null @@ -1,106 +0,0 @@ - - - - -{#if hasChildren} -
    (collapsed = !collapsed)}> - - < - - - - - > - {#if collapsed} - …</ - - - - > - {/if} -
    - {#if !collapsed} - -
    - </ - - - - > -
    - {/if} -{:else} -
    - < - - - - -  /> -
    -{/if} diff --git a/src/nodes/ElementAttributes.svelte b/src/nodes/ElementAttributes.svelte deleted file mode 100644 index 45688d5..0000000 --- a/src/nodes/ElementAttributes.svelte +++ /dev/null @@ -1,56 +0,0 @@ - - - - -{#each attributes as { key, value, isBound, flash } (key)} -   - - - {#if isBound}bind:{/if} - - - = - - - - -{/each} - -{#each listeners as { event, handler, modifiers }} -   - - on: - - {#if modifiers && modifiers.length}|{modifiers.join('|')}{/if} - -{/each} diff --git a/src/nodes/Iteration.svelte b/src/nodes/Iteration.svelte deleted file mode 100644 index 07a84f9..0000000 --- a/src/nodes/Iteration.svelte +++ /dev/null @@ -1,23 +0,0 @@ - - - - -
    - diff --git a/src/nodes/Node.svelte b/src/nodes/Node.svelte deleted file mode 100644 index 8f9287f..0000000 --- a/src/nodes/Node.svelte +++ /dev/null @@ -1,128 +0,0 @@ - - - - -{#if $visibility[node.type]} -
  • (flash = false)} - on:mouseover|stopPropagation={e => ($hoveredNodeId = node.id)} - on:click|stopPropagation={e => ($selectedNode = node)}> - - {#if $selectedNode.id == node.id} - - {/if} -
      - {#each node.children as child (child.id)} - - {/each} -
    -
    -
  • -{:else} - {#each node.children as node (node.id)} - - {/each} -{/if} diff --git a/src/nodes/SearchTerm.svelte b/src/nodes/SearchTerm.svelte deleted file mode 100644 index dd76974..0000000 --- a/src/nodes/SearchTerm.svelte +++ /dev/null @@ -1,21 +0,0 @@ - - - - -{#if i == -1 || $searchValue.length < 2} - {text} -{:else}{pre}{highlight}{post}{/if} diff --git a/src/nodes/Slot.svelte b/src/nodes/Slot.svelte deleted file mode 100644 index ab1b3dd..0000000 --- a/src/nodes/Slot.svelte +++ /dev/null @@ -1,50 +0,0 @@ - - - - -
    (collapsed = !collapsed)}> - - < - - > - {#if collapsed} - …</ - - > - {/if} -
    -{#if !collapsed} - -
    - </ - - > -
    -{/if} diff --git a/src/nodes/Text.svelte b/src/nodes/Text.svelte deleted file mode 100644 index 7a8674f..0000000 --- a/src/nodes/Text.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - -
    - -
    diff --git a/src/panel/CollapsableValue.svelte b/src/panel/CollapsableValue.svelte deleted file mode 100644 index d99d655..0000000 --- a/src/panel/CollapsableValue.svelte +++ /dev/null @@ -1,156 +0,0 @@ - - - - -
  • (collapsed = !collapsed)}> - {#if type == 'string'} - {key}:  - - - {:else if value == null || value == undefined || value != value} - {key}:  - - - {:else if type == 'number' || type == 'boolean'} - {key}:  - - - {:else if Array.isArray(value)} - {#if value.length} - - {key}:  - Array [{value.length}] - {#if !collapsed} -
      - {#each value as v, key} - dispatch('change', stringify(value, key, e.detail))} /> - {/each} -
    - {/if} - {:else}{key}:  Array []{/if} - {:else if type == 'object'} - {#if value.__isFunction} - - {key}:  - function {value.name || ''} () - {#if !collapsed} -
    {value.source}
    - {/if} - {:else if value.__isSymbol} - {key}:  - {value.name || 'Symbol()'} - {:else if Object.keys(value).length} - - {key}:  - Object {…} - {#if !collapsed} -
      - {#each Object.entries(value) as [key, v] (key)} - dispatch('change', stringify(value, key, e.detail))} /> - {/each} -
    - {/if} - {:else} - {key}:  - Object { } - {/if} - {/if} - {#if errorMessage}!{/if} -
  • diff --git a/src/panel/ComponentView.svelte b/src/panel/ComponentView.svelte deleted file mode 100644 index efe5957..0000000 --- a/src/panel/ComponentView.svelte +++ /dev/null @@ -1,72 +0,0 @@ - - - - - -
    - -
    - - - {#if $selectedNode.type == 'component'} - - - {:else if $selectedNode.type == 'block' || $selectedNode.type == 'iteration'} - - {:else if $selectedNode.type == 'element'} - - {/if} -
    - diff --git a/src/panel/Editable.svelte b/src/panel/Editable.svelte deleted file mode 100644 index fbf6b53..0000000 --- a/src/panel/Editable.svelte +++ /dev/null @@ -1,50 +0,0 @@ - - - - -{#if isEditing} - e.key == 'Enter' && commit(e)} - on:blur={commit} /> -{:else} - (isEditing = !readOnly)}> - {JSON.stringify(value)} - -{/if} diff --git a/src/panel/Panel.svelte b/src/panel/Panel.svelte deleted file mode 100644 index 3726eef..0000000 --- a/src/panel/Panel.svelte +++ /dev/null @@ -1,53 +0,0 @@ - - - - - isResizing && (size = grow == 'left' ? window.innerWidth - e.x : window.innerHeight - e.y)} - on:mouseup={e => (isResizing = false)} /> - -
    -
    (isResizing = true)} /> - -
    diff --git a/src/panel/PropertyList.svelte b/src/panel/PropertyList.svelte deleted file mode 100644 index 0d33519..0000000 --- a/src/panel/PropertyList.svelte +++ /dev/null @@ -1,61 +0,0 @@ - - - - -

    {header}

    - -{#if entries.length} -
      - {#each entries as { key, value } (key)} - change(key, e.detail)} /> - {/each} -
    -{:else} -
    None
    -{/if} diff --git a/src/profiler/Frame.svelte b/src/profiler/Frame.svelte deleted file mode 100644 index e6dca49..0000000 --- a/src/profiler/Frame.svelte +++ /dev/null @@ -1,28 +0,0 @@ - - - - -{#if children} -
      - {#each children as child, i} -
    • - - -
    • - {/each} -
    -{/if} diff --git a/src/profiler/Operation.svelte b/src/profiler/Operation.svelte deleted file mode 100644 index 24b30c7..0000000 --- a/src/profiler/Operation.svelte +++ /dev/null @@ -1,41 +0,0 @@ - - - - -
    dispatch('click', frame)}> - ‌ - {frame.node.tagName} -
    diff --git a/src/profiler/Profiler.svelte b/src/profiler/Profiler.svelte deleted file mode 100644 index 07af9e2..0000000 --- a/src/profiler/Profiler.svelte +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - {#if top} - - {:else} - - {/if} - - -
    - {#if children.length} - - {:else} -

    Nothing to display. Perform an action or refresh the page.

    - {/if} -
    -{#if selected} - -
    -
    - Tag name - {selected.node.tagName} (#{selected.node.id}) -
    -
    Start {round(selected.start)}ms
    -
    Operation {selected.type}
    -
    Block type {selected.node.type}
    -
    End {round(selected.end)}ms
    -
    - Duration - {round(selected.children.reduce((acc, o) => acc - o.duration, selected.duration))}ms - of {round(selected.duration)}ms -
    -
    -
    -{/if} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..02b5166 --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,178 @@ + + + { + if (target !== document.body || !$selected) return; + + if (key === 'Enter') { + $selected.expanded = !$selected.expanded; + $selected.invalidate(); + } else if (key === 'ArrowRight') { + if (!$selected) $selected = $root[0]; + $selected.expanded = true; + $selected.invalidate(); + } else if (key === 'ArrowLeft') { + $selected.expanded = false; + $selected.invalidate(); + } else if (key === 'ArrowUp') { + let nodes = ($selected.parent?.children || $root).filter((n) => $visibility[n.type]); + let sibling = nodes[nodes.findIndex((o) => o.id === $selected?.id) - 1]; + while (sibling?.expanded) { + nodes = sibling.children.filter((n) => $visibility[n.type]); + sibling = nodes[nodes.length - 1]; + } + $selected = sibling ?? $selected.parent ?? $selected; + } else if (key === 'ArrowDown') { + const children = $selected.children.filter((n) => $visibility[n.type]); + + if (!$selected.expanded || children.length === 0) { + let next = $selected; + let current = $selected; + do { + const nodes = current.parent ? current.parent.children : $root; + const siblings = nodes.filter((n) => $visibility[n.type]); + const index = siblings.findIndex((o) => o.id === current.id); + next = siblings[index + 1]; + current = current.parent; + } while (!next && current); + + $selected = next ?? $selected; + } else { + $selected = children[0]; + } + } + }} +/> + +{#if $root.length} +
    + + + + + + + + + + + + + + + + + + + + +
      + {#each $root as node (node.id)} + + {/each} +
    + + +
    + + + + {@const events = $selected?.detail.listeners?.map((l) => { + const suffix = l.modifiers?.length ? `|${l.modifiers.join('|')}` : ''; + const value = { __is: 'function', source: l.handler }; + return { key: l.event + suffix, value }; + })} + + {#if $selected?.type === 'component'} +

    Props

    + + + + +

    Events

    + + + + +

    State

    + + {:else if $selected?.type === 'block' || $selected?.type === 'iteration'} +

    State

    + + {:else if $selected?.type === 'element'} +

    Attributes

    + + + + +

    Events

    + + {/if} +
    +{:else} + +{/if} + + diff --git a/src/routes/Breadcrumbs.svelte b/src/routes/Breadcrumbs.svelte new file mode 100644 index 0000000..6087997 --- /dev/null +++ b/src/routes/Breadcrumbs.svelte @@ -0,0 +1,63 @@ + + +{#if breadcrumbs.length} +
      + {#each breadcrumbs as node} + {#if $visibility[node.type]} + + {/if} + {/each} +
    +{/if} + + diff --git a/src/routes/ConnectMessage.svelte b/src/routes/ConnectMessage.svelte new file mode 100644 index 0000000..753cdb8 --- /dev/null +++ b/src/routes/ConnectMessage.svelte @@ -0,0 +1,62 @@ + + +
    +

    Svelte DevTools

    +

    + No Svelte app detected + + +

    + +
    +

    Not working? Did you...

    +
      +
    • Build with dev mode enabled?
    • +
    • Use Svelte version 4.0.0 or above?
    • +
    +
    +
    + + diff --git a/src/routes/PickerButton.svelte b/src/routes/PickerButton.svelte new file mode 100644 index 0000000..e792d86 --- /dev/null +++ b/src/routes/PickerButton.svelte @@ -0,0 +1,40 @@ + + + diff --git a/src/routes/ProfileButton.svelte b/src/routes/ProfileButton.svelte new file mode 100644 index 0000000..2ef7830 --- /dev/null +++ b/src/routes/ProfileButton.svelte @@ -0,0 +1,18 @@ + + + diff --git a/src/routes/Profiler.svelte b/src/routes/Profiler.svelte new file mode 100644 index 0000000..7ab8ef9 --- /dev/null +++ b/src/routes/Profiler.svelte @@ -0,0 +1,124 @@ + + + + {#if top} + + {:else} + + {/if} + + +
    + {#if children.length} + { + if (selected === detail) top = detail; + else selected = detail; + }} + /> + {:else} +

    Nothing to display. Perform an action or refresh the page.

    + {/if} +
    +{#if selected} + +
    +
    + Tag name + {selected.node.tagName} + (#{selected.node.id}) +
    +
    + Start + {round(selected.start)}ms +
    +
    + Operation + {selected.type} +
    +
    + Block type + {selected.node.type} +
    +
    + End + {round(selected.end)}ms +
    +
    + Duration + + {round(selected.children.reduce((acc, o) => acc - o.duration, selected.duration))}ms + + of + {round(selected.duration)}ms +
    +
    +
    +{/if} + + diff --git a/src/routes/ProfilerFrame.svelte b/src/routes/ProfilerFrame.svelte new file mode 100644 index 0000000..abc0cb9 --- /dev/null +++ b/src/routes/ProfilerFrame.svelte @@ -0,0 +1,63 @@ + + +{#if children?.length} +
      + {#each children as frame} +
    • + + + dispatch('click', frame)} /> +
    • + {/each} +
    +{/if} + + diff --git a/src/routes/SearchBox.svelte b/src/routes/SearchBox.svelte new file mode 100644 index 0000000..71d6a4d --- /dev/null +++ b/src/routes/SearchBox.svelte @@ -0,0 +1,98 @@ + + +
    + + + + + + { + if (key === 'Enter') submit[shiftKey ? 'prev' : 'next'](); + }} + /> + + {#if results.length && position > -1} + {position + 1} of {results.length} + {/if} + + + +
    + + diff --git a/src/routes/VisibilitySelection.svelte b/src/routes/VisibilitySelection.svelte new file mode 100644 index 0000000..f68ffff --- /dev/null +++ b/src/routes/VisibilitySelection.svelte @@ -0,0 +1,76 @@ + + + + + + {#if opened} +
    + {#each Object.keys($visibility) as key} + + {/each} +
    + {/if} +
    + + diff --git a/src/store.js b/src/store.js deleted file mode 100644 index f36f840..0000000 --- a/src/store.js +++ /dev/null @@ -1,299 +0,0 @@ -import { writable, get, derived } from 'svelte/store' - -export const visibility = writable({ - component: true, - element: true, - block: true, - iteration: true, - slot: true, - text: true, - anchor: false, -}) -export const selectedNode = writable({}) -export const hoveredNodeId = writable(null) -export const rootNodes = writable([]) -export const searchValue = writable('') -export const profilerEnabled = writable(false) -export const profileFrame = writable({}) - -// #if process.env.TARGET === 'firefox' -// Zoom workaround -let fontSize = 11 -window.addEventListener('keyup', e => { - if (!e.ctrlKey) return - - switch (e.key) { - case '=': - fontSize = Math.min(fontSize + 1.1, 22) - break - case '-': - fontSize = Math.max(fontSize - 1.1, 5.5) - break - case '0': - fontSize = 11 - break - } - - document.documentElement.style.fontSize = fontSize + 'px' -}) -// #endif - -function interactableNodes(list) { - const _visibility = get(visibility) - return list.filter( - o => _visibility[o.type] && o.type !== 'text' && o.type !== 'anchor' - ) -} - -window.addEventListener('keydown', e => { - if (e.target !== document.body) return - - selectedNode.update(node => { - if (node.invalidate === undefined) return node - switch (e.key) { - case 'Enter': - node.collapsed = !node.collapsed - node.invalidate() - return node - - case 'ArrowRight': - node.collapsed = false - node.invalidate() - return node - - case 'ArrowDown': { - const children = interactableNodes(node.children) - - if (node.collapsed || children.length === 0) { - var next = node - var current = node - do { - const siblings = interactableNodes( - current.parent === undefined - ? get(rootNodes) - : current.parent.children - ) - const index = siblings.findIndex(o => o.id === current.id) - next = siblings[index + 1] - - current = current.parent - } while (next === undefined && current !== undefined) - - return next ?? node - } else { - return children[0] - } - } - - case 'ArrowLeft': - node.collapsed = true - node.invalidate() - return node - - case 'ArrowUp': { - const siblings = interactableNodes( - node.parent === undefined ? get(rootNodes) : node.parent.children - ) - const index = siblings.findIndex(o => o.id === node.id) - return index > 0 ? siblings[index - 1] : node.parent ?? node - } - - default: - return node - } - }) -}) - -const nodeMap = new Map() - -const port = chrome.runtime.connect() -port.postMessage({ - type: 'init', - tabId: chrome.devtools.inspectedWindow.tabId, -}) - -export function reload() { - port.postMessage({ - type: 'reload', - tabId: chrome.devtools.inspectedWindow.tabId, - }) -} - -export function startPicker() { - port.postMessage({ - type: 'startPicker', - tabId: chrome.devtools.inspectedWindow.tabId, - }) -} - -export function stopPicker() { - port.postMessage({ - type: 'stopPicker', - tabId: chrome.devtools.inspectedWindow.tabId, - }) -} - -selectedNode.subscribe(node => { - port.postMessage({ - type: 'setSelected', - tabId: chrome.devtools.inspectedWindow.tabId, - nodeId: node.id, - }) - - let invalid = null - while (node.parent) { - node = node.parent - if (node.collapsed) { - invalid = node - node.collapsed = false - } - } - - if (invalid) invalid.invalidate() -}) - -hoveredNodeId.subscribe(nodeId => - port.postMessage({ - type: 'setHover', - tabId: chrome.devtools.inspectedWindow.tabId, - nodeId, - }) -) - -profilerEnabled.subscribe(o => - port.postMessage({ - type: o ? 'startProfiler' : 'stopProfiler', - tabId: chrome.devtools.inspectedWindow.tabId, - }) -) - -function noop() {} - -function insertNode(node, target, anchorId) { - node.parent = target - - let index = -1 - if (anchorId) index = target.children.findIndex(o => o.id == anchorId) - - if (index != -1) { - target.children.splice(index, 0, node) - } else { - target.children.push(node) - } - - target.invalidate() -} - -function resolveFrame(frame) { - frame.children.forEach(resolveFrame) - - if (!frame.node) return - - frame.node = nodeMap.get(frame.node) || { - tagName: 'Unknown', - type: 'Unknown', - } -} - -function resolveEventBubble(node) { - if (!node.detail || !node.detail.listeners) return - - for (const listener of node.detail.listeners) { - if (!listener.handler.includes('bubble($$self, event)')) continue - - listener.handler = () => { - let target = node - while ((target = target.parent)) if (target.type == 'component') break - - const listeners = target.detail.listeners - if (!listeners) return null - - const parentListener = listeners.find(o => o.event == listener.event) - if (!parentListener) return null - - const handler = parentListener.handler - if (!handler) return null - - return ( - '// From parent\n' + - (typeof handler == 'function' ? handler() : handler) - ) - } - } -} - -port.onMessage.addListener(msg => { - switch (msg.type) { - case 'clear': { - selectedNode.set({}) - hoveredNodeId.set(null) - rootNodes.set([]) - - break - } - - case 'addNode': { - const node = msg.node - node.children = [] - node.collapsed = true - node.invalidate = noop - resolveEventBubble(node) - - const targetNode = nodeMap.get(msg.target) - nodeMap.set(node.id, node) - - if (targetNode) { - insertNode(node, targetNode, msg.anchor) - return - } - - if (node._timeout) return - - node._timeout = setTimeout(() => { - delete node._timeout - const targetNode = nodeMap.get(msg.target) - if (targetNode) insertNode(node, targetNode, msg.anchor) - else rootNodes.update(o => (o.push(node), o)) - }, 100) - - break - } - - case 'removeNode': { - const node = nodeMap.get(msg.node.id) - const index = node.parent.children.findIndex(o => o.id == node.id) - node.parent.children.splice(index, 1) - nodeMap.delete(node.id) - - node.parent.invalidate() - - break - } - - case 'updateNode': { - const node = nodeMap.get(msg.node.id) - Object.assign(node, msg.node) - resolveEventBubble(node) - - const selected = get(selectedNode) - if (selected && selected.id == msg.node.id) selectedNode.update(o => o) - - node.invalidate() - - break - } - - case 'inspect': { - let node = nodeMap.get(msg.node.id) - selectedNode.set(node) - - break - } - - case 'updateProfile': { - resolveFrame(msg.frame) - profileFrame.set(msg.frame) - break - } - } -}) diff --git a/src/toolbar/Button.svelte b/src/toolbar/Button.svelte deleted file mode 100644 index f59f91f..0000000 --- a/src/toolbar/Button.svelte +++ /dev/null @@ -1,78 +0,0 @@ - - - - - diff --git a/src/toolbar/PickerButton.svelte b/src/toolbar/PickerButton.svelte deleted file mode 100644 index dcfd1d6..0000000 --- a/src/toolbar/PickerButton.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - - diff --git a/src/toolbar/ProfileButton.svelte b/src/toolbar/ProfileButton.svelte deleted file mode 100644 index 63b4d04..0000000 --- a/src/toolbar/ProfileButton.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/src/toolbar/Search.svelte b/src/toolbar/Search.svelte deleted file mode 100644 index ba4bb0a..0000000 --- a/src/toolbar/Search.svelte +++ /dev/null @@ -1,112 +0,0 @@ - - - - -
    -
    - - - - - - {#if resultsPosition > -1} - {resultsPosition + 1} of {results.length}  - {/if} - - - diff --git a/src/toolbar/Toolbar.svelte b/src/toolbar/Toolbar.svelte deleted file mode 100644 index bf73101..0000000 --- a/src/toolbar/Toolbar.svelte +++ /dev/null @@ -1,22 +0,0 @@ - - -
    - -
    diff --git a/src/toolbar/VisibilityButton.svelte b/src/toolbar/VisibilityButton.svelte deleted file mode 100644 index 99ebf02..0000000 --- a/src/toolbar/VisibilityButton.svelte +++ /dev/null @@ -1,139 +0,0 @@ - - - - - diff --git a/dest/.web-extension-id b/static/.web-extension-id similarity index 100% rename from dest/.web-extension-id rename to static/.web-extension-id diff --git a/static/background.js b/static/background.js new file mode 100644 index 0000000..ced758a --- /dev/null +++ b/static/background.js @@ -0,0 +1,132 @@ +/** @type {Map} */ +const ports = new Map(); + +chrome.runtime.onConnect.addListener((port) => { + if (port.sender?.url !== chrome.runtime.getURL('/index.html')) { + console.error(`Unexpected connection from ${port.sender?.url || ''}`); + return port.disconnect(); + } + + // messages are from the devtools page and not content script (courier.js) + port.onMessage.addListener((message, sender) => { + if (message.type === 'ext/init') { + ports.set(message.tabId, sender); + + return chrome.tabs.onUpdated.addListener(courier); + } else if (message.type === 'page/refresh') { + return chrome.tabs.reload(message.tabId, { bypassCache: true }); + } + + // relay messages from devtools page to `chrome.scripting` + return chrome.tabs.sendMessage(message.tabId, message); + }); + + port.onDisconnect.addListener((disconnected) => { + ports.delete(+disconnected.name); + + if (ports.size === 0) { + chrome.tabs.onUpdated.removeListener(courier); + } + }); +}); + +// relay messages from `chrome.scripting` to devtools page +chrome.runtime.onMessage.addListener((message, sender) => { + if (sender.id !== chrome.runtime.id) return; // unexpected sender + + if (message.type === 'ext/icon:set') { + const selected = message.payload ? 'default' : 'disabled'; + const icons = [16, 24, 48, 96, 128].map((s) => [s, `icons/${selected}-${s}.png`]); + return chrome.action.setIcon({ path: Object.fromEntries(icons) }); + } + + const port = sender.tab?.id && ports.get(sender.tab.id); + if (port) return port.postMessage(message); +}); + +/** @type {Parameters[0]} */ +function courier(tabId, changed) { + if (!ports.has(tabId) || changed.status !== 'loading') return; + + chrome.scripting.executeScript({ + target: { tabId }, + injectImmediately: true, + + // no lexical context, `func` is serialized and deserialized. + // a limbo world where both `chrome` and `window` are defined + // with many unexpected and out of the ordinary behaviors, do + // minimal work here and delegate to `courier.js` in the page. + func: () => { + const source = chrome.runtime.getURL('/courier.js'); + if (document.querySelector(`script[src="${source}"]`)) return; + + // attach script manually instead of declaring through `files` + // because `detail` in the dispatched custom events is `null` + const script = document.createElement('script'); + script.setAttribute('src', source); + document.documentElement.appendChild(script); + + // // TODO: reenable profiler + // if (message.type === 'ext/profiler' && message.payload) { + // // start profiler + // } + + chrome.runtime.onMessage.addListener((message, sender) => { + if (sender.id !== chrome.runtime.id) return; // unexpected sender + window.postMessage(message); // relay to content script (courier.js) + + // switch (message.type) { + // case 'startProfiler': + // window.sessionStorage.SvelteDevToolsProfilerEnabled = 'true'; + // break; + // case 'stopProfiler': + // case 'ext/clear': + // delete window.sessionStorage.SvelteDevToolsProfilerEnabled; + // break; + // } + }); + + window.addEventListener('message', ({ source, data }) => { + // only accept messages from our application or script + if (source === window && data?.source === 'svelte-devtools') { + chrome.runtime.sendMessage(data); + } + }); + + window.addEventListener('unload', () => { + chrome.runtime.sendMessage({ type: 'ext/clear' }); + }); + }, + }); +} + +chrome.tabs.onActivated.addListener(({ tabId }) => sensor(tabId)); +chrome.tabs.onUpdated.addListener( + (tabId, changed) => changed.status === 'loading' && sensor(tabId), +); + +/** @param {number} tabId */ +async function sensor(tabId) { + try { + await chrome.scripting.executeScript({ + target: { tabId }, + + func: () => { + const source = chrome.runtime.getURL('/sensor.js'); + document.querySelector(`script[src="${source}"]`)?.remove(); + const script = document.createElement('script'); + script.setAttribute('src', source); + document.documentElement.appendChild(script); + + document.addEventListener('SvelteDevTools', ({ detail }) => { + chrome.runtime.sendMessage(detail); + }); + }, + }); + } catch { + // for internal URLs like `chrome://` or `edge://` and extension gallery + // https://chromium.googlesource.com/chromium/src/+/ee77a52baa1f8a98d15f9749996f90e9d3200f2d/chrome/common/extensions/chrome_extensions_client.cc#131 + const icons = [16, 24, 48, 96, 128].map((s) => [s, `icons/disabled-${s}.png`]); + chrome.action.setIcon({ path: Object.fromEntries(icons) }); + } +} diff --git a/static/icons/default-128.png b/static/icons/default-128.png new file mode 100644 index 0000000..e6ee090 Binary files /dev/null and b/static/icons/default-128.png differ diff --git a/static/icons/default-16.png b/static/icons/default-16.png new file mode 100644 index 0000000..9b7bb76 Binary files /dev/null and b/static/icons/default-16.png differ diff --git a/static/icons/default-24.png b/static/icons/default-24.png new file mode 100644 index 0000000..b5eb83c Binary files /dev/null and b/static/icons/default-24.png differ diff --git a/static/icons/default-48.png b/static/icons/default-48.png new file mode 100644 index 0000000..543a150 Binary files /dev/null and b/static/icons/default-48.png differ diff --git a/static/icons/default-96.png b/static/icons/default-96.png new file mode 100644 index 0000000..903efd5 Binary files /dev/null and b/static/icons/default-96.png differ diff --git a/static/icons/disabled-128.png b/static/icons/disabled-128.png new file mode 100644 index 0000000..8df184a Binary files /dev/null and b/static/icons/disabled-128.png differ diff --git a/static/icons/disabled-16.png b/static/icons/disabled-16.png new file mode 100644 index 0000000..a3b737f Binary files /dev/null and b/static/icons/disabled-16.png differ diff --git a/static/icons/disabled-24.png b/static/icons/disabled-24.png new file mode 100644 index 0000000..d75f5a2 Binary files /dev/null and b/static/icons/disabled-24.png differ diff --git a/static/icons/disabled-48.png b/static/icons/disabled-48.png new file mode 100644 index 0000000..602426f Binary files /dev/null and b/static/icons/disabled-48.png differ diff --git a/static/icons/disabled-96.png b/static/icons/disabled-96.png new file mode 100644 index 0000000..906eccb Binary files /dev/null and b/static/icons/disabled-96.png differ diff --git a/dest/devtools/svelte-logo-dark.svg b/static/icons/svelte-dark.svg similarity index 100% rename from dest/devtools/svelte-logo-dark.svg rename to static/icons/svelte-dark.svg diff --git a/dest/devtools/svelte-logo-light.svg b/static/icons/svelte-default.svg similarity index 100% rename from dest/devtools/svelte-logo-light.svg rename to static/icons/svelte-default.svg diff --git a/static/icons/svelte-disabled.svg b/static/icons/svelte-disabled.svg new file mode 100644 index 0000000..43dc3fb --- /dev/null +++ b/static/icons/svelte-disabled.svg @@ -0,0 +1,14 @@ + + + + + diff --git a/src/svelte-logo.svg b/static/icons/svelte.svg similarity index 100% rename from src/svelte-logo.svg rename to static/icons/svelte.svg diff --git a/static/manifest.json b/static/manifest.json new file mode 100644 index 0000000..27c497b --- /dev/null +++ b/static/manifest.json @@ -0,0 +1,33 @@ +{ + "manifest_version": 3, + "name": "Svelte DevTools", + "version": "2.0.0", + "description": "Browser DevTools extension for debugging Svelte applications.", + "icons": { + "16": "icons/default-16.png", + "24": "icons/default-24.png", + "48": "icons/default-48.png", + "96": "icons/default-96.png", + "128": "icons/default-128.png" + }, + + "action": { + "default_icon": { + "16": "icons/disabled-16.png", + "24": "icons/disabled-24.png", + "48": "icons/disabled-48.png", + "96": "icons/disabled-96.png", + "128": "icons/disabled-128.png" + } + }, + "background": { + "service_worker": "background.js" + }, + "devtools_page": "register.html", + "host_permissions": ["*://*/*"], + "permissions": ["activeTab", "scripting"], + "web_accessible_resources": [ + { "matches": ["*://*/*"], "resources": ["courier.js"] }, + { "matches": ["*://*/*"], "resources": ["sensor.js"], "world": "MAIN" } + ] +} diff --git a/static/register.html b/static/register.html new file mode 100644 index 0000000..6689014 --- /dev/null +++ b/static/register.html @@ -0,0 +1,5 @@ + + + + + diff --git a/static/register.js b/static/register.js new file mode 100644 index 0000000..73cfe9f --- /dev/null +++ b/static/register.js @@ -0,0 +1,12 @@ +chrome.devtools.panels.create( + 'Svelte', + `icons/svelte-${chrome.devtools.panels.themeName}.svg`, + 'index.html', + // (panel) => { + // panel.onShown.addListener((win) => + // chrome.devtools.inspectedWindow.eval('$0', (payload) => + // win.postMessage({ source: 'svelte-devtools', type: 'ext/inspect', payload }), + // ), + // ); + // }, +); diff --git a/static/sensor.js b/static/sensor.js new file mode 100644 index 0000000..5255d97 --- /dev/null +++ b/static/sensor.js @@ -0,0 +1,10 @@ +(() => { + // @ts-ignore - injected if the website is using svelte + const [major] = [...(window.__svelte?.v ?? [])]; + + document.dispatchEvent( + new CustomEvent('SvelteDevTools', { + detail: { type: 'ext/icon:set', payload: major }, + }), + ); +})(); diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..d5a9d0f --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,10 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +export default { + preprocess: vitePreprocess(), + + onwarn(warning, handler) { + if (warning.message.includes('A11y')) return; + !warning.message.includes('chrome') && handler(warning); + }, +}; diff --git a/test/public/index.html b/test/public/index.html deleted file mode 100644 index df93b4e..0000000 --- a/test/public/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Svelte Devtools test - - - - - - diff --git a/test/src/App.svelte b/test/src/App.svelte deleted file mode 100644 index 0f8e72e..0000000 --- a/test/src/App.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - - - -
    - - - - - - -
    diff --git a/test/src/BasicTree/BasicTree.svelte b/test/src/BasicTree/BasicTree.svelte deleted file mode 100644 index 778502c..0000000 --- a/test/src/BasicTree/BasicTree.svelte +++ /dev/null @@ -1,21 +0,0 @@ - - -
    -
      -
    1. Basic tree rendering
    2. -
    3. Element attributes, component properties and state
    4. -
    5. Text nodes / anchors
    6. -
    - - -
    diff --git a/test/src/BasicTree/Component.svelte b/test/src/BasicTree/Component.svelte deleted file mode 100644 index 969d4cc..0000000 --- a/test/src/BasicTree/Component.svelte +++ /dev/null @@ -1,15 +0,0 @@ - - -
    - A component with string, number, array, and object attributes. The value is - {value} -
    diff --git a/test/src/Bind/Bind.svelte b/test/src/Bind/Bind.svelte deleted file mode 100644 index af67498..0000000 --- a/test/src/Bind/Bind.svelte +++ /dev/null @@ -1,14 +0,0 @@ - - -
    -

    - Prepend 'bind' for bound Component binding. Note: element binds are simple - implicit event handlers -

    - - console.log(e)} /> -
    diff --git a/test/src/Bind/BindComponent.svelte b/test/src/Bind/BindComponent.svelte deleted file mode 100644 index 5e44a4b..0000000 --- a/test/src/Bind/BindComponent.svelte +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/test/src/Blocks.svelte b/test/src/Blocks.svelte deleted file mode 100644 index 108696b..0000000 --- a/test/src/Blocks.svelte +++ /dev/null @@ -1,71 +0,0 @@ - - -
    -

    - Renders {#each} and {#if} blocks with original - source line -

    - - {#each valueList as value}{value}{/each} - -
    - {#if value > 10} - Value is over 10 - {:else if value > 5}Value is over 5{:else}Value is under 5{/if} -
    - -
    - {#await promise} - waiting for the promise to resolve... - {:then value} - Promise resolved to - {value} - {:catch error} - Something went wrong - {error.message} - {/await} -
    -
    - {#await new Promise(() => {})} - Pending forever - {:then value} - Something went wrong - {value} - {:catch error} - Something went wrong - {error.message} - {/await} -
    - -
    - {#await Promise.resolve(5)} - Something went wrong - {:then value} - Promise resolved to - {value} - {:catch error} - Something went wrong - {error.message} - {/await} -
    -
    - {#await Promise.reject('rejected')} - Something went wrong - {:then value} - Something went wrong - {value} - {:catch error} - Should reject - {error} - {/await} -
    -
    diff --git a/test/src/Detach/Detach.svelte b/test/src/Detach/Detach.svelte deleted file mode 100644 index 4068a76..0000000 --- a/test/src/Detach/Detach.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - -
    -

    Component / element nodes are

    -
      -
    1. positioned correctly when mounted after first render
    2. -
    3. removed when detached
    4. -
    - - {#if isShown} - -
    Element renders below component and above button
    - {/if} - - -
    diff --git a/test/src/Detach/DetachComponent.svelte b/test/src/Detach/DetachComponent.svelte deleted file mode 100644 index 337dc72..0000000 --- a/test/src/Detach/DetachComponent.svelte +++ /dev/null @@ -1 +0,0 @@ -Element renders above both element and button diff --git a/test/src/Events/Events.svelte b/test/src/Events/Events.svelte deleted file mode 100644 index ee035d6..0000000 --- a/test/src/Events/Events.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - -
    console.log('Captured a key', e)}> -

    Render event listeners on elements and components.

    - - console.log(e.detail)} /> -
    diff --git a/test/src/Events/EventsComponent.svelte b/test/src/Events/EventsComponent.svelte deleted file mode 100644 index db96e18..0000000 --- a/test/src/Events/EventsComponent.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/test/src/Slots/SlotComponent.svelte b/test/src/Slots/SlotComponent.svelte deleted file mode 100644 index 4fa864c..0000000 --- a/test/src/Slots/SlotComponent.svelte +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/src/Slots/Slots.svelte b/test/src/Slots/Slots.svelte deleted file mode 100644 index 43f6d61..0000000 --- a/test/src/Slots/Slots.svelte +++ /dev/null @@ -1,8 +0,0 @@ - - -
    -

    Render slots.

    - Slot content -
    diff --git a/test/src/index.js b/test/src/index.js deleted file mode 100644 index c0c67e9..0000000 --- a/test/src/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import App from './App.svelte' - -new App({ target: document.body }) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..10ae081 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + + "checkJs": true, + "strict": true, + "composite": true, + "noEmit": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + + "skipLibCheck": true, + "isolatedModules": true, + "useDefineForClassFields": true, + "forceConsistentCasingInFileNames": true, + + "paths": { + "$lib": ["./src/lib"], + "$lib/*": ["./src/lib/*"] + } + }, + "include": [ + "vite.config.ts", + "src/**/*.d.ts", + "src/**/*.ts", + "src/**/*.js", + "src/**/*.svelte", + "static/**/*.js" + ], + "exclude": ["static/courier.js"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..553a45c --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,21 @@ +import { resolve } from 'node:path'; +import { svelte } from '@sveltejs/vite-plugin-svelte'; +import { defineConfig } from 'vite'; + +export default defineConfig(() => { + return { + plugins: [svelte()], + + build: { + outDir: 'build', + }, + + publicDir: 'static', + + resolve: { + alias: { + $lib: resolve(__dirname, 'src/lib'), + }, + }, + }; +}); diff --git a/zen b/zen deleted file mode 100644 index 3c6d0dc..0000000 --- a/zen +++ /dev/null @@ -1,404 +0,0 @@ -position -top -right -bottom -left -z-index -display -visibility -float -clear -overflow --ms-overflow-x --ms-overflow-y -overflow-x -overflow-y --webkit-overflow-scrolling -clip --webkit-align-content --ms-flex-line-pack -align-content --webkit-box-align --moz-box-align --webkit-align-items -align-items --ms-flex-align --webkit-align-self --ms-flex-item-align --ms-grid-row-align -align-self --webkit-box-flex --webkit-flex --moz-box-flex --ms-flex -flex --webkit-flex-flow --ms-flex-flow -flex-flow --webkit-flex-basis --ms-flex-preferred-size -flex-basis --webkit-box-orient --webkit-box-direction --webkit-flex-direction --moz-box-orient --moz-box-direction --ms-flex-direction -flex-direction --webkit-flex-grow --ms-flex-positive -flex-grow --webkit-flex-shrink --ms-flex-negative -flex-shrink --webkit-flex-wrap --ms-flex-wrap -flex-wrap --webkit-box-pack --moz-box-pack --ms-flex-pack --webkit-justify-content -justify-content --webkit-box-ordinal-group --webkit-order --moz-box-ordinal-group --ms-flex-order -order --webkit-box-sizing --moz-box-sizing -box-sizing -margin -margin-top -margin-right -margin-bottom -margin-left -padding -padding-top -padding-right -padding-bottom -padding-left -min-width -min-height -max-width -max-height -width -height -outline -outline-width -outline-style -outline-color -outline-offset -border -border-spacing -border-collapse -border-width -border-style -border-color -border-top -border-top-width -border-top-style -border-top-color -border-right -border-right-width -border-right-style -border-right-color -border-bottom -border-bottom-width -border-bottom-style -border-bottom-color -border-left -border-left-width -border-left-style -border-left-color --webkit-border-radius --moz-border-radius -border-radius --webkit-border-top-left-radius --moz-border-radius-topleft -border-top-left-radius --webkit-border-top-right-radius --moz-border-radius-topright -border-top-right-radius --webkit-border-bottom-right-radius --moz-border-radius-bottomright -border-bottom-right-radius --webkit-border-bottom-left-radius --moz-border-radius-bottomleft -border-bottom-left-radius --webkit-border-image --moz-border-image --o-border-image -border-image --webkit-border-image-source --moz-border-image-source --o-border-image-source -border-image-source --webkit-border-image-slice --moz-border-image-slice --o-border-image-slice -border-image-slice --webkit-border-image-width --moz-border-image-width --o-border-image-width -border-image-width --webkit-border-image-outset --moz-border-image-outset --o-border-image-outset -border-image-outset --webkit-border-image-repeat --moz-border-image-repeat --o-border-image-repeat -border-image-repeat --webkit-border-top-image --moz-border-top-image --o-border-top-image -border-top-image --webkit-border-right-image --moz-border-right-image --o-border-right-image -border-right-image --webkit-border-bottom-image --moz-border-bottom-image --o-border-bottom-image -border-bottom-image --webkit-border-left-image --moz-border-left-image --o-border-left-image -border-left-image --webkit-border-corner-image --moz-border-corner-image --o-border-corner-image -border-corner-image --webkit-border-top-left-image --moz-border-top-left-image --o-border-top-left-image -border-top-left-image --webkit-border-top-right-image --moz-border-top-right-image --o-border-top-right-image -border-top-right-image --webkit-border-bottom-right-image --moz-border-bottom-right-image --o-border-bottom-right-image -border-bottom-right-image --webkit-border-bottom-left-image --moz-border-bottom-left-image --o-border-bottom-left-image -border-bottom-left-image -background -filter:progid:DXImageTransform.Microsoft.AlphaImageLoader -background-color -background-image -background-attachment -background-position --ms-background-position-x --ms-background-position-y -background-position-x -background-position-y --webkit-background-clip --moz-background-clip -background-clip -background-origin --webkit-background-size --moz-background-size --o-background-size -background-size -background-repeat -box-decoration-break --webkit-box-shadow --moz-box-shadow -box-shadow -color -table-layout -caption-side -empty-cells -list-style -list-style-position -list-style-type -list-style-image -quotes -content -counter-increment -counter-reset --ms-writing-mode -vertical-align -text-align --webkit-text-align-last --moz-text-align-last --ms-text-align-last -text-align-last -text-decoration -text-emphasis -text-emphasis-position -text-emphasis-style -text-emphasis-color -text-indent --ms-text-justify -text-justify -text-outline -text-transform -text-wrap --ms-text-overflow -text-overflow -text-overflow-ellipsis -text-overflow-mode -text-shadow -white-space -word-spacing --ms-word-wrap -word-wrap --ms-word-break -word-break --moz-tab-size --o-tab-size -tab-size --webkit-hyphens --moz-hyphens -hyphens -letter-spacing -font -font-weight -font-style -font-variant -font-size-adjust -font-stretch -font-size -font-family -src -line-height -opacity --ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha -filter:progid:DXImageTransform.Microsoft.Alpha(Opacity --ms-interpolation-mode --webkit-filter --ms-filter -filter -resize -cursor -nav-index -nav-up -nav-right -nav-down -nav-left --webkit-transition --moz-transition --ms-transition --o-transition -transition --webkit-transition-delay --moz-transition-delay --ms-transition-delay --o-transition-delay -transition-delay --webkit-transition-timing-function --moz-transition-timing-function --ms-transition-timing-function --o-transition-timing-function -transition-timing-function --webkit-transition-duration --moz-transition-duration --ms-transition-duration --o-transition-duration -transition-duration --webkit-transition-property --moz-transition-property --ms-transition-property --o-transition-property -transition-property --webkit-transform --moz-transform --ms-transform --o-transform -transform --webkit-transform-origin --moz-transform-origin --ms-transform-origin --o-transform-origin -transform-origin --webkit-animation --moz-animation --ms-animation --o-animation -animation --webkit-animation-name --moz-animation-name --ms-animation-name --o-animation-name -animation-name --webkit-animation-duration --moz-animation-duration --ms-animation-duration --o-animation-duration -animation-duration --webkit-animation-play-state --moz-animation-play-state --ms-animation-play-state --o-animation-play-state -animation-play-state --webkit-animation-timing-function --moz-animation-timing-function --ms-animation-timing-function --o-animation-timing-function -animation-timing-function --webkit-animation-delay --moz-animation-delay --ms-animation-delay --o-animation-delay -animation-delay --webkit-animation-iteration-count --moz-animation-iteration-count --ms-animation-iteration-count --o-animation-iteration-count -animation-iteration-count --webkit-animation-direction --moz-animation-direction --ms-animation-direction --o-animation-direction -animation-direction -pointer-events -unicode-bidi -direction --webkit-columns --moz-columns -columns --webkit-column-span --moz-column-span -column-span --webkit-column-width --moz-column-width -column-width --webkit-column-count --moz-column-count -column-count --webkit-column-fill --moz-column-fill -column-fill --webkit-column-gap --moz-column-gap -column-gap --webkit-column-rule --moz-column-rule -column-rule --webkit-column-rule-width --moz-column-rule-width -column-rule-width --webkit-column-rule-style --moz-column-rule-style -column-rule-style --webkit-column-rule-color --moz-column-rule-color -column-rule-color -break-before -break-inside -break-after -page-break-before -page-break-inside -page-break-after -orphans -widows --ms-zoom -zoom -max-zoom -min-zoom -user-zoom -orientation