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..0d411e1 --- /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@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + 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/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..826ac1f --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,90 @@ +name: REL + +on: + push: + branches: [master] + pull_request: + +jobs: + manifest: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.extract.outputs.version }} + + steps: + - uses: actions/checkout@v4 + - id: extract + working-directory: ./workspace/extension/static + run: echo "version=$(jq -r '.version' manifest.json)" >> $GITHUB_OUTPUT + + bundle: + runs-on: ubuntu-latest + needs: manifest + + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - run: pnpm install + - working-directory: ./workspace/extension + run: pnpm build && cd build && zip -r svelte-devtools * + + - uses: actions/upload-artifact@v4 + with: + name: extension-${{ github.sha }} + path: workspace/extension/build/svelte-devtools.zip + + - if: github.event_name == 'pull_request' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.number }} + WORKFLOW: ${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts + run: | + url=https://github.com/$WORKFLOW/$(gh api repos/$WORKFLOW --jq '.artifacts[0].id') + commented=$(gh pr view $PR_NUMBER --json comments --jq '.comments[].author.login | select(. | contains("github-actions"))') + body="Try the changes in this PR by [side-loading the built extension]($url). :rocket:" + + if [ -z "$commented" ]; then + gh pr comment $PR_NUMBER --body "$body" + else + gh pr comment $PR_NUMBER --edit-last --body "$body" + fi + + publish: + runs-on: ubuntu-latest + needs: [manifest, bundle] + + if: | + github.repository == 'sveltejs/svelte-devtools' && + github.event_name == 'push' && github.ref == 'refs/heads/master' && + startsWith(github.event.head_commit.message, format('~ v{0}', needs.manifest.outputs.version)) + + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/download-artifact@v4 + + - working-directory: extension-${{ github.sha }} + run: | + pnpm dlx web-ext sign --channel unlisted \ + --api-key ${{ secrets.WEB_EXT_API_KEY }} \ + --api-secret ${{ secrets.WEB_EXT_API_SECRET }} + + - working-directory: extension-${{ github.sha }} + env: + GH_TOKEN: ${{ github.token }} + run: | # https://cli.github.com/manual/gh_release_create + gh release create v${{ needs.manifest.outputs.version }} \ + svelte-devtools.zip web-ext-artifacts/*.xpi#svelte-devtools.xpi \ + --title ${{ needs.manifest.outputs.version }} \ + --draft --generate-notes --notes ' + Built from ${{ github.event.head_commit.id }} at https://github.com/sveltejs/svelte-devtools/actions/runs/${{ github.run_id }} + - Chrome Web Store: https://chrome.google.com/webstore/detail/svelte-devtools/kfidecgcdjjfpeckbblhmfkhmlgecoff + - Firefox Signed Add-on: https://github.com/sveltejs/svelte-devtools/releases/download/v${{ needs.manifest.outputs.version }}/svelte-devtools.xpi + ---' + + # TODO: publish to Chrome Web Store diff --git a/.gitignore b/.gitignore index d695b30..aaa9ce1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,5 @@ -.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 + +.DS_Store +.cache +.env diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..e12a68b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,34 @@ +{ + "printWidth": 100, + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "useTabs": true, + "overrides": [ + { + "files": ["pnpm-lock.yaml"], + "options": { + "rangeEnd": 0 + } + }, + + { + "files": ["package.json"], + "options": { + "plugins": ["prettier-plugin-sort-package-json"] + } + }, + { + "files": ["*.md"], + "options": { + "tabWidth": 4 + } + }, + { + "files": ["*.svelte"], + "options": { + "plugins": ["prettier-plugin-svelte"] + } + } + ] +} diff --git a/README.md b/README.md index 931f534..f630494 100644 --- a/README.md +++ b/README.md @@ -1,100 +1,54 @@ # 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 + + -**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 browser 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** +> For Firefox users, you can install the [`.xpi` file of the latest version from the GitHub releases page](https://github.com/sveltejs/svelte-devtools/releases/latest). Note that if you grab the `.zip` file, you will need to load it as a temporary extension and enable "Always Allow on localhost" in the extension settings. -![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 the following to be true: -### Template -By default the [svelte template](https://github.com/sveltejs/template) will set `dev: true` when running `npm run dev` and `false` otherwise. +- Chrome or Firefox version 121 or higher +- Application running Svelte version `^4.0.0` +- Application compiled with `dev: true` ([SvelteKit](https://kit.svelte.dev/) does this automatically for you) -### 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'; - -export default { - input: 'src/main.js', - output: { - file: 'public/bundle.js', - format: 'iife' - }, - plugins: [ - svelte({ - compilerOptions: { - dev: true - } - }) - ] -} -``` +## Development -### 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, - }, - }, - }, - ... - ] - }, - ... -``` - -## Build from source +Clone this repository and setup the environment with `pnpm` -### Firefox +```sh +git clone https://github.com/sveltejs/svelte-devtools.git +cd svelte-devtools +pnpm install -Clone this repository and run the package script. +cd workspace/extension +pnpm dev ``` -git clone https://github.com/RedHatter/svelte-devtools.git -cd svelte-devtools -npm install -npm run package:firefox + +To work on the extension, run the `dev` script from `workspace/extension` directory + +```sh +cd workspace/extension +pnpm dev ``` -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 extension and create a directory called `build`. Steps may vary depending on the browser you are using, but generally: -### 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/.web-extension-id b/dest/.web-extension-id deleted file mode 100644 index 8530d52..0000000 --- a/dest/.web-extension-id +++ /dev/null @@ -1,3 +0,0 @@ -# This file was created by https://github.com/mozilla/web-ext -# Your auto-generated extension ID for addons.mozilla.org is: -{a0370179-acc3-452f-9530-246b6adb2768} \ No newline at end of file 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/package.json b/package.json index a039c72..8642808 100644 --- a/package.json +++ b/package.json @@ -1,37 +1,21 @@ { - "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": { + "clean": "git add * && git clean -dfx -e node_modules", + "check": "pnpm --filter \"./workspace/*\" check", + "format": "pnpm --filter \"./workspace/*\" format" + }, + "packageManager": "pnpm@8.15.6", + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "next", + "@types/node": "^20.16.6", + "prettier": "^3.3.3", + "prettier-plugin-sort-package-json": "^0.2.0", + "prettier-plugin-svelte": "^3.2.6", + "svelte": "5.0.0-next.141", + "svelte-check": "^4.0.2", + "typescript": "^5.6.2", + "vite": "^5.4.7" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..88cde3b --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,901 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@sveltejs/vite-plugin-svelte': + specifier: next + version: 4.0.0-next.7(svelte@5.0.0-next.141)(vite@5.4.7) + '@types/node': + specifier: ^20.16.6 + version: 20.16.6 + prettier: + specifier: ^3.3.3 + version: 3.3.3 + prettier-plugin-sort-package-json: + specifier: ^0.2.0 + version: 0.2.0(prettier@3.3.3) + prettier-plugin-svelte: + specifier: ^3.2.6 + version: 3.2.6(prettier@3.3.3)(svelte@5.0.0-next.141) + svelte: + specifier: 5.0.0-next.141 + version: 5.0.0-next.141 + svelte-check: + specifier: ^4.0.2 + version: 4.0.2(svelte@5.0.0-next.141)(typescript@5.6.2) + typescript: + specifier: ^5.6.2 + version: 5.6.2 + vite: + specifier: ^5.4.7 + version: 5.4.7(@types/node@20.16.6) + + workspace/extension: + devDependencies: + '@types/chrome': + specifier: ^0.0.266 + version: 0.0.266 + rollup: + specifier: ^4.22.4 + version: 4.22.4 + +packages: + + /@ampproject/remapping@2.3.0: + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + dev: true + + /@esbuild/aix-ppc64@0.21.5: + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.21.5: + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.21.5: + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.21.5: + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.21.5: + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.21.5: + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.21.5: + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.21.5: + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.21.5: + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.21.5: + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.21.5: + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.21.5: + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.21.5: + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.21.5: + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.21.5: + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.21.5: + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.21.5: + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.21.5: + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.21.5: + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.21.5: + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.21.5: + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.21.5: + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.21.5: + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@jridgewell/gen-mapping@0.3.5: + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + dev: true + + /@jridgewell/resolve-uri@3.1.2: + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/set-array@1.2.1: + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec@1.5.0: + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + dev: true + + /@jridgewell/trace-mapping@0.3.25: + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + dev: true + + /@rollup/rollup-android-arm-eabi@4.22.4: + resolution: {integrity: sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-android-arm64@4.22.4: + resolution: {integrity: sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-arm64@4.22.4: + resolution: {integrity: sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-x64@4.22.4: + resolution: {integrity: sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.22.4: + resolution: {integrity: sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-musleabihf@4.22.4: + resolution: {integrity: sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.22.4: + resolution: {integrity: sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.22.4: + resolution: {integrity: sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-powerpc64le-gnu@4.22.4: + resolution: {integrity: sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.22.4: + resolution: {integrity: sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-s390x-gnu@4.22.4: + resolution: {integrity: sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.22.4: + resolution: {integrity: sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.22.4: + resolution: {integrity: sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.22.4: + resolution: {integrity: sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.22.4: + resolution: {integrity: sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.22.4: + resolution: {integrity: sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@sveltejs/vite-plugin-svelte-inspector@3.0.0-next.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7)(svelte@5.0.0-next.141)(vite@5.4.7): + resolution: {integrity: sha512-kuGJ2CZ5lAw3gKF8Kw0AfKtUJWbwdlDHY14K413B0MCyrzvQvsKTorwmwZcky0+QqY6RnVIZ/5FttB9bQmkLXg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^4.0.0-next.0||^4.0.0 + svelte: ^5.0.0-next.96 || ^5.0.0 + vite: ^5.0.0 + dependencies: + '@sveltejs/vite-plugin-svelte': 4.0.0-next.7(svelte@5.0.0-next.141)(vite@5.4.7) + debug: 4.3.7 + svelte: 5.0.0-next.141 + vite: 5.4.7(@types/node@20.16.6) + transitivePeerDependencies: + - supports-color + dev: true + + /@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.141)(vite@5.4.7): + resolution: {integrity: sha512-yMUnAqquoayvBDztk1rWUgdtvjv7YcHgopCAB7sWl9SQht8U/7lqwTlJU0ZTAY09pFFRe6bbakd7YoiyyIvJiA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + svelte: ^5.0.0-next.96 || ^5.0.0 + vite: ^5.0.0 + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 3.0.0-next.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7)(svelte@5.0.0-next.141)(vite@5.4.7) + debug: 4.3.7 + deepmerge: 4.3.1 + kleur: 4.1.5 + magic-string: 0.30.11 + svelte: 5.0.0-next.141 + vite: 5.4.7(@types/node@20.16.6) + vitefu: 1.0.2(vite@5.4.7) + transitivePeerDependencies: + - supports-color + dev: true + + /@types/chrome@0.0.266: + resolution: {integrity: sha512-QSQWJTL7NjZElvq/6/E5C1+pHgEP8UAJzwoz7M4vSJ7AECt6NNehJ+tU6snnvuTqZOBjFCivvitYo5+8tNPmhg==} + dependencies: + '@types/filesystem': 0.0.36 + '@types/har-format': 1.2.15 + dev: true + + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + dev: true + + /@types/estree@1.0.6: + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + dev: true + + /@types/filesystem@0.0.36: + resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==} + dependencies: + '@types/filewriter': 0.0.33 + dev: true + + /@types/filewriter@0.0.33: + resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==} + dev: true + + /@types/har-format@1.2.15: + resolution: {integrity: sha512-RpQH4rXLuvTXKR0zqHq3go0RVXYv/YVqv4TnPH95VbwUxZdQlK1EtcMvQvMpDngHbt13Csh9Z4qT9AbkiQH5BA==} + dev: true + + /@types/node@20.16.6: + resolution: {integrity: sha512-T7PpxM/6yeDE+AdlVysT62BX6/bECZOmQAgiFg5NoBd5MQheZ3tzal7f1wvzfiEcmrcJNRi2zRr2nY2zF+0uqw==} + dependencies: + undici-types: 6.19.8 + dev: true + + /acorn-typescript@1.4.13(acorn@8.12.1): + resolution: {integrity: sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==} + peerDependencies: + acorn: '>=8.9.0' + dependencies: + acorn: 8.12.1 + dev: true + + /acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + 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.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + dev: true + + /axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + dev: true + + /binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + dev: true + + /braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.1.1 + dev: true + + /chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + 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 + + /debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + dev: true + + /deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + dev: true + + /esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + dev: true + + /esm-env@1.0.0: + resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==} + dev: true + + /esrap@1.2.2: + resolution: {integrity: sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==} + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + '@types/estree': 1.0.6 + dev: true + + /fdir@6.3.0: + resolution: {integrity: sha512-QOnuT+BOtivR77wYvCWHfGt9s4Pz1VIMbD463vegT5MLqNXy8rYFT/lPVEqf/bhYeT6qmqrNHhsX+rWwe3rOCQ==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + dev: true + + /fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + 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 + + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.3.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.6 + 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.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + dev: true + + /mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + dev: true + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: true + + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + 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 + + /picocolors@1.1.0: + resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + dev: true + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /postcss@8.4.47: + resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.1.0 + source-map-js: 1.2.1 + dev: true + + /prettier-plugin-sort-package-json@0.2.0(prettier@3.3.3): + resolution: {integrity: sha512-jg+CfEHpmXyMJxoBSQh+IObmCxqt7KyHOuXGQm9D4heeyipEhlTJZfiS9SlfhBKrtf/yA8WZwHmynUG9xfF/rA==} + peerDependencies: + prettier: ^3.0.0 + dependencies: + prettier: 3.3.3 + dev: true + + /prettier-plugin-svelte@3.2.6(prettier@3.3.3)(svelte@5.0.0-next.141): + resolution: {integrity: sha512-Y1XWLw7vXUQQZmgv1JAEiLcErqUniAF2wO7QJsw8BVMvpLET2dI5WpEIEJx1r11iHVdSMzQxivyfrH9On9t2IQ==} + peerDependencies: + prettier: ^3.0.0 + svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 + dependencies: + prettier: 3.3.3 + svelte: 5.0.0-next.141 + dev: true + + /prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + engines: {node: '>=14'} + hasBin: true + dev: true + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /rollup@4.22.4: + resolution: {integrity: sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.22.4 + '@rollup/rollup-android-arm64': 4.22.4 + '@rollup/rollup-darwin-arm64': 4.22.4 + '@rollup/rollup-darwin-x64': 4.22.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.22.4 + '@rollup/rollup-linux-arm-musleabihf': 4.22.4 + '@rollup/rollup-linux-arm64-gnu': 4.22.4 + '@rollup/rollup-linux-arm64-musl': 4.22.4 + '@rollup/rollup-linux-powerpc64le-gnu': 4.22.4 + '@rollup/rollup-linux-riscv64-gnu': 4.22.4 + '@rollup/rollup-linux-s390x-gnu': 4.22.4 + '@rollup/rollup-linux-x64-gnu': 4.22.4 + '@rollup/rollup-linux-x64-musl': 4.22.4 + '@rollup/rollup-win32-arm64-msvc': 4.22.4 + '@rollup/rollup-win32-ia32-msvc': 4.22.4 + '@rollup/rollup-win32-x64-msvc': 4.22.4 + fsevents: 2.3.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 + + /source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + dev: true + + /svelte-check@4.0.2(svelte@5.0.0-next.141)(typescript@5.6.2): + resolution: {integrity: sha512-w2yqcG9ELJe2RJCnAvB7v0OgkHhL3czzz/tVoxGFfO6y4mOrF6QHCDhXijeXzsU7LVKEwWS3Qd9tza4JBuDxqA==} + engines: {node: '>= 18.0.0'} + hasBin: true + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: '>=5.0.0' + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + chokidar: 3.6.0 + fdir: 6.3.0 + picocolors: 1.1.0 + sade: 1.8.1 + svelte: 5.0.0-next.141 + typescript: 5.6.2 + transitivePeerDependencies: + - picomatch + dev: true + + /svelte@5.0.0-next.141: + resolution: {integrity: sha512-zT74TUo0vOOrbxRfdlWXu+ac4O9lqPFG0YoZB3uOfrOewT1GKxKm0qwG/jo9bGvgZ++TSHjR7AtV091LY2FhBA==} + engines: {node: '>=18'} + dependencies: + '@ampproject/remapping': 2.3.0 + '@jridgewell/sourcemap-codec': 1.5.0 + '@types/estree': 1.0.6 + acorn: 8.12.1 + acorn-typescript: 1.4.13(acorn@8.12.1) + aria-query: 5.3.2 + axobject-query: 4.1.0 + esm-env: 1.0.0 + esrap: 1.2.2 + is-reference: 3.0.2 + locate-character: 3.0.0 + magic-string: 0.30.11 + zimmerframe: 1.1.2 + 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.6.2: + resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + dev: true + + /vite@5.4.7(@types/node@20.16.6): + resolution: {integrity: sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.16.6 + esbuild: 0.21.5 + postcss: 8.4.47 + rollup: 4.22.4 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /vitefu@1.0.2(vite@5.4.7): + resolution: {integrity: sha512-0/iAvbXyM3RiPPJ4lyD4w6Mjgtf4ejTK6TPvTNG3H32PLwuT0N/ZjJLiXug7ETE/LWtTeHw9WRv7uX/tIKYyKg==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + vite: + optional: true + dependencies: + vite: 5.4.7(@types/node@20.16.6) + dev: true + + /zimmerframe@1.1.2: + resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} + dev: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..2c52ad5 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'workspace/*' diff --git a/rollup.config.js b/rollup.config.js deleted file mode 100644 index 8afd883..0000000 --- a/rollup.config.js +++ /dev/null @@ -1,91 +0,0 @@ -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 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' }) - ] -}] 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/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 deleted file mode 100644 index 3c2abab..0000000 --- a/src/client/highlight.js +++ /dev/null @@ -1,149 +0,0 @@ -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 -} - -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 }) -} diff --git a/src/client/index.js b/src/client/index.js deleted file mode 100644 index 760044e..0000000 --- a/src/client/index.js +++ /dev/null @@ -1,203 +0,0 @@ -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 - } -} - -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))) - ) -} - -let _shouldUseCapture = null -function shouldUseCapture() { - return _shouldUseCapture == null - ? (_shouldUseCapture = gte(3, 19, 2)) - : _shouldUseCapture -} - -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 -} - -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 - }) - } -}) 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/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/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/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/workspace/extension/.gitignore b/workspace/extension/.gitignore new file mode 100644 index 0000000..ef49293 --- /dev/null +++ b/workspace/extension/.gitignore @@ -0,0 +1,4 @@ +/build + +# generated files +static/courier.js diff --git a/workspace/extension/index.html b/workspace/extension/index.html new file mode 100644 index 0000000..396be6e --- /dev/null +++ b/workspace/extension/index.html @@ -0,0 +1,12 @@ + + + + + + + + +
    + + + diff --git a/workspace/extension/package.json b/workspace/extension/package.json new file mode 100644 index 0000000..971a2fa --- /dev/null +++ b/workspace/extension/package.json @@ -0,0 +1,20 @@ +{ + "private": true, + "type": "module", + "scripts": { + "dev": "pnpm run --parallel \"/^dev:.*/\"", + "dev:app": "vite build -wd --minify=false --sourcemap=inline", + "dev:scripts": "rollup -cw", + "build": "rollup -c && vite build", + "bundle:zip": "cd build && zip -r svelte-devtools.zip *", + "bundle:tar": "cd build && tar -czf svelte-devtools.tar.gz *", + "format": "prettier -w .", + "check": "pnpm run --parallel \"/^check:.*/\"", + "check:style": "prettier -c .", + "check:svelte": "svelte-check --tsconfig ./tsconfig.json" + }, + "devDependencies": { + "@types/chrome": "^0.0.266", + "rollup": "^4.22.4" + } +} diff --git a/workspace/extension/rollup.config.js b/workspace/extension/rollup.config.js new file mode 100644 index 0000000..3368c80 --- /dev/null +++ b/workspace/extension/rollup.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'rollup'; + +export default defineConfig([ + { + input: 'static/background.js', + output: { + file: 'build/background.js', + }, + }, + { + input: 'src/client/core.js', + output: [ + { file: 'static/courier.js', format: 'iife' }, + { file: 'build/courier.js', format: 'iife' }, + ], + }, +]); diff --git a/workspace/extension/src/App.svelte b/workspace/extension/src/App.svelte new file mode 100644 index 0000000..eb9d75f --- /dev/null +++ b/workspace/extension/src/App.svelte @@ -0,0 +1,194 @@ + + + { + const { target, key } = event; + if (target !== document.body || !app.selected) return; + + if (key === 'Enter') { + app.selected.expanded = !app.selected.expanded; + } else if (key === 'ArrowRight') { + event.preventDefault(); + if (!app.selected) app.selected = app.root[0]; + app.selected.expanded = true; + } else if (key === 'ArrowLeft') { + event.preventDefault(); + if (app.selected.expanded) { + app.selected.expanded = false; + return; + } + do app.selected = app.selected.parent ?? app.selected; + while (!visibility[app.selected.type]); + } else if (key === 'ArrowUp') { + event.preventDefault(); + let nodes = (app.selected.parent?.children || app.root).filter((n) => visibility[n.type]); + let sibling = nodes[nodes.findIndex((o) => o.id === app.selected?.id) - 1]; + while (sibling?.expanded) { + nodes = sibling.children.filter((n) => visibility[n.type]); + sibling = nodes[nodes.length - 1]; + } + app.selected = sibling ?? app.selected.parent ?? app.selected; + } else if (key === 'ArrowDown') { + event.preventDefault(); + const children = app.selected.children.filter((n) => visibility[n.type]); + + if (!app.selected.expanded || children.length === 0) { + let next = app.selected; + let current = app.selected; + do { + const nodes = current.parent ? current.parent.children : app.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); + + app.selected = next ?? app.selected; + } else { + app.selected = children[0]; + } + } + }} +/> + +{#if app.root.length} +
    + + + + + + + + + + + + + + + + +
      event.currentTarget === event.target && reset()} + onmouseleave={reset} + > + {#each app.root as node (node.id)} + + {/each} +
    + + +
    + + + + {@const events = app.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 app.selected?.type === 'component'} +

    Props

    + + + + +

    Events

    + + + + +

    State

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

    State

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

    Attributes

    + + + + +

    Events

    + + {/if} +
    +{:else} + +{/if} + + diff --git a/workspace/extension/src/app.css b/workspace/extension/src/app.css new file mode 100644 index 0000000..6472580 --- /dev/null +++ b/workspace/extension/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/workspace/extension/src/app.d.ts b/workspace/extension/src/app.d.ts new file mode 100644 index 0000000..11f5926 --- /dev/null +++ b/workspace/extension/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: string; // crypto.randomUUID(); + 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: CustomEvent<{ + type: string; + payload: string; + }>; + } +} + +export {}; diff --git a/workspace/extension/src/client/core.js b/workspace/extension/src/client/core.js new file mode 100644 index 0000000..6b874c1 --- /dev/null +++ b/workspace/extension/src/client/core.js @@ -0,0 +1,108 @@ +import { highlight } from './highlight.js'; +import { send } from './runtime.js'; +import { index as v4 } from './svelte-4.js'; +import { serialize } from './utils.js'; + +// @ts-ignore - https://developer.chrome.com/docs/extensions/how-to/devtools/extend-devtools#selected-element +window['#SvelteDevTools'] = { + /** + * @param {string} id + * @param {string[]} keys + * @param {any} value + */ + inject(id, keys, value) { + const { detail: component } = v4.map.get(id) || {}; + if (component) { + const [prop, ...rest] = keys; + const original = component.$capture_state()[prop]; + if (typeof original === 'object') { + let ref = original; + for (let i = 0; i < rest.length - 1; i += 1) { + ref = ref[rest[i]]; + } + ref[rest[rest.length - 1]] = value; + component.$inject_state({ [prop]: original }); + } else { + component.$inject_state({ [prop]: value }); + } + } + }, +}; + +const previous = { + /** @type {HTMLElement | null} */ + target: null, + style: { + cursor: '', + background: '', + outline: '', + }, + + clear() { + if (this.target) { + for (const key in this.style) { + // @ts-expect-error - trust me TS + this.target.style[key] = this.style[key]; + } + } + this.target = null; + }, +}; + +const inspect = { + /** @param {MouseEvent} event */ + handle({ target }) { + const same = previous.target && previous.target === target; + const html = target instanceof HTMLElement; + if (same || !html) return; + + if (previous.target) previous.clear(); + previous.target = target; + previous.style = { + cursor: target.style.cursor, + background: target.style.background, + outline: target.style.outline, + }; + target.style.cursor = 'pointer'; + target.style.background = 'rgba(0, 136, 204, 0.2)'; + target.style.outline = '1px dashed rgb(0, 136, 204)'; + }, + /** @param {MouseEvent} event */ + click(event) { + event.preventDefault(); + document.removeEventListener('mousemove', inspect.handle, true); + const node = v4.map.get(/** @type {Node} */ (event.target)); + if (node) send('bridge::ext/inspect', { node: serialize(node) }); + previous.clear(); + }, +}; + +window.addEventListener('message', ({ data, source }) => { + // only accept messages from our application or script + if (source !== window || data?.source !== 'svelte-devtools') return; + + if (data.type === 'bridge::ext/select') { + const node = v4.map.get(data.payload); + // @ts-expect-error - saved for `devtools.inspect()` + if (node) window.$n = node.detail; + } else if (data.type === 'bridge::ext/highlight') { + const node = v4.map.get(data.payload); + return highlight(node); + } else if (data.type === 'bridge::ext/inspect') { + switch (data.payload) { + case 'start': { + document.addEventListener('mousemove', inspect.handle, true); + document.addEventListener('click', inspect.click, { + capture: true, + once: true, + }); + break; + } + default: { + document.removeEventListener('mousemove', inspect.handle, true); + document.removeEventListener('click', inspect.click, true); + previous.clear(); + } + } + } +}); diff --git a/workspace/extension/src/client/highlight.js b/workspace/extension/src/client/highlight.js new file mode 100644 index 0000000..fde12ef --- /dev/null +++ b/workspace/extension/src/client/highlight.js @@ -0,0 +1,51 @@ +const dom = { + area: document.createElement('div'), + x: document.createElement('div'), + y: document.createElement('div'), +}; + +/** @param {Pick} [node] */ +export function highlight(node) { + 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/workspace/extension/src/client/runtime.js b/workspace/extension/src/client/runtime.js new file mode 100644 index 0000000..fd5a24e --- /dev/null +++ b/workspace/extension/src/client/runtime.js @@ -0,0 +1,7 @@ +/** + * @param {string} type + * @param {Record} [payload] + */ +export function send(type, payload) { + window.postMessage({ source: 'svelte-devtools', type, payload }); +} diff --git a/workspace/extension/src/client/svelte-4.js b/workspace/extension/src/client/svelte-4.js new file mode 100644 index 0000000..7f85143 --- /dev/null +++ b/workspace/extension/src/client/svelte-4.js @@ -0,0 +1,276 @@ +import { send } from './runtime.js'; +import { serialize } from './utils.js'; + +/** @type {undefined | SvelteBlockDetail} */ +let current_block; + +export const index = { + /** @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 = anchor && this.map.get(anchor); + if (target) { + const idx = target.children.findIndex((n) => n === sibling); + if (idx === -1) target.children.push(node); + else target.children.splice(idx, 0, node); + } + + send('bridge::courier/node->add', { + node: serialize(node), + target: node.parent?.id, + anchor: sibling?.id, + }); + }, + + /** @param {{ node: SvelteBlockDetail; target?: Node; anchor?: Node }} opts */ + update({ node }) { + send('bridge::courier/node->update', { + node: serialize(node), + }); + }, + + /** @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; + } + + send('bridge::courier/node->remove', { + node: serialize(node), + }); + }, +}; + +document.addEventListener('SvelteRegisterComponent', ({ detail }) => { + const { component, tagName } = detail; + + const node = index.map.get(component.$$.fragment); + if (node) { + index.map.delete(component.$$.fragment); + + node.detail = component; + node.tagName = tagName; + + index.update({ node }); + } else { + // @ts-expect-error - component special case + index.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 = crypto.randomUUID(); + + 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 = index.map.get(block); + if (component) { + index.map.delete(block); + Object.assign(node, component); + } else { + node.type = 'component'; + node.tagName = 'Unknown'; + node.detail = {}; + index.map.set(block, node); + } + + Promise.resolve().then(() => { + const invalidate = node.detail.$$?.bound || {}; + Object.keys(invalidate).length && index.update({ node }); + }); + break; + } + } + + if (type === 'each') { + let group = parent && index.map.get(parent.id + id); + if (!group) { + // @ts-expect-error - each block fallback + group = /** @type {SvelteBlockDetail} */ ({ + version: '', + id: crypto.randomUUID(), + type: 'block', + tagName: 'each', + container: parent, + children: [], + detail: { + ctx: {}, + source: detail.source, + }, + }); + parent && index.map.set(parent.id + id, group); + index.add({ node: group, target, anchor }); + } + + node.container = group; + node.type = 'iteration'; + + // @ts-expect-error - overloaded nodes + index.add({ node, target: group, anchor }); + } else { + index.add({ node, target, anchor }); + } + + current_block = node; + + original(target, anchor); + + current_block = parent; + }; + } + + if (block.p) { + const original = block.p; + block.p = (changed, ctx) => { + const parent = current_block; + current_block = index.map.get(current_node_id); + current_block && index.update({ node: current_block }); + + original(changed, ctx); + + current_block = parent; + }; + } + + if (block.d) { + const original = block.d; + block.d = (detaching) => { + const node = index.map.get(current_node_id); + if (node) { + if (node.tagName === 'await') { + last_promise = node.container; + } + index.remove(node); + } + + 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'; + + index.add({ + anchor, + target, + // @ts-expect-error - missing properties are irrelevant + node: { + id: crypto.randomUUID(), + type, + detail: element, + tagName: element.nodeName.toLowerCase(), + container: current_block, + children: [], + }, + }); + + element.childNodes.forEach((child) => { + !index.map.has(child) && deep_insert({ node: child, target: element }); + }); + } +}); + +document.addEventListener('SvelteDOMRemove', ({ detail }) => { + const node = index.map.get(detail.node); + if (node) index.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 = index.map.get(detail.node); + if (!node) return; + if (node.type === 'anchor') node.type = 'text'; + index.update({ node }); +}); + +document.addEventListener('SvelteDOMSetProperty', ({ detail }) => { + const node = index.map.get(detail.node); + if (!node) return; + if (node.type === 'anchor') node.type = 'text'; + index.update({ node }); +}); + +document.addEventListener('SvelteDOMSetAttribute', ({ detail }) => { + const node = index.map.get(detail.node); + if (!node) return; + if (node.type === 'anchor') node.type = 'text'; + index.update({ node }); +}); + +document.addEventListener('SvelteDOMRemoveAttribute', ({ detail }) => { + const node = index.map.get(detail.node); + if (!node) return; + if (node.type === 'anchor') node.type = 'text'; + index.update({ node }); +}); diff --git a/workspace/extension/src/client/utils.js b/workspace/extension/src/client/utils.js new file mode 100644 index 0000000..6137ab2 --- /dev/null +++ b/workspace/extension/src/client/utils.js @@ -0,0 +1,101 @@ +/** + * @param {unknown} value + * @returns {any} + */ +function clone(value, seen = new Map()) { + 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); + + const descriptors = Object.getOwnPropertyDescriptors(value); + for (const [key, v] of Object.entries(value)) { + const { get, writable } = descriptors[key]; + const readonly = !writable || get !== undefined; + o[key] = { key, value: clone(v, seen), readonly }; + } + return o; + } + default: + return value; + } +} + +/** @param {SvelteBlockDetail} node */ +export 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 captured = 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 = clone(captured[key]); + delete captured[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(captured).map(([key, v]) => ({ key, value: clone(v) })), + }; + 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, readonly: true })), + 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, readonly: true })), + source: source.slice(source.indexOf('{'), source.indexOf('}') + 1), + }; + break; + } + } + + return res; +} diff --git a/workspace/extension/src/entry.ts b/workspace/extension/src/entry.ts new file mode 100644 index 0000000..54dd677 --- /dev/null +++ b/workspace/extension/src/entry.ts @@ -0,0 +1,13 @@ +import './app.css'; +import App from './App.svelte'; +import { mount } from 'svelte'; + +if (chrome.devtools.panels.themeName === 'dark') { + document.body.classList.add('dark'); +} else { + document.body.classList.remove('dark'); +} + +export default mount(App, { + target: document.querySelector('#app')!, +}); diff --git a/workspace/extension/src/lib/components/Button.svelte b/workspace/extension/src/lib/components/Button.svelte new file mode 100644 index 0000000..1883286 --- /dev/null +++ b/workspace/extension/src/lib/components/Button.svelte @@ -0,0 +1,83 @@ + + + + + diff --git a/workspace/extension/src/lib/components/Divider.svelte b/workspace/extension/src/lib/components/Divider.svelte new file mode 100644 index 0000000..a44166d --- /dev/null +++ b/workspace/extension/src/lib/components/Divider.svelte @@ -0,0 +1,30 @@ + + +
    + + diff --git a/workspace/extension/src/lib/components/Indexer.svelte b/workspace/extension/src/lib/components/Indexer.svelte new file mode 100644 index 0000000..d966b46 --- /dev/null +++ b/workspace/extension/src/lib/components/Indexer.svelte @@ -0,0 +1,31 @@ + + +

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

    + + diff --git a/workspace/extension/src/lib/components/Relative.svelte b/workspace/extension/src/lib/components/Relative.svelte new file mode 100644 index 0000000..1e63383 --- /dev/null +++ b/workspace/extension/src/lib/components/Relative.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children()} +
    + + diff --git a/workspace/extension/src/lib/components/Resizable.svelte b/workspace/extension/src/lib/components/Resizable.svelte new file mode 100644 index 0000000..8439766 --- /dev/null +++ b/workspace/extension/src/lib/components/Resizable.svelte @@ -0,0 +1,93 @@ + + + (resizing = false)} + onmousemove={({ pageX: x, pageY: y }) => { + if (!resizing) return; + size = axis === 'x' ? window.innerWidth - x : window.innerHeight - y; + }} +/> + + + + diff --git a/workspace/extension/src/lib/components/Toolbar.svelte b/workspace/extension/src/lib/components/Toolbar.svelte new file mode 100644 index 0000000..8f73a8e --- /dev/null +++ b/workspace/extension/src/lib/components/Toolbar.svelte @@ -0,0 +1,26 @@ + + + + + diff --git a/workspace/extension/src/lib/nodes/Block.svelte b/workspace/extension/src/lib/nodes/Block.svelte new file mode 100644 index 0000000..564f8f6 --- /dev/null +++ b/workspace/extension/src/lib/nodes/Block.svelte @@ -0,0 +1,43 @@ + + + + +{#if expanded} + {@render children()} + +
    + +
    +{/if} + + diff --git a/workspace/extension/src/lib/nodes/Element.svelte b/workspace/extension/src/lib/nodes/Element.svelte new file mode 100644 index 0000000..a6e6171 --- /dev/null +++ b/workspace/extension/src/lib/nodes/Element.svelte @@ -0,0 +1,96 @@ + + +{#snippet close()} + </ + + + + > +{/snippet} + +
    (expanded = !empty && !expanded)} +> + < + + + + + + {#if empty} +  /> + {:else} + > + {#if !expanded} + (expanded = true)} /> + + {@render close()} + {/if} + {/if} +
    + +{#if expanded} + {@render children()} + +
    + {@render close()} +
    +{/if} + + diff --git a/workspace/extension/src/lib/nodes/ElementAttributes.svelte b/workspace/extension/src/lib/nodes/ElementAttributes.svelte new file mode 100644 index 0000000..d989fbc --- /dev/null +++ b/workspace/extension/src/lib/nodes/ElementAttributes.svelte @@ -0,0 +1,67 @@ + + +{#each attributes as { key, value, bounded } (key)} + {@const prefix = bounded ? 'bind:' : ''} + +   + + + + + = + + + + +{/each} + +{#each listeners as { event, handler, modifiers }} + {@const suffix = modifiers?.length ? `|${modifiers.join('|')}` : ''} + +   + + + +{/each} + + diff --git a/workspace/extension/src/lib/nodes/Ellipsis.svelte b/workspace/extension/src/lib/nodes/Ellipsis.svelte new file mode 100644 index 0000000..466e591 --- /dev/null +++ b/workspace/extension/src/lib/nodes/Ellipsis.svelte @@ -0,0 +1,26 @@ + + + + + diff --git a/workspace/extension/src/lib/nodes/Iteration.svelte b/workspace/extension/src/lib/nodes/Iteration.svelte new file mode 100644 index 0000000..dff58c2 --- /dev/null +++ b/workspace/extension/src/lib/nodes/Iteration.svelte @@ -0,0 +1,22 @@ + + + + + +{#if expanded} + {@render children()} +{/if} diff --git a/workspace/extension/src/lib/nodes/Node.svelte b/workspace/extension/src/lib/nodes/Node.svelte new file mode 100644 index 0000000..aac0b82 --- /dev/null +++ b/workspace/extension/src/lib/nodes/Node.svelte @@ -0,0 +1,161 @@ + + +{#snippet expand(children: (typeof node)['children'], level: number)} + {#each children as child (child.id)} + + {/each} +{/snippet} + +{#if visibility[node.type]} + +
  • Object.assign(prev, node)} + onclick={(event) => { + event.stopPropagation(); + app.selected = node; + }} + onmousemove={(event) => { + event.stopPropagation(); + if (app.hovered?.id === node.id) return; + background.send('bridge::ext/highlight', node.id); + app.hovered = node; + }} + > + {#if node.type === 'component' || node.type === 'element'} + +
      {@render expand(node.children, depth + 1)}
    +
    + {:else if node.type === 'block'} + +
      {@render expand(node.children, depth + 1)}
    +
    + {:else if node.type === 'iteration'} + +
      {@render expand(node.children, depth + 1)}
    +
    + {:else if node.type === 'slot'} + +
      {@render expand(node.children, depth + 1)}
    +
    + {:else if node.type === 'text'} +
    + +
    + {:else if node.type === 'anchor'} +
    #anchor
    + {/if} +
  • +{:else} + {@render expand(node.children, depth)} +{/if} + + diff --git a/workspace/extension/src/lib/nodes/Slot.svelte b/workspace/extension/src/lib/nodes/Slot.svelte new file mode 100644 index 0000000..efa65e7 --- /dev/null +++ b/workspace/extension/src/lib/nodes/Slot.svelte @@ -0,0 +1,29 @@ + + + + +{#if expanded} + {@render children()} + +
    + +
    +{/if} diff --git a/workspace/extension/src/lib/panel/Editable.svelte b/workspace/extension/src/lib/panel/Editable.svelte new file mode 100644 index 0000000..8a08bc0 --- /dev/null +++ b/workspace/extension/src/lib/panel/Editable.svelte @@ -0,0 +1,105 @@ + + +{#if editing} + + { + // @ts-expect-error - target and value exists + update(target.value); + }} + onkeydown={(event) => { + const { key, target } = event; + if (key === 'Escape') { + event.preventDefault(); + editing = false; + return; + } + if (key !== 'Enter') return; + // @ts-expect-error - target and value exists + update(target.value); + }} + /> +{:else} + + (editing = !readonly)}> + {type === 'string' ? `"${value}"` : `${value}`} + +{/if} + + diff --git a/workspace/extension/src/lib/panel/PropertyList.svelte b/workspace/extension/src/lib/panel/PropertyList.svelte new file mode 100644 index 0000000..f6188aa --- /dev/null +++ b/workspace/extension/src/lib/panel/PropertyList.svelte @@ -0,0 +1,140 @@ + + +{#if entries.length} +
      + {#each entries as { key, value, readonly = false } (key)} + {@const keys = [...parents, key]} + {@const type = typeof value} + + +
    • { + event.stopPropagation(); + expanded[key] = !expanded[key]; + }} + > + {key}: +   + + {#if type === 'string'} + inject(keys, updated)} + /> + {:else if value == null || value !== value} + inject(keys, updated)} + /> + {:else if type === 'number' || type === 'boolean'} + inject(keys, updated)} + /> + {:else if Array.isArray(value)} + Array [{value.length || ''}] + + {#if value.length && expanded[key]} + {@const entries = value.map((v, i) => ({ key: `${i}`, value: v, readonly }))} + + + {/if} + {:else if type === 'object'} + {#if value.__is === 'function'} + function {value.name || ''}() + {#if expanded[key]}
      {value.source}
      {/if} + {:else if value.__is === 'symbol'} + {value.name || 'Symbol()'} + {:else if Object.keys(value).length} + Object {…} + + {#if expanded[key]} + + {/if} + {:else} + Object { } + {/if} + {/if} +
    • + {/each} +
    +{:else} +
    None
    +{/if} + + diff --git a/workspace/extension/src/lib/panel/core.svelte.ts b/workspace/extension/src/lib/panel/core.svelte.ts new file mode 100644 index 0000000..3865e1a --- /dev/null +++ b/workspace/extension/src/lib/panel/core.svelte.ts @@ -0,0 +1,17 @@ +import { app } from '$lib/state.svelte'; + +export const errors = $state<{ [keys: string]: string | false }>({}); + +export function inject(keys: string[], value: any) { + const uuid = app.selected?.id; + if (!uuid) return; + + const accessors = `[${keys.map((k) => `'${k}'`).join(', ')}]`; + chrome.devtools.inspectedWindow.eval( + `window['#SvelteDevTools'].inject('${uuid}', ${accessors}, ${value})`, + (_, error) => { + const id = `${uuid}+${keys.join('.')}`; + errors[id] = error?.isException && error.value.slice(0, error.value.indexOf('\n')); + }, + ); +} diff --git a/workspace/extension/src/lib/runtime.svelte.ts b/workspace/extension/src/lib/runtime.svelte.ts new file mode 100644 index 0000000..d8cd54c --- /dev/null +++ b/workspace/extension/src/lib/runtime.svelte.ts @@ -0,0 +1,121 @@ +import { type DebugNode, app } from './state.svelte'; + +const tabId = chrome.devtools.inspectedWindow.tabId; +let port = chrome.runtime.connect({ name: `${tabId}` }); + +port.postMessage({ source: 'svelte-devtools', tabId, type: 'bypass::ext/init' }); + +export const background = { + send(type: `bridge::${'ext' | 'page'}/${string}` | 'bypass::ext/page->refresh', payload?: any) { + try { + port.postMessage({ source: 'svelte-devtools', tabId, type, payload }); + } catch { + // https://developer.chrome.com/docs/extensions/develop/concepts/messaging#port-lifetime + // chrome aggressively disconnects the port, not much we can do other than to reconnect + port = chrome.runtime.connect({ name: `${tabId}` }); + background.send(type, payload); // retry immediately + } + }, +}; + +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 'bridge::ext/clear': { + app.nodes = {}; + app.selected = undefined; + app.hovered = undefined; + break; + } + + case 'bridge::ext/inspect': { + if (typeof payload === 'string') break; + app.selected = app.nodes[payload.node.id]; + app.inspecting = false; + break; + } + + case 'bridge::courier/node->add': { + const { node, target, anchor } = payload as { + node: DebugNode; + target: string; + anchor: string; + }; + + node.parent = app.nodes[target]; + node.children = []; + node.expanded = false; + resolveEventBubble(node); + + app.nodes[node.id] = node; + if (!node.parent) break; + + const siblings = node.parent.children; + const index = siblings.findIndex((n) => n.id === anchor); + if (index === -1) siblings.push(node); + else siblings.splice(index, 0, node); + + break; + } + + case 'bridge::courier/node->remove': { + const node = payload.node as SvelteBlockDetail; + const current = app.nodes[node.id]; + if (current) delete app.nodes[current.id]; + if (!current?.parent) break; + + const index = current.parent.children.findIndex((o) => o.id === current.id); + current.parent.children.splice(index, 1); + break; + } + + case 'bridge::courier/node->update': { + const node = payload.node as SvelteBlockDetail; + const current = app.nodes[node.id]; + if (!current) break; + Object.assign(current, node); + resolveEventBubble(current); + break; + } + + // case 'bridge::courier/profile->update': { + // resolveFrame(frame); + // profileFrame.set(frame); + // break; + + // function resolveFrame(frame) { + // frame.children.forEach(resolveFrame); + + // if (!frame.node) return; + + // frame.node = app.nodes.get(frame.node) || { + // type: 'Unknown', + // tagName: 'Unknown', + // }; + // } + // } + } +}); diff --git a/workspace/extension/src/lib/state.svelte.ts b/workspace/extension/src/lib/state.svelte.ts new file mode 100644 index 0000000..f192277 --- /dev/null +++ b/workspace/extension/src/lib/state.svelte.ts @@ -0,0 +1,53 @@ +type Overwrite = Omit & B; + +export type DebugNode = Overwrite< + SvelteBlockDetail, + { + 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 app = $state({ + nodes: {} as { [key: string]: DebugNode }, + get root() { + const nodes = Object.values(this.nodes); + return nodes.filter((node) => !node.parent); + }, + + selected: undefined as undefined | DebugNode, + hovered: undefined as undefined | DebugNode, + + inspecting: false, + query: '', +}); + +export const visibility = $state<{ [key: string]: boolean }>({ + component: true, + element: true, + block: true, + iteration: true, + slot: true, + text: true, + anchor: false, +}); diff --git a/workspace/extension/src/routes/Breadcrumbs.svelte b/workspace/extension/src/routes/Breadcrumbs.svelte new file mode 100644 index 0000000..3cf1c56 --- /dev/null +++ b/workspace/extension/src/routes/Breadcrumbs.svelte @@ -0,0 +1,65 @@ + + +{#if breadcrumbs.length} +
      + {#each breadcrumbs as node} + {#if visibility[node.type]} + + {/if} + {/each} +
    +{/if} + + diff --git a/workspace/extension/src/routes/ConnectMessage.svelte b/workspace/extension/src/routes/ConnectMessage.svelte new file mode 100644 index 0000000..4815049 --- /dev/null +++ b/workspace/extension/src/routes/ConnectMessage.svelte @@ -0,0 +1,66 @@ + + +
    +

    Svelte DevTools

    +

    + No Svelte app detected + + +

    + +
    +

    Not working? Did you...

    +
      +
    • Build with dev mode enabled?
    • +
    • Use Svelte version ^4.0.0?
    • +
    +
    +
    + + diff --git a/workspace/extension/src/routes/Inspector.svelte b/workspace/extension/src/routes/Inspector.svelte new file mode 100644 index 0000000..ff7ca08 --- /dev/null +++ b/workspace/extension/src/routes/Inspector.svelte @@ -0,0 +1,22 @@ + + + diff --git a/workspace/extension/src/routes/ProfileButton.svelte b/workspace/extension/src/routes/ProfileButton.svelte new file mode 100644 index 0000000..75951f1 --- /dev/null +++ b/workspace/extension/src/routes/ProfileButton.svelte @@ -0,0 +1,18 @@ + + + diff --git a/workspace/extension/src/routes/Profiler.svelte b/workspace/extension/src/routes/Profiler.svelte new file mode 100644 index 0000000..5b99c4b --- /dev/null +++ b/workspace/extension/src/routes/Profiler.svelte @@ -0,0 +1,133 @@ + + + + {#if top} + + {:else} + + {/if} + + +
    + {#if children.length} + { + if (selected === frame) top = frame; + else selected = frame; + }} + /> + {: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/workspace/extension/src/routes/ProfilerFrame.svelte b/workspace/extension/src/routes/ProfilerFrame.svelte new file mode 100644 index 0000000..c102163 --- /dev/null +++ b/workspace/extension/src/routes/ProfilerFrame.svelte @@ -0,0 +1,72 @@ + + +{#if children?.length} +
      + {#each children as frame} +
    • + + + onclick(frame)} /> +
    • + {/each} +
    +{/if} + + diff --git a/workspace/extension/src/routes/SearchBox.svelte b/workspace/extension/src/routes/SearchBox.svelte new file mode 100644 index 0000000..e305db3 --- /dev/null +++ b/workspace/extension/src/routes/SearchBox.svelte @@ -0,0 +1,98 @@ + + +
    event.preventDefault()}> + + + + + + { + if (key === 'Enter') submit[shiftKey ? 'prev' : 'next'](); + }} + /> + + {#if results.length && position > -1} + {position + 1} of {results.length} + {/if} + + + +
    + + diff --git a/workspace/extension/src/routes/VisibilitySelection.svelte b/workspace/extension/src/routes/VisibilitySelection.svelte new file mode 100644 index 0000000..d126ff9 --- /dev/null +++ b/workspace/extension/src/routes/VisibilitySelection.svelte @@ -0,0 +1,80 @@ + + + + + + {#if opened} +
    + {#each Object.keys(visibility) as key} + + {/each} +
    + {/if} +
    + + diff --git a/workspace/extension/static/background.js b/workspace/extension/static/background.js new file mode 100644 index 0000000..ce2d67a --- /dev/null +++ b/workspace/extension/static/background.js @@ -0,0 +1,131 @@ +/** @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) => { + switch (message.type) { + case 'bypass::ext/init': { + ports.set(message.tabId, sender); + if (!chrome.tabs.onUpdated.hasListener(courier)) { + chrome.tabs.onUpdated.addListener(courier); + } + break; + } + case 'bypass::ext/page->refresh': { + chrome.tabs.reload(message.tabId, { bypassCache: true }); + break; + } + + default: // relay messages from devtools to tab + 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 === 'bypass::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 }, + + // ensures we're listening to the events before they're dispatched + 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. + // only a subset of APIs are available in this `chrome` limbo + // - chrome.csi->f() + // - chrome.dom.{openOrClosedShadowRoot->f()} + // - chrome.extension.{ViewType, inIncognitoContext} + // - chrome.i18n + // - chrome.runtime + func: () => { + chrome.runtime.onMessage.addListener((message, sender) => { + if (sender.id !== chrome.runtime.id) return; // unexpected sender + window.postMessage(message); // relay to content script (courier.js) + }); + + 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: 'bridge::ext/clear' }); + }); + }, + }); +} + +chrome.tabs.onActivated.addListener(({ tabId }) => sensor(tabId)); +chrome.tabs.onUpdated.addListener( + (tabId, changed) => changed.status === 'complete' && sensor(tabId), +); + +/** @param {number} tabId */ +async function sensor(tabId) { + try { + // add SvelteDevTools event listener + await chrome.scripting.executeScript({ + target: { tabId }, + func: () => { + document.addEventListener('SvelteDevTools', ({ detail }) => { + chrome.runtime.sendMessage(detail); + }); + }, + }); + // capture data to send to listener + await chrome.scripting.executeScript({ + target: { tabId }, + world: 'MAIN', + func: () => { + // @ts-ignore - injected if the website is using svelte + const [major] = [...(window.__svelte?.v ?? [])]; + + document.dispatchEvent( + new CustomEvent('SvelteDevTools', { + detail: { type: 'bypass::ext/icon:set', payload: major }, + }), + ); + }, + }); + } 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/workspace/extension/static/icons/default-128.png b/workspace/extension/static/icons/default-128.png new file mode 100644 index 0000000..e6ee090 Binary files /dev/null and b/workspace/extension/static/icons/default-128.png differ diff --git a/workspace/extension/static/icons/default-16.png b/workspace/extension/static/icons/default-16.png new file mode 100644 index 0000000..9b7bb76 Binary files /dev/null and b/workspace/extension/static/icons/default-16.png differ diff --git a/workspace/extension/static/icons/default-24.png b/workspace/extension/static/icons/default-24.png new file mode 100644 index 0000000..b5eb83c Binary files /dev/null and b/workspace/extension/static/icons/default-24.png differ diff --git a/workspace/extension/static/icons/default-48.png b/workspace/extension/static/icons/default-48.png new file mode 100644 index 0000000..543a150 Binary files /dev/null and b/workspace/extension/static/icons/default-48.png differ diff --git a/workspace/extension/static/icons/default-96.png b/workspace/extension/static/icons/default-96.png new file mode 100644 index 0000000..903efd5 Binary files /dev/null and b/workspace/extension/static/icons/default-96.png differ diff --git a/workspace/extension/static/icons/disabled-128.png b/workspace/extension/static/icons/disabled-128.png new file mode 100644 index 0000000..8df184a Binary files /dev/null and b/workspace/extension/static/icons/disabled-128.png differ diff --git a/workspace/extension/static/icons/disabled-16.png b/workspace/extension/static/icons/disabled-16.png new file mode 100644 index 0000000..a3b737f Binary files /dev/null and b/workspace/extension/static/icons/disabled-16.png differ diff --git a/workspace/extension/static/icons/disabled-24.png b/workspace/extension/static/icons/disabled-24.png new file mode 100644 index 0000000..d75f5a2 Binary files /dev/null and b/workspace/extension/static/icons/disabled-24.png differ diff --git a/workspace/extension/static/icons/disabled-48.png b/workspace/extension/static/icons/disabled-48.png new file mode 100644 index 0000000..602426f Binary files /dev/null and b/workspace/extension/static/icons/disabled-48.png differ diff --git a/workspace/extension/static/icons/disabled-96.png b/workspace/extension/static/icons/disabled-96.png new file mode 100644 index 0000000..906eccb Binary files /dev/null and b/workspace/extension/static/icons/disabled-96.png differ diff --git a/dest/devtools/svelte-logo-dark.svg b/workspace/extension/static/icons/svelte-dark.svg similarity index 100% rename from dest/devtools/svelte-logo-dark.svg rename to workspace/extension/static/icons/svelte-dark.svg diff --git a/dest/devtools/svelte-logo-light.svg b/workspace/extension/static/icons/svelte-default.svg similarity index 100% rename from dest/devtools/svelte-logo-light.svg rename to workspace/extension/static/icons/svelte-default.svg diff --git a/workspace/extension/static/icons/svelte-disabled.svg b/workspace/extension/static/icons/svelte-disabled.svg new file mode 100644 index 0000000..43dc3fb --- /dev/null +++ b/workspace/extension/static/icons/svelte-disabled.svg @@ -0,0 +1,14 @@ + + + + + diff --git a/src/svelte-logo.svg b/workspace/extension/static/icons/svelte.svg similarity index 100% rename from src/svelte-logo.svg rename to workspace/extension/static/icons/svelte.svg diff --git a/workspace/extension/static/manifest.json b/workspace/extension/static/manifest.json new file mode 100644 index 0000000..55e14ed --- /dev/null +++ b/workspace/extension/static/manifest.json @@ -0,0 +1,48 @@ +{ + "manifest_version": 3, + "name": "Svelte DevTools", + "version": "2.2.2", + "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": { + "scripts": ["background.js"], + "service_worker": "background.js" + }, + "content_scripts": [ + { + "matches": [""], + "js": ["courier.js"], + "run_at": "document_start", + "world": "MAIN" + } + ], + "devtools_page": "register.html", + "host_permissions": ["*://*/*"], + "permissions": ["activeTab", "scripting"], + "web_accessible_resources": [{ "matches": ["*://*/*"], "resources": ["courier.js"] }], + + "minimum_chrome_version": "121", + "browser_specific_settings": { + "gecko": { + "id": "firefox-devtools@svelte.dev", + "strict_min_version": "121.0", + "update_url": "https://svelte.dev/devtools/updates.json" + } + } +} diff --git a/workspace/extension/static/register.html b/workspace/extension/static/register.html new file mode 100644 index 0000000..1b02225 --- /dev/null +++ b/workspace/extension/static/register.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/workspace/extension/static/register.js b/workspace/extension/static/register.js new file mode 100644 index 0000000..a33bdbc --- /dev/null +++ b/workspace/extension/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: 'bridge::ext/inspect', payload }), + // ), + // ); + // }, +); diff --git a/workspace/extension/svelte.config.js b/workspace/extension/svelte.config.js new file mode 100644 index 0000000..58e94ac --- /dev/null +++ b/workspace/extension/svelte.config.js @@ -0,0 +1,14 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +export default { + compilerOptions: { + runes: true, + }, + + preprocess: vitePreprocess(), + + onwarn(warning, handler) { + if (warning.message.includes('A11y')) return; + !warning.message.includes('chrome') && handler(warning); + }, +}; diff --git a/workspace/extension/tsconfig.json b/workspace/extension/tsconfig.json new file mode 100644 index 0000000..10ae081 --- /dev/null +++ b/workspace/extension/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/workspace/extension/vite.config.ts b/workspace/extension/vite.config.ts new file mode 100644 index 0000000..fde691d --- /dev/null +++ b/workspace/extension/vite.config.ts @@ -0,0 +1,22 @@ +import { resolve } from 'node:path'; +import { svelte } from '@sveltejs/vite-plugin-svelte'; +import { defineConfig } from 'vite'; + +export default defineConfig(() => { + return { + plugins: [svelte()], + + build: { + cssTarget: 'chrome111', + 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