├── .nvmrc ├── .npmrc ├── src ├── routes │ ├── +layout.ts │ ├── +layout.svelte │ └── +page.svelte ├── app.css ├── lib │ ├── index.ts │ └── components │ │ ├── RenderScanObserver.css │ │ ├── Logo.svelte │ │ ├── RenderScan.svelte │ │ └── RenderScanObserver.svelte ├── app.d.ts └── app.html ├── .aidigestignore ├── .prettierignore ├── static ├── favicon.png └── og-image.jpg ├── .github ├── render-scan-demo.gif └── workflows │ └── build.yml ├── postcss.config.js ├── .prettierrc ├── .gitignore ├── tailwind.config.ts ├── vite.config.ts ├── tsconfig.json ├── svelte.config.js ├── eslint.config.js ├── LICENSE ├── README.md └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | 22 -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /src/routes/+layout.ts: -------------------------------------------------------------------------------- 1 | export const prerender = true; 2 | -------------------------------------------------------------------------------- /.aidigestignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .prettier* 3 | eslint* 4 | postcss* 5 | docs -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Package Managers 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khromov/svelte-render-scan/HEAD/static/favicon.png -------------------------------------------------------------------------------- /static/og-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khromov/svelte-render-scan/HEAD/static/og-image.jpg -------------------------------------------------------------------------------- /.github/render-scan-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khromov/svelte-render-scan/HEAD/.github/render-scan-demo.gif -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import 'tailwindcss/components'; 3 | @import 'tailwindcss/utilities'; 4 | -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | // Reexport your entry components here 2 | export { default as RenderScan } from './components/RenderScan.svelte'; 3 | export { default as RenderScanObserver } from './components/RenderScanObserver.svelte'; 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], 7 | "overrides": [ 8 | { 9 | "files": "*.svelte", 10 | "options": { 11 | "parser": "svelte" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://svelte.dev/docs/kit/types#app.d.ts 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Output 4 | .output 5 | .vercel 6 | .netlify 7 | .wrangler 8 | /.svelte-kit 9 | /build 10 | /dist 11 | 12 | # OS 13 | .DS_Store 14 | Thumbs.db 15 | 16 | # Env 17 | .env 18 | .env.* 19 | !.env.example 20 | !.env.test 21 | 22 | # Vite 23 | vite.config.js.timestamp-* 24 | vite.config.ts.timestamp-* 25 | codebase.md -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import forms from '@tailwindcss/forms'; 2 | import typography from '@tailwindcss/typography'; 3 | import type { Config } from 'tailwindcss'; 4 | 5 | export default { 6 | content: ['./src/**/*.{html,js,svelte,ts}'], 7 | 8 | theme: { 9 | extend: {} 10 | }, 11 | 12 | plugins: [typography, forms] 13 | } satisfies Config; 14 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | import { visualizer } from 'rollup-plugin-visualizer'; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | sveltekit(), 8 | visualizer({ 9 | emitFile: true, 10 | filename: 'stats.html' 11 | }) 12 | ], 13 | server: { 14 | fs: { 15 | allow: ['./package.json'] 16 | } 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "module": "NodeNext", 13 | "moduleResolution": "NodeNext", 14 | "declaration": true, 15 | "declarationMap": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-static'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://svelte.dev/docs/kit/integrations 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | kit: { 10 | // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. 11 | // If your environment is not supported, or you settled on a specific environment, switch out the adapter. 12 | // See https://svelte.dev/docs/kit/adapters for more information about adapters. 13 | adapter: adapter(), 14 | output: { 15 | bundleStrategy: 'single' 16 | } 17 | } 18 | }; 19 | 20 | export default config; 21 | -------------------------------------------------------------------------------- /src/lib/components/RenderScanObserver.css: -------------------------------------------------------------------------------- 1 | .render-perf__container { 2 | position: fixed; 3 | pointer-events: none; 4 | top: 0; 5 | left: 0; 6 | right: 0; 7 | bottom: 0; 8 | box-sizing: border-box; 9 | z-index: 1000000000; 10 | } 11 | 12 | .render-perf__box { 13 | box-sizing: border-box; 14 | font-family: sans-serif; 15 | display: block; 16 | position: fixed; 17 | pointer-events: none; 18 | border: 1px solid #2189b5; 19 | background-color: rgba(33, 137, 181, 0.05); 20 | transition: opacity 0.25s; 21 | } 22 | 23 | .render-perf__title { 24 | display: inline-block; 25 | min-width: max-content; 26 | box-sizing: content-box; 27 | font-size: 0.75rem; 28 | border: 1px solid #2189b5; 29 | border-bottom: none; 30 | padding: 0.125rem 0.5rem; 31 | color: white; 32 | background-color: #2189b5; 33 | position: relative; 34 | top: -1.5rem; 35 | } 36 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import prettier from 'eslint-config-prettier'; 2 | import js from '@eslint/js'; 3 | import { includeIgnoreFile } from '@eslint/compat'; 4 | import svelte from 'eslint-plugin-svelte'; 5 | import globals from 'globals'; 6 | import { fileURLToPath } from 'node:url'; 7 | import ts from 'typescript-eslint'; 8 | const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); 9 | 10 | export default ts.config( 11 | includeIgnoreFile(gitignorePath), 12 | js.configs.recommended, 13 | ...ts.configs.recommended, 14 | ...svelte.configs['flat/recommended'], 15 | prettier, 16 | ...svelte.configs['flat/prettier'], 17 | { 18 | languageOptions: { 19 | globals: { 20 | ...globals.browser, 21 | ...globals.node 22 | } 23 | } 24 | }, 25 | { 26 | files: ['**/*.svelte'], 27 | 28 | languageOptions: { 29 | parserOptions: { 30 | parser: ts.parser 31 | } 32 | } 33 | } 34 | ); 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 NullVoxPopuli, Stanislav Khromov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | Svelte Render Scan 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {@render children()} 31 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Deploy build to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: ['main'] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | pages: write 11 | id-token: write 12 | 13 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 14 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 15 | concurrency: 16 | group: 'pages' 17 | cancel-in-progress: false 18 | 19 | jobs: 20 | deploy: 21 | environment: 22 | name: github-pages 23 | url: ${{ steps.deployment.outputs.page_url }} 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | - name: Setup Pages 29 | uses: actions/configure-pages@v5 30 | - name: Use Node.js 31 | uses: actions/setup-node@v2 32 | with: 33 | node-version: 22 34 | - name: Install dependencies 35 | run: npm ci 36 | - name: Build the code 37 | run: npm run build 38 | - name: Upload artifact 39 | uses: actions/upload-pages-artifact@v3 40 | with: 41 | # Upload entire repository 42 | path: 'build' 43 | - name: Deploy to GitHub Pages 44 | id: deployment 45 | uses: actions/deploy-pages@v4 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Svelte Render Scan 2 | 3 | Visual debugging tool for Svelte applications. 4 | 5 | Svelte port of [render-scan](https://github.com/NullVoxPopuli/render-scan/). 6 | 7 | ![Screenshot of svelte-render-scan in action](.github/render-scan-demo.gif) 8 | 9 | ## Installation 10 | 11 | ```bash 12 | npm install -D svelte-render-scan 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```svelte 18 | 21 | 22 | 23 | ``` 24 | 25 | The overlay button appears in the bottom right corner and is enabled by default. You can disable it by clicking on it or setting the `initialEnabled` prop to `false`. 26 | 27 | ## Props 28 | 29 | - `initialEnabled` (default: `true`) - Start with render scanning enabled 30 | - `offsetLeft` (default: `0`) - Offset the button left from its default position 31 | - `hideIcon` (default: `false`) - Hide the render scan button while keeping functionality active 32 | - `callback` (default: `undefined`) - Optional user defined function that gets called once per valid mutation. Signature is `(mutation:MutationRecord)=>void;` 33 | - `duration` (defult: `1000`) - Adjust how long the render scan highlights remain on screen in ms 34 | 35 | ## Development 36 | 37 | Clone this repository and install dependencies: 38 | 39 | ```bash 40 | nvm use 41 | npm install 42 | npm run dev 43 | ``` 44 | 45 | ## License 46 | 47 | MIT 48 | -------------------------------------------------------------------------------- /src/lib/components/Logo.svelte: -------------------------------------------------------------------------------- 1 | 38 | 39 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-render-scan", 3 | "version": "1.1.0", 4 | "license": "MIT", 5 | "homepage": "https://khromov.github.io/svelte-render-scan/", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/khromov/svelte-render-scan.git" 9 | }, 10 | "scripts": { 11 | "dev": "vite dev", 12 | "build": "vite build && npm run prepack", 13 | "preview": "vite preview", 14 | "prepare": "svelte-kit sync || echo ''", 15 | "prepack": "svelte-kit sync && svelte-package && publint", 16 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 17 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 18 | "format": "prettier --write .", 19 | "lint": "prettier --check . && eslint ." 20 | }, 21 | "files": [ 22 | "dist", 23 | "!dist/**/*.test.*", 24 | "!dist/**/*.spec.*", 25 | "src/lib", 26 | "!src/lib/**/*.test.*", 27 | "!src/lib/**/*.spec.*" 28 | ], 29 | "sideEffects": [ 30 | "**/*.css" 31 | ], 32 | "svelte": "./dist/index.js", 33 | "types": "./dist/index.d.ts", 34 | "type": "module", 35 | "exports": { 36 | ".": { 37 | "types": "./dist/index.d.ts", 38 | "svelte": "./dist/index.js" 39 | } 40 | }, 41 | "peerDependencies": { 42 | "svelte": "^5.0.0" 43 | }, 44 | "devDependencies": { 45 | "@eslint/compat": "^1.2.5", 46 | "@eslint/js": "^9.18.0", 47 | "@sveltejs/adapter-static": "^3.0.8", 48 | "@sveltejs/kit": "^2.16.0", 49 | "@sveltejs/package": "^2.0.0", 50 | "@sveltejs/vite-plugin-svelte": "^5.0.0", 51 | "@tailwindcss/forms": "^0.5.10", 52 | "@tailwindcss/typography": "^0.5.16", 53 | "autoprefixer": "^10.4.20", 54 | "eslint": "^9.18.0", 55 | "eslint-config-prettier": "^10.0.1", 56 | "eslint-plugin-svelte": "^2.46.1", 57 | "globals": "^15.14.0", 58 | "prettier": "^3.4.2", 59 | "prettier-plugin-svelte": "^3.3.3", 60 | "prettier-plugin-tailwindcss": "^0.6.10", 61 | "publint": "^0.3.2", 62 | "rollup-plugin-visualizer": "^5.14.0", 63 | "svelte": "^5.0.0", 64 | "svelte-check": "^4.0.0", 65 | "tailwindcss": "^3.4.17", 66 | "typescript": "^5.0.0", 67 | "typescript-eslint": "^8.20.0", 68 | "vite": "^6.2.3" 69 | }, 70 | "keywords": [ 71 | "svelte", 72 | "library" 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /src/lib/components/RenderScan.svelte: -------------------------------------------------------------------------------- 1 | 42 | 43 | {#if enabled} 44 | 45 | {/if} 46 | 47 | 48 | {#if !hideIcon} 49 | 59 | {/if} 60 | 61 | 103 | -------------------------------------------------------------------------------- /src/lib/components/RenderScanObserver.svelte: -------------------------------------------------------------------------------- 1 | 222 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 93 | 94 |
95 |
96 | 97 |
103 | 108 |
109 | 110 |
113 |
116 | Svelte 117 |
118 |
122 | Render 123 |
124 |
127 | Scan 128 |
129 |
130 |

Visual debugging for Svelte apps

131 |

132 | Watch your components update in real-time. Perfect for debugging reactivity and performance 133 | issues. 134 |

135 | 136 | 137 |
138 |
139 |

See DOM Updates Live

140 |

141 | Try switching the highlight colors on this page below! 142 |

143 | 144 |
145 | 146 |
147 | 148 | 149 |
150 | 151 | 152 |
153 |
154 | 173 |
174 | 175 |
176 | {#each boxes as box (box)} 177 |
181 | {/each} 182 |
183 |
184 |
185 |
186 |
187 | 188 |
189 | {#each ['Track DOM Updates', 'Debug Re-renders', 'Visual Feedback'] as feature} 190 |
193 | 204 |

{feature}

205 |
206 | {/each} 207 |
208 |
209 |
210 | 211 |
212 |
213 |
214 |

Install

215 |
216 | {#each installers as i} 217 | 231 | {/each} 232 |
233 |
234 | {#each installers as i} 235 |
236 |
{i.cmd}
237 |
238 | {/each} 239 |
240 | 241 |
242 |

SvelteKit

243 |

Add to your root +layout.svelte file to enable the component in all pages:

244 |
{demoSvelteKitCode}
245 |
246 | 247 |
248 |

Vanilla Svelte

249 |
{demoCode}
250 |
251 | 252 |
253 |

Advanced Usage

254 | 255 |
256 |
257 |

Start Disabled

258 |

Start with render scanning disabled by default:

259 |
{advancedCode.disable}
260 |
261 | 262 |
263 |

Adjust Position

264 |

265 | Move the button left to avoid overlapping with other UI elements: 266 |

267 |
{advancedCode.offset}
268 |
269 | 270 |
271 |

Hide Icon

272 |

273 | Hide the render scan button while keeping functionality active: 274 |

275 |
{``}
276 |
277 | 278 |
279 |

Highlight Duration

280 |

281 | Adjust how long the render scan highlights remain on screen (default=1000): 282 |

283 |
{``}
284 |
285 | 286 |
287 |

Callback Function

288 |

289 | Optional user defined function that gets called once per valid mutation: 290 |

291 |
{callbackCode}
292 |
293 | 294 |
295 |

Combined Props

296 |

Use multiple props together:

297 |
{advancedCode.combined}
298 |
299 |
300 |
301 | 302 |

303 | GitHub 304 |

305 |

306 | © {new Date().getFullYear()} svelte-render-scan · Version {pkg.version} 307 |

308 | 309 | {#if mounted} 310 | console.debug('Mutation observed!', m.type)} duration={1000} /> 311 | {/if} 312 |
313 | 314 | 353 | --------------------------------------------------------------------------------