├── .gitignore
├── Readme.md
├── app
├── .gitignore
├── .vscode
│ └── extensions.json
├── helper
│ ├── helper.ts
│ ├── stringify.ts
│ └── tsconfig.json
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
│ ├── App.svelte
│ ├── Components
│ │ ├── Container.svelte
│ │ ├── Details.svelte
│ │ ├── Details
│ │ │ ├── CollapsableValue.svelte
│ │ │ ├── Editable.svelte
│ │ │ ├── PropertyList.svelte
│ │ │ └── nodes
│ │ │ │ └── Collapse.svelte
│ │ ├── LeftPane.svelte
│ │ ├── List.svelte
│ │ ├── Resize.svelte
│ │ ├── RightPane.svelte
│ │ ├── Statement.svelte
│ │ ├── Summary.svelte
│ │ └── Toolbar.svelte
│ ├── main.ts
│ ├── scss
│ │ ├── _icons.scss
│ │ ├── _json-diff.scss
│ │ ├── _tablesort.scss
│ │ ├── _theme.scss
│ │ └── global.scss
│ ├── store.ts
│ ├── types.ts
│ └── utils.ts
└── tsconfig.json
├── extension
├── devtools
│ ├── background.js
│ ├── content.js
│ ├── devtools.html
│ ├── devtools.js
│ ├── panel.html
│ ├── svelte-logo-dark.svg
│ └── svelte-logo-light.svg
├── dist
│ ├── .gitignore
│ └── icons
│ │ ├── largeIcons-on.svg
│ │ ├── largeIcons.svg
│ │ └── mediumIcons.svg
└── manifest.json
├── images
├── chrome_32x32.png
├── firefox_32x32.png
├── list.png
└── summary.png
├── logo
└── logo.svg
├── package.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | web-ext-artifacts
3 | *.map
4 | extension/devtools/helper.js
5 | extension/icons
6 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # Svelte Reactive Debugger
2 | Easily monitor svelte reactive statements
3 |
4 | This extension needs this custom preprocessor to work
5 | https://github.com/unlocomqx/svelte-reactive-preprocessor
6 |
7 | ## Install (Chrome/Firefox)
8 | [](https://chrome.google.com/webstore/detail/svelte-reactive-debugger/mieppkcamgfhpjedhnfdlbndijhohmjf)
9 | [](https://addons.mozilla.org/en-US/firefox/addon/svelte-reactive-debugger/)
10 |
11 | ## List
12 | The extension displays the statements as they are executed
13 |
14 | 
15 |
16 | ## Summary
17 | The statements are grouped to show the total count and duration of each statement
18 |
19 | 
20 |
21 | # How to build
22 | ```shell
23 | npm i && npm i --prefix app
24 | npm run build
25 | ```
26 | _Built using node 15.4.0 and npm 7.0.15_
27 |
28 | # How to test (requires Firefox)
29 | ```shell
30 | npm run start
31 | ```
32 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /public/build/
3 |
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/app/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["svelte.svelte-vscode"]
3 | }
4 |
--------------------------------------------------------------------------------
/app/helper/helper.ts:
--------------------------------------------------------------------------------
1 | import { stringify } from "./stringify";
2 |
3 | (window as any).rpDsp = function (type, detail, start_time, exec_id, start_state, end_state) {
4 | detail = detail || {};
5 | detail.start_time = start_time;
6 | detail.exec_id = exec_id;
7 | detail.start_state = stringify(start_state);
8 | detail.end_state = stringify(end_state);
9 | const ev = new CustomEvent(type, { detail });
10 | document.dispatchEvent(ev);
11 | };
12 |
--------------------------------------------------------------------------------
/app/helper/stringify.ts:
--------------------------------------------------------------------------------
1 | export function stringify(obj, replacer?, spaces?, cycleReplacer?) {
2 |
3 | function serializer(replacer, cycleReplacer) {
4 | var stack = [], keys = [];
5 |
6 | if (cycleReplacer == null) {
7 | cycleReplacer = function (key, value) {
8 | if (stack[0] === value) {
9 | return "[Circular ~]";
10 | }
11 | return "[Circular ~." + keys.slice(0, stack.indexOf(value)).join(".") + "]";
12 | };
13 | }
14 |
15 | return function (key, value) {
16 | if (stack.length > 0) {
17 | var thisPos = stack.indexOf(this);
18 | ~thisPos ? stack.splice(thisPos + 1) : stack.push(this);
19 | ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key);
20 | if (~stack.indexOf(value)) {
21 | value = cycleReplacer.call(this, key, value);
22 | }
23 | } else {
24 | stack.push(value);
25 | }
26 |
27 | // filter rpGlobal from dispatched state (being equal to window, we want to avoid seriliazing it)
28 | if (key === "rpGlobal") {
29 | return undefined;
30 | }
31 | // filter functions
32 | if (typeof value === "function") {
33 | return undefined;
34 | }
35 | // filter svelte stores
36 | if (value && typeof value.subscribe === "function") {
37 | return undefined;
38 | }
39 |
40 | if (value instanceof Element) {
41 | return undefined;
42 | }
43 |
44 | return replacer == null ? value : replacer.call(this, key, value);
45 | };
46 | }
47 |
48 | return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces);
49 | }
50 |
--------------------------------------------------------------------------------
/app/helper/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "moduleResolution": "node",
5 | "allowSyntheticDefaultImports": true
6 | },
7 | "include": [
8 | "**/*"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelte-app",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "build": "rollup -c",
6 | "dev": "rollup -c -w",
7 | "start": "sirv ../extension/dist",
8 | "validate": "svelte-check"
9 | },
10 | "devDependencies": {
11 | "@rollup/plugin-commonjs": "^17.0.0",
12 | "@rollup/plugin-node-resolve": "^11.0.1",
13 | "@rollup/plugin-typescript": "^8.1.0",
14 | "@tsconfig/svelte": "^1.0.10",
15 | "@types/chrome": "^0.0.128",
16 | "node-sass": "^7.0.1",
17 | "rollup": "^2.36.1",
18 | "rollup-plugin-css-only": "^3.1.0",
19 | "rollup-plugin-livereload": "^2.0.0",
20 | "rollup-plugin-scss": "^2.6.1",
21 | "rollup-plugin-svelte": "^7.0.0",
22 | "rollup-plugin-terser": "^7.0.2",
23 | "svelte": "^3.31.2",
24 | "svelte-check": "^1.1.26",
25 | "svelte-preprocess": "^4.6.1",
26 | "tslib": "^2.1.0",
27 | "typescript": "^4.1.3"
28 | },
29 | "dependencies": {
30 | "copy-to-clipboard": "^3.3.1",
31 | "deep-object-diff": "^1.1.0",
32 | "jsondiffpatch": "^0.4.1",
33 | "sirv-cli": "^1.0.10",
34 | "svelte-local-storage-store": "^0.1.4",
35 | "svelte-tablesort": "github:unlocomqx/svelte-tablesort"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/rollup.config.js:
--------------------------------------------------------------------------------
1 | import svelte from "rollup-plugin-svelte";
2 | import commonjs from "@rollup/plugin-commonjs";
3 | import resolve from "@rollup/plugin-node-resolve";
4 | import css from "rollup-plugin-css-only";
5 | import sveltePreprocess from "svelte-preprocess";
6 | import typescript from "@rollup/plugin-typescript";
7 | import scss from "rollup-plugin-scss";
8 | import livereload from "rollup-plugin-livereload";
9 |
10 | const production = !process.env.ROLLUP_WATCH;
11 |
12 | function serve() {
13 | let server;
14 |
15 | function toExit() {
16 | if (server) {
17 | server.kill(0);
18 | }
19 | }
20 |
21 | return {
22 | writeBundle() {
23 | if (server) {
24 | return;
25 | }
26 | server = require("child_process").spawn("npm", ["run", "start", "--", "--dev"], {
27 | stdio: ["ignore", "inherit", "inherit"],
28 | shell: true
29 | });
30 |
31 | process.on("SIGTERM", toExit);
32 | process.on("exit", toExit);
33 | }
34 | };
35 | }
36 |
37 | const svelteCompile = {
38 | input: "src/main.ts",
39 | output: {
40 | sourcemap: true,
41 | format: "iife",
42 | name: "app",
43 | file: "../extension/dist/build/bundle.js",
44 | globals: {chrome: "chrome"},
45 | },
46 | external: ["chrome"],
47 | plugins: [
48 | svelte({
49 | preprocess: sveltePreprocess(),
50 | compilerOptions: {
51 | // enable run-time checks when not in production
52 | dev: !production
53 | }
54 | }),
55 | // we'll extract any component CSS out into
56 | // a separate file - better for performance
57 | css({output: "bundle.css"}),
58 |
59 | // If you have external dependencies installed from
60 | // npm, you'll most likely need these plugins. In
61 | // some cases you'll need additional configuration -
62 | // consult the documentation for details:
63 | // https://github.com/rollup/plugins/tree/master/packages/commonjs
64 | resolve({
65 | browser: true,
66 | dedupe: importee => importee === "svelte" || importee.startsWith("svelte/")
67 | }),
68 | commonjs(),
69 | typescript({
70 | sourceMap: true,
71 | inlineSources: !production
72 | }),
73 | scss({
74 | output: '../extension/dist/build/global.css',
75 | }),
76 |
77 | // Watch the `public` directory and refresh the
78 | // browser on changes when not in production
79 | !production && livereload("../dist"),
80 |
81 | // In dev mode, call `npm run start` once
82 | // the bundle has been generated
83 | !production && serve(),
84 | ],
85 | watch: {
86 | clearScreen: false
87 | }
88 | };
89 |
90 | const helperCompile = {
91 | input: "helper/helper.ts",
92 | output: {
93 | sourcemap: true,
94 | format: "iife",
95 | file: "../extension/devtools/helper.js",
96 | },
97 | plugins: [
98 | resolve({
99 | extensions: [".js", ".jsx", ".ts", ".tsx"],
100 | browser: true,
101 | }),
102 | commonjs(),
103 | typescript({
104 | tsconfig: "helper/tsconfig.json",
105 | sourceMap: true,
106 | inlineSources: !production
107 | }),
108 | ]
109 | };
110 |
111 | export default [svelteCompile, helperCompile];
112 |
--------------------------------------------------------------------------------
/app/src/App.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | {#if $dbg_store.debugger_enabled}
19 |
20 | {#if !$dbg_store.tab_connected}
21 |
22 | Reload the page to connect to the svelte reactive debugger
23 |
24 | {:else}
25 |
26 |
27 | {#if $pref_store.group_statements}
28 |
29 | {:else}
30 |
31 | {/if}
32 |
33 |
34 | {#if $ui_store.show_details}
35 |
36 | {/if}
37 |
38 |
39 | {/if}
40 | {:else}
41 |
42 |
43 | Svelte RD not detected, follow the steps described here to enable it
44 |
45 |
50 |
51 | {/if}
52 |
53 |
54 |
60 |
--------------------------------------------------------------------------------
/app/src/Components/Container.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/Components/Details.svelte:
--------------------------------------------------------------------------------
1 |
35 |
36 |
37 |
40 |
41 | {#if !start_state && !end_state}
42 |
No state was detected, here are the checks to make
43 |
44 | - Svelte dev mode is enabled
45 | - The preprocessor state option is enabled
46 |
47 | {/if}
48 | {#if diffHtml}
49 |
50 |
Changes
51 |
52 | {@html diffHtml}
53 |
54 |
55 | {/if}
56 | {#if endStateObj}
57 |
63 | {/if}
64 |
65 | {#if startEntries}
66 |
72 | {/if}
73 |
74 |
75 |
76 |
77 |
104 |
--------------------------------------------------------------------------------
/app/src/Components/Details/CollapsableValue.svelte:
--------------------------------------------------------------------------------
1 |
35 |
36 | (collapsed = !collapsed)}>
38 | {#if type == 'string'}
39 | {key}:
40 |
41 | {:else if value == null || value == undefined || value != value}
42 | {key}:
43 |
44 | {:else if type == 'number' || type == 'boolean'}
45 | {key}:
46 |
47 | {:else if Array.isArray(value)}
48 | {#if value.length}
49 |
50 | {key}:
51 | Array [{value.length}]
52 | {#if !collapsed}
53 |
54 | {#each value as v, key}
55 | dispatch('change', stringify(value, key, e.detail))} />
60 | {/each}
61 |
62 | {/if}
63 | {:else}
64 | {key}:
65 | Array []
66 | {/if}
67 | {:else if type == 'object'}
68 | {#if value.__isFunction}
69 |
70 | {key}:
71 | function {value.name || ''} ()
72 | {#if !collapsed}
73 | {value.source}
74 | {/if}
75 | {:else if value.__isSymbol}
76 | {key}:
77 | {value.name || 'Symbol()'}
78 | {:else if Object.keys(value).length}
79 |
80 | {key}:
81 | Object {…}
82 | {#if !collapsed}
83 |
84 | {#each Object.entries(value) as [key, v] (key)}
85 | dispatch('change', stringify(value, key, e.detail))} />
90 | {/each}
91 |
92 | {/if}
93 | {:else}
94 | {key}:
95 | Object { }
96 | {/if}
97 | {/if}
98 |
99 |
100 |
147 |
--------------------------------------------------------------------------------
/app/src/Components/Details/Editable.svelte:
--------------------------------------------------------------------------------
1 |
20 |
21 |
36 |
37 | {#if isEditing}
38 | e.key == 'Enter' && commit(e)}
42 | on:blur={commit} />
43 | {:else}
44 | (isEditing = !readOnly)}>
48 | {JSON.stringify(value)}
49 |
50 | {/if}
51 |
--------------------------------------------------------------------------------
/app/src/Components/Details/PropertyList.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
25 |
26 | {#if header}
27 | {header}
28 | {/if}
29 |
30 | {#if entries.length}
31 |
32 | {#each entries as { key, value } (key)}
33 |
37 | {/each}
38 |
39 | {:else}
40 | None
41 | {/if}
42 |
--------------------------------------------------------------------------------
/app/src/Components/Details/nodes/Collapse.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
48 |
49 | (collapsed = !collapsed)} />
54 |
--------------------------------------------------------------------------------
/app/src/Components/LeftPane.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/Components/List.svelte:
--------------------------------------------------------------------------------
1 |
57 |
58 | {#if $ev_store.length > 0}
59 |
60 |
61 |
62 |
63 | Statements ({items.length})
64 | |
65 | Duration |
66 | Start time
67 | |
68 |
69 | showDetails(ev, item)}>
73 |
74 |
76 | |
77 | {item.duration} |
78 | {time(item.start_time)} |
79 |
80 |
81 |
82 | {/if}
83 |
84 |
102 |
--------------------------------------------------------------------------------
/app/src/Components/Resize.svelte:
--------------------------------------------------------------------------------
1 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/Components/RightPane.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/Components/Statement.svelte:
--------------------------------------------------------------------------------
1 |
25 |
26 |
28 | {has_changes ? '*' : ''} {statement}
29 |
30 |
31 |
41 |
--------------------------------------------------------------------------------
/app/src/Components/Summary.svelte:
--------------------------------------------------------------------------------
1 |
49 |
50 | {#if statements.size > 0}
51 |
52 |
53 |
54 | Statements ({items.length}) |
55 | Count |
56 | Duration |
57 |
58 |
59 |
60 |
61 | |
62 |
63 | {#key item.count}
64 | {item.count}
65 | {/key}
66 | |
67 | {item.duration} |
68 |
69 |
70 |
71 | {/if}
72 |
--------------------------------------------------------------------------------
/app/src/Components/Toolbar.svelte:
--------------------------------------------------------------------------------
1 |
18 |
19 |
53 |
54 |
91 |
--------------------------------------------------------------------------------
/app/src/main.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import { devtools, runtime } from "chrome"
3 | import { stringify } from "javascript-stringify"
4 | import { get } from "svelte/store"
5 | import App from "./App.svelte"
6 | import { dbg_store, ev_store, pref_store, ui_store } from "./store"
7 | import type { ReactiveEvent } from "./types"
8 |
9 | const prefs = get(pref_store)
10 |
11 | function setDarkMode (theme) {
12 | if (theme == "dark") {
13 | document.body.classList.add("dark")
14 | } else {
15 | document.body.classList.remove("dark")
16 | }
17 | }
18 |
19 | setDarkMode(chrome.devtools.panels.themeName)
20 | if (devtools.panels.onThemeChanged) {
21 | devtools.panels.onThemeChanged.addListener(setDarkMode)
22 | }
23 |
24 | // Create a connection to the background page
25 | var backgroundPort = runtime.connect()
26 |
27 | setTimeout(() => {
28 | backgroundPort.postMessage({
29 | name : "init",
30 | tabId: devtools.inspectedWindow.tabId
31 | })
32 | }, 1000)
33 |
34 | backgroundPort.onMessage.addListener(function (request) {
35 | if (request.type === "SvelteReactiveEnable" || request.type === "SvelteReactiveStart") {
36 | dbg_store.setProp("tab_connected", true)
37 | dbg_store.setProp("debugger_enabled", true)
38 | }
39 |
40 | if (request.type === "SvelteReactiveEnd") {
41 | const now = Date.now()
42 | let reactiveEvent: ReactiveEvent = request.detail
43 | const duration = now - reactiveEvent.start_time
44 |
45 | let hasDiff = stringify(reactiveEvent.start_state) !== stringify(reactiveEvent.end_state)
46 |
47 | if (!dbg_store.getProp("paused")) {
48 | ev_store.insertEvent({
49 | ...reactiveEvent,
50 | duration,
51 | has_changes: hasDiff,
52 | })
53 | }
54 | }
55 |
56 | if (request.type === "Reload") {
57 | if (!prefs.preserve_log) {
58 | ui_store.setProp("show_details", false)
59 | ui_store.setProp("inspected_item", null)
60 | ev_store.clear()
61 | }
62 | }
63 | })
64 |
65 | new App({
66 | target: document.body
67 | })
68 |
--------------------------------------------------------------------------------
/app/src/scss/_icons.scss:
--------------------------------------------------------------------------------
1 | .icon {
2 | display: inline-block;
3 | width: 24px;
4 | height: 24px;
5 | border: none;
6 | background-color: transparent;
7 | background-image: url(/dist/icons/largeIcons.svg);
8 | cursor: pointer;
9 | outline: none;
10 | }
11 |
12 | .icon.medium {
13 | width: 16px;
14 | height: 16px;
15 | background-image: url(/dist/icons/mediumIcons.svg);
16 | }
17 |
18 | .icon.on {
19 | background-image: url(/dist/icons/largeIcons-on.svg);
20 | }
21 |
22 | .icon.delete {
23 | background-position: 0px 144px;
24 | width: 28px;
25 | height: 24px;
26 | }
27 |
28 | .icon.pause {
29 | background-position: -56px 73px;
30 | width: 28px;
31 | height: 24px;
32 | }
33 |
34 | .icon.resume {
35 | background-position: -28px 49px;
36 | width: 28px;
37 | height: 24px;
38 | }
39 |
40 | .icon.medium.clear {
41 | background-position: -32px 32px;
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/scss/_json-diff.scss:
--------------------------------------------------------------------------------
1 | .jsondiffpatch-unchanged {
2 | display: none;
3 | }
4 |
5 | .jsondiffpatch-delta {
6 | font-family: inherit !important;
7 | font-size: inherit !important;
8 | padding: 0 !important;
9 | }
10 |
11 | .jsondiffpatch-delta ul {
12 | white-space: nowrap;
13 | }
14 |
15 | .jsondiffpatch-delta pre {
16 | font-family: inherit !important;
17 | font-size: inherit !important;
18 | }
19 |
20 | .jsondiffpatch-added .jsondiffpatch-property-name,
21 | .jsondiffpatch-added .jsondiffpatch-value pre,
22 | .jsondiffpatch-modified .jsondiffpatch-right-value pre,
23 | .jsondiffpatch-textdiff-added {
24 | background: #8BC34A !important;
25 | color: #000;
26 | }
27 |
28 | .jsondiffpatch-deleted .jsondiffpatch-property-name,
29 | .jsondiffpatch-deleted pre,
30 | .jsondiffpatch-modified .jsondiffpatch-left-value pre,
31 | .jsondiffpatch-textdiff-deleted {
32 | background: #f44336 !important;
33 | color: #fff;
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/scss/_tablesort.scss:
--------------------------------------------------------------------------------
1 | table.tablesort {
2 | -table-layout: fixed;
3 | width: 100%;
4 | border-collapse: collapse;
5 | border-spacing: 0;
6 | border: 1px solid var(--border-color);
7 | }
8 |
9 | table.tablesort th, table.tablesort td {
10 | padding: 3px 5px;
11 | }
12 |
13 | table.tablesort th {
14 | text-align: left;
15 | white-space: pre;
16 | border-bottom: 1px solid var(--border-color);
17 | }
18 |
19 | table.tablesort th::after {
20 | content: ' ';
21 | white-space: pre;
22 | }
23 |
24 | table.tablesort th.ascending::after {
25 | content: ' \2191';
26 | }
27 |
28 | table.tablesort th.descending::after {
29 | content: ' \2193';
30 | }
31 |
32 | table.tablesort tbody tr:nth-child(even) {
33 | background-color: var(--bg-alt-color);
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/scss/_theme.scss:
--------------------------------------------------------------------------------
1 | $text-color-light: #3A3A3A;
2 | $bg-color-light: #FFF;
3 | $bg-alt-color-light: #f5f5f5;
4 | $border-color-light: #CDCDCD;
5 |
6 | $text-color-dark: #B1B1B3;
7 | $bg-color-dark: #2A2A2E;
8 | $bg-alt-color-dark: #242424;
9 | $border-color-dark: #3A3A3A;
10 |
11 | :root {
12 | --text-color: #{$text-color-light};
13 | --bg-color: #{$bg-color-light};
14 | --bg-alt-color: #{$bg-alt-color-light};
15 | --border-color: #{$border-color-light};
16 | }
17 |
18 | .dark {
19 | --text-color: #{$text-color-dark};
20 | --bg-color: #{$bg-color-dark};
21 | --bg-alt-color: #{$bg-alt-color-dark};
22 | --border-color: #{$border-color-dark};
23 | }
24 |
25 | $highlight-color: #FF3E00;
26 |
--------------------------------------------------------------------------------
/app/src/scss/global.scss:
--------------------------------------------------------------------------------
1 | @import "theme";
2 | @import "json-diff";
3 | @import "tablesort";
4 | @import "icons";
5 |
6 | html, body {
7 | margin: 0;
8 | padding: 0;
9 | height: 100%;
10 | }
11 |
12 | body {
13 | background: var(--bg-color);
14 | color: var(--text-color);
15 | }
16 |
17 | .noselect {
18 | user-select: none;
19 | }
20 |
21 | a {
22 | color: #2196f3;
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/store.ts:
--------------------------------------------------------------------------------
1 | import { writable as localStorageWritable } from "svelte-local-storage-store";
2 | import { get, writable } from "svelte/store";
3 | import type { DbgStore, EventStore, PrefStore, UiStore } from "./types";
4 |
5 | function createStore () {
6 | const { subscribe, update, set } = writable([]);
7 |
8 | return {
9 | subscribe,
10 |
11 | insertEvent (data) {
12 | return update(state => {
13 | state.push(data);
14 | return state;
15 | });
16 | },
17 |
18 | clear () {
19 | set([]);
20 | }
21 | };
22 | }
23 | export const ev_store: EventStore = createStore();
24 |
25 |
26 | function createPrefStore (): PrefStore {
27 | const { subscribe, update, set } = localStorageWritable("svrd_prefs", {
28 | group_statements: true,
29 | preserve_log : false,
30 | changes_only : false,
31 | filter_text : null,
32 | sort : {
33 | list : {
34 | name: "start_time",
35 | dir : "descending",
36 | },
37 | summary: {
38 | name: "count",
39 | dir : "descending",
40 | },
41 | },
42 | details_width : null,
43 | });
44 |
45 | return {
46 | subscribe,
47 | update,
48 | set,
49 |
50 | setPref (name, value) {
51 | return update(state => {
52 | state[name] = value;
53 | return state;
54 | });
55 | },
56 | };
57 | }
58 | export const pref_store: PrefStore = createPrefStore();
59 |
60 |
61 | function createDbgStore () {
62 | const { subscribe, set, update } = writable({
63 | tab_connected : false,
64 | debugger_enabled: false,
65 | paused : false,
66 | });
67 |
68 | let store = {
69 | subscribe,
70 | update,
71 | set,
72 |
73 | setProp (name, value) {
74 | return update(state => {
75 | state[name] = value;
76 | return state;
77 | });
78 | },
79 |
80 | getProp (name) {
81 | const store_data = get(store);
82 | return store_data[name];
83 | },
84 | };
85 | return store;
86 | }
87 | export const dbg_store: DbgStore = createDbgStore();
88 |
89 |
90 | function createUiStore (): UiStore {
91 | const { subscribe, update, set } = writable({
92 | show_details : false,
93 | inspected_item: null,
94 | });
95 |
96 | return {
97 | subscribe,
98 | set,
99 | update,
100 |
101 | setProp (name, value) {
102 | return update(state => {
103 | state[name] = value;
104 | return state;
105 | });
106 | },
107 | };
108 | }
109 | export const ui_store: UiStore = createUiStore();
110 |
--------------------------------------------------------------------------------
/app/src/types.ts:
--------------------------------------------------------------------------------
1 | import type { Readable, Writable } from "svelte/store";
2 |
3 | export interface ReactiveEvent {
4 | id: string;
5 | statement: string;
6 | filename: string;
7 | line: number;
8 | start_time: number;
9 | start_state: string;
10 | end_state: string;
11 | exec_id: string;
12 | duration: number;
13 | has_changes: boolean;
14 | }
15 |
16 | export interface EventStore extends Readable {
17 | insertEvent (ev: ReactiveEvent);
18 | clear ();
19 | }
20 |
21 | export type Sort = {
22 | name: "statement" | "count" | "duration" | "start_time";
23 | direction: "ascending" | "descending";
24 | };
25 |
26 | export interface PrefStoreData {
27 | group_statements: boolean;
28 | preserve_log: boolean;
29 | changes_only: boolean;
30 | filter_text: string;
31 | sort: {
32 | [name in "list" | "summary"]: Sort
33 | }
34 | details_width: number;
35 | }
36 |
37 | export interface PrefStore extends Writable {
38 | setPref (name: string, value: boolean);
39 | }
40 |
41 | export interface DbgStoreData {
42 | tab_connected: boolean;
43 | debugger_enabled: boolean;
44 | paused: boolean;
45 | }
46 |
47 | export interface DbgStore extends Writable {
48 | setProp (name: string, value: boolean);
49 | getProp (name: string);
50 | }
51 |
52 | export interface UiStoreData {
53 | show_details: boolean;
54 | inspected_item: ReactiveEvent;
55 | }
56 |
57 | export interface UiStore extends Writable {
58 | setProp (name: string, value: boolean);
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/utils.ts:
--------------------------------------------------------------------------------
1 | export function trigger (node: HTMLElement, event: string, data = null): boolean {
2 | const customEvent = new CustomEvent(event, { detail: data });
3 | return node.dispatchEvent(customEvent);
4 | }
5 |
--------------------------------------------------------------------------------
/app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/svelte/tsconfig.json",
3 |
4 | "include": ["src/**/*"],
5 | "exclude": ["node_modules/*", "__sapper__/*", "public/*"]
6 | }
7 |
--------------------------------------------------------------------------------
/extension/devtools/background.js:
--------------------------------------------------------------------------------
1 | const isChrome = typeof browser == "undefined";
2 |
3 | // background.js
4 | var connections = new Map();
5 |
6 | chrome.runtime.onConnect.addListener(function (port) {
7 |
8 | var extensionListener = function (message) {
9 |
10 | // The original connection event doesn't include the tab ID of the
11 | // DevTools page, so we need to send it explicitly.
12 | if (message.name == "init") {
13 | connections.set(message.tabId, port);
14 | return;
15 | }
16 |
17 | };
18 |
19 | // Listen to messages sent from the DevTools page
20 | port.onMessage.addListener(extensionListener);
21 |
22 | port.onDisconnect.addListener(function (port) {
23 | port.onMessage.removeListener(extensionListener);
24 |
25 | var tabs = Array.from(connections.keys());
26 | for (var i = 0, len = tabs.length; i < len; i++) {
27 | if (connections.get(tabs[i]) == port) {
28 | connections.delete(tabs[i]);
29 | break;
30 | }
31 | }
32 | });
33 | });
34 |
35 | // Receive message from content script and relay to the devTools page for the
36 | // current tab
37 | chrome.runtime.onMessage.addListener(function (request, sender) {
38 | console.log({request})
39 | // Messages from content scripts should have sender.tab set
40 | if (sender.tab) {
41 | var tabId = sender.tab.id;
42 | if (connections.has(tabId)) {
43 | console.log('post message', tabId, request);
44 | connections.get(tabId).postMessage(request);
45 | } else {
46 | console.log("Tab not found in connection list.");
47 | }
48 | } else {
49 | console.log("sender.tab not defined.");
50 | }
51 | return true;
52 | });
53 |
54 | chrome.tabs.onUpdated.addListener((tabId, changed) => {
55 | if (!connections.has(tabId) || changed.status != "loading" || (isChrome && changed.url)) {
56 | return;
57 | }
58 |
59 | chrome.scripting.executeScript({
60 | target: {tabId},
61 | files: ["/devtools/content.js"],
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/extension/devtools/content.js:
--------------------------------------------------------------------------------
1 | if (!window.svrdAttached) {
2 | document.addEventListener("SvelteReactiveStart", function (ev) {
3 | post("SvelteReactiveStart", ev.detail);
4 | });
5 |
6 | document.addEventListener("SvelteReactiveEnd", function (ev) {
7 | post("SvelteReactiveEnd", ev.detail);
8 | });
9 |
10 | document.addEventListener("SvelteReactiveEnable", function () {
11 | post("SvelteReactiveEnable");
12 | });
13 | }
14 |
15 | window.svrdAttached = true;
16 |
17 | var connected = true;
18 |
19 | function post(type, detail) {
20 | if (!connected) {
21 | // avoid extension context invaluidated errors
22 | return;
23 | }
24 | chrome.runtime.sendMessage({
25 | type,
26 | detail,
27 | });
28 | }
29 |
30 | post("Reload");
31 |
32 | var runtime_port = chrome.runtime.connect();
33 |
34 | runtime_port.onDisconnect.addListener(function () {
35 | connected = false;
36 | });
37 |
38 | function injectScript(file_path, tag) {
39 | var node = document.getElementsByTagName(tag)[0];
40 | if (!node) {
41 | return;
42 | }
43 | var script = document.createElement('script');
44 | script.setAttribute('type', 'text/javascript');
45 | script.setAttribute('src', file_path);
46 | node.appendChild(script);
47 | }
48 | injectScript(chrome.runtime.getURL('devtools/helper.js'), 'head');
49 |
--------------------------------------------------------------------------------
/extension/devtools/devtools.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/extension/devtools/devtools.js:
--------------------------------------------------------------------------------
1 | // Create a tab in the devtools area
2 | chrome.devtools.panels.create(
3 | "Svelte RD",
4 | chrome.devtools.panels.themeName == "dark"
5 | ? "/devtools/svelte-logo-dark.svg"
6 | : "/devtools/svelte-logo-light.svg",
7 | "/devtools/panel.html",
8 | function (panel) {
9 | }
10 | );
11 |
--------------------------------------------------------------------------------
/extension/devtools/panel.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/extension/devtools/svelte-logo-dark.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/extension/devtools/svelte-logo-light.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/extension/dist/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 |
3 | .DS_Store
4 |
--------------------------------------------------------------------------------
/extension/dist/icons/largeIcons-on.svg:
--------------------------------------------------------------------------------
1 |
331 |
--------------------------------------------------------------------------------
/extension/dist/icons/largeIcons.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/extension/dist/icons/mediumIcons.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/extension/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Svelte Reactive Debugger",
3 | "version": "0.8.3",
4 | "description": "Monitor svelte reactive statements",
5 | "icons": {
6 | "16": "icons/icon-16.png",
7 | "24": "icons/icon-24.png",
8 | "32": "icons/icon-32.png",
9 | "48": "icons/icon-48.png",
10 | "96": "icons/icon-96.png",
11 | "128": "icons/icon-128.png"
12 | },
13 | "devtools_page": "devtools/devtools.html",
14 | "web_accessible_resources": [
15 | {
16 | "resources": [
17 | "devtools/content.js",
18 | "devtools/helper.js"
19 | ],
20 | "matches": [
21 | "*://*/*"
22 | ]
23 | }
24 | ],
25 | "background": {
26 | "service_worker": "devtools/background.js"
27 | },
28 | "permissions": [
29 | "tabs",
30 | "scripting"
31 | ],
32 | "optional_host_permissions": [
33 | ""
34 | ],
35 | "manifest_version": 3
36 | }
37 |
--------------------------------------------------------------------------------
/images/chrome_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unlocomqx/svelte-reactive-debugger/a0d530f89b96a2c2f9d82d68f44cf79cecc88ff5/images/chrome_32x32.png
--------------------------------------------------------------------------------
/images/firefox_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unlocomqx/svelte-reactive-debugger/a0d530f89b96a2c2f9d82d68f44cf79cecc88ff5/images/firefox_32x32.png
--------------------------------------------------------------------------------
/images/list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unlocomqx/svelte-reactive-debugger/a0d530f89b96a2c2f9d82d68f44cf79cecc88ff5/images/list.png
--------------------------------------------------------------------------------
/images/summary.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unlocomqx/svelte-reactive-debugger/a0d530f89b96a2c2f9d82d68f44cf79cecc88ff5/images/summary.png
--------------------------------------------------------------------------------
/logo/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
92 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelte-reactive-debugger",
3 | "version": "0.0.0",
4 | "description": "Easily monitor svelte reactive statements",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "npm run dev --prefix app",
8 | "start": "npm run dev & web-ext run -s extension -u http://localhost:8888 -u about:debugging#/runtime/this-firefox && kill $!",
9 | "build:icons": "for size in 16 24 32 48 96 128; do if [ ! -e extension/icons/icon-$size.png -o logo/logo.svg -nt extension/icons/icon-$size.png ]; then svgexport logo/logo.svg extension/icons/icon-$size.png $size:; fi; done",
10 | "build": "npm run build --prefix app && npm run build:icons && web-ext build -s extension --overwrite-dest && git archive -o web-ext-artifacts/code.zip HEAD"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/unlocomqx/svelte-reactive-debugger.git"
15 | },
16 | "keywords": [
17 | "svelte",
18 | "extension"
19 | ],
20 | "author": "unlocomqx",
21 | "license": "ISC",
22 | "bugs": {
23 | "url": "https://github.com/unlocomqx/svelte-reactive-debugger/issues"
24 | },
25 | "homepage": "https://github.com/unlocomqx/svelte-reactive-debugger#readme",
26 | "devDependencies": {
27 | "@types/chrome": "^0.0.204",
28 | "sass": "^1.56.2",
29 | "svgexport": "^0.4.1",
30 | "typescript": "^4.1.3",
31 | "web-ext": "^5.4.1"
32 | },
33 | "dependencies": {
34 | "copy-to-clipboard": "^3.3.1",
35 | "javascript-stringify": "^2.0.1"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------