vite-devtools-svelte 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -6
- package/dist/index.mjs +90 -68
- package/dist/index.mjs.map +1 -1
- package/dist/template-injector.d.ts +42 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# vite-devtools-svelte
|
|
2
2
|
|
|
3
|
-
Svelte DevTools plugin for [Vite DevTools](https://github.com/
|
|
3
|
+
Svelte DevTools plugin for [Vite DevTools](https://github.com/vitejs/devtools). Provides 15 specialized panels for debugging, profiling, and inspecting Svelte/SvelteKit applications — all integrated directly into the Vite DevTools UI.
|
|
4
4
|
|
|
5
5
|
> **Status:** Early development. APIs may change.
|
|
6
6
|
|
|
@@ -135,25 +135,33 @@ Svelte DevTools plugin for [Vite DevTools](https://github.com/nicepkg/vite-devto
|
|
|
135
135
|
- **Vite** >= 8.0.0
|
|
136
136
|
- **Svelte** 5 (runes mode)
|
|
137
137
|
- **SvelteKit** (recommended, but not required for basic features)
|
|
138
|
+
- **[@vitejs/devtools](https://devtools.vite.dev/)** — the host UI this plugin's panels render inside
|
|
138
139
|
|
|
139
140
|
## Installation
|
|
140
141
|
|
|
142
|
+
`vite-devtools-svelte` is a panel provider — it needs the [`@vitejs/devtools`](https://devtools.vite.dev/) host plugin to render its UI. Install both:
|
|
143
|
+
|
|
141
144
|
```bash
|
|
142
|
-
npm install -D vite-devtools-svelte
|
|
145
|
+
npm install -D vite-devtools-svelte @vitejs/devtools
|
|
143
146
|
```
|
|
144
147
|
|
|
145
148
|
## Setup
|
|
146
149
|
|
|
147
|
-
|
|
150
|
+
Register **both** plugins in your `vite.config.ts`. `svelteDevtools()` **must come before `sveltekit()`** so that its transforms run before the Svelte compiler.
|
|
148
151
|
|
|
149
152
|
```ts
|
|
150
153
|
// vite.config.ts
|
|
151
|
-
import { svelteDevtools } from 'vite-devtools-svelte'
|
|
152
154
|
import { sveltekit } from '@sveltejs/kit/vite'
|
|
155
|
+
import { DevTools } from '@vitejs/devtools'
|
|
156
|
+
import { svelteDevtools } from 'vite-devtools-svelte'
|
|
153
157
|
import { defineConfig } from 'vite'
|
|
154
158
|
|
|
155
159
|
export default defineConfig({
|
|
156
|
-
plugins: [
|
|
160
|
+
plugins: [
|
|
161
|
+
svelteDevtools(), // must come before sveltekit()
|
|
162
|
+
DevTools(), // the @vitejs/devtools host — without it the panels have nowhere to render
|
|
163
|
+
sveltekit(),
|
|
164
|
+
],
|
|
157
165
|
})
|
|
158
166
|
```
|
|
159
167
|
|
|
@@ -163,7 +171,10 @@ Then start your dev server as usual:
|
|
|
163
171
|
npm run dev
|
|
164
172
|
```
|
|
165
173
|
|
|
166
|
-
The
|
|
174
|
+
Open your app in the browser. The Vite DevTools drawer appears at the bottom edge of the page — click the floating handle to open it, then switch to the **Svelte** tab to see the panels provided by this plugin.
|
|
175
|
+
|
|
176
|
+
> [!NOTE]
|
|
177
|
+
> If you only register `svelteDevtools()` without `DevTools()` from `@vitejs/devtools`, the dev server starts fine but **no DevTools UI appears** — there is no host for the panels to mount into.
|
|
167
178
|
|
|
168
179
|
## Options
|
|
169
180
|
|
package/dist/index.mjs
CHANGED
|
@@ -258,8 +258,8 @@ function N(e) {
|
|
|
258
258
|
}
|
|
259
259
|
//#endregion
|
|
260
260
|
//#region src/runtime.ts
|
|
261
|
-
var P = "virtual:svelte-devtools-runtime", F = "\0virtual:svelte-devtools-runtime", I = "\0svelte-devtools:wrapped-client", L = "\nimport * as __svelte_original from 'svelte/internal/client';\nexport * from 'svelte/internal/client';\n\nfunction __dt() {\n return typeof window !== 'undefined' ? window.__SVELTE_DEVTOOLS__ : null;\n}\n\n// Component ID stack (parallel to runtime._stack but local to wrapper)\nconst __idStack = [];\nfunction __currentId() {\n return __idStack.length > 0 ? __idStack[__idStack.length - 1] : null;\n}\n\n// Tracks the most recently created signal for type determination in tag()\nconst __pendingSignal = { ref: null, type: null };\n\n// --- Component Lifecycle ---\n//\n// Observer must not break the observed: every devtools side-effect below is\n// wrapped in try/catch so a bug or an out-of-shape runtime cannot tear down\n// the user's app. The original Svelte function is always called, and its\n// return value always returned, regardless of devtools failures.\n\nexport function push() {\n const result = __svelte_original.push.apply(null, arguments);\n try {\n const dt = __dt();\n if (dt) {\n const file = dt._pendingFile || 'Unknown';\n dt._pendingFile = null;\n const id = dt.register(file);\n __idStack.push(id);\n dt.startInit(id);\n try {\n __svelte_original.user_effect(() => {\n return () => { try { dt.unmount(id); } catch {} };\n });\n } catch {}\n }\n } catch {}\n return result;\n}\n\nexport function pop() {\n // Capture devtools errors but ALWAYS forward to the original pop so the\n // Svelte component stack stays balanced.\n try {\n const dt = __dt();\n if (dt && __idStack.length > 0) {\n const id = __idStack.pop();\n try { dt.endInit(id); } catch {}\n try { dt.registered(id); } catch {}\n }\n } catch {}\n return __svelte_original.pop.apply(null, arguments);\n}\n\n// --- Signal Creation (type markers) ---\n\nexport function state() {\n const signal = __svelte_original.state.apply(null, arguments);\n try {\n __pendingSignal.ref = signal;\n __pendingSignal.type = 'state';\n } catch {}\n return signal;\n}\n\nexport function derived() {\n const signal = __svelte_original.derived.apply(null, arguments);\n try {\n __pendingSignal.ref = signal;\n __pendingSignal.type = 'derived';\n } catch {}\n return signal;\n}\n\nexport function proxy() {\n const p = __svelte_original.proxy.apply(null, arguments);\n try {\n __pendingSignal.ref = p;\n __pendingSignal.type = 'proxy';\n } catch {}\n return p;\n}\n\n// --- Signal Tagging (Svelte dev mode) ---\n\nexport function tag(signal, name) {\n const result = __svelte_original.tag.apply(null, arguments);\n try {\n const dt = __dt();\n const cid = __currentId();\n if (dt && cid !== null) {\n const type = (__pendingSignal.ref === signal) ? __pendingSignal.type : 'state';\n if (type === 'derived') {\n dt.trackDerived(signal, name, cid);\n } else {\n dt.trackState(signal, name, cid);\n }\n }\n __pendingSignal.ref = null;\n __pendingSignal.type = null;\n } catch {}\n return result;\n}\n\nexport function tag_proxy(proxy, name) {\n const result = __svelte_original.tag_proxy.apply(null, arguments);\n try {\n const dt = __dt();\n const cid = __currentId();\n if (dt && cid !== null) {\n dt.trackProxy(proxy, name, cid);\n }\n __pendingSignal.ref = null;\n __pendingSignal.type = null;\n } catch {}\n return result;\n}\n\n// --- Effect Tracking ---\n\nexport function user_effect() {\n const result = __svelte_original.user_effect.apply(null, arguments);\n try {\n const dt = __dt();\n const cid = __currentId();\n if (dt && cid !== null) {\n dt._effectCounter = (dt._effectCounter || 0) + 1;\n // Track the effect OBJECT (not the callback). The Svelte runtime\n // populates result.deps with the signals this effect depends on,\n // which is what getReactiveGraph() reads to build edges.\n dt.trackEffect(result, 'effect_' + dt._effectCounter, cid);\n }\n } catch {}\n return result;\n}\n\nexport function user_pre_effect() {\n const result = __svelte_original.user_pre_effect.apply(null, arguments);\n try {\n const dt = __dt();\n const cid = __currentId();\n if (dt && cid !== null) {\n dt._effectCounter = (dt._effectCounter || 0) + 1;\n dt.trackEffect(result, 'effect_pre_' + dt._effectCounter, cid);\n }\n } catch {}\n return result;\n}\n", R = "\nif (typeof window !== 'undefined' && !window.__SVELTE_DEVTOOLS__) {\n const __SVELTE_DT = {\n _nextId: 0,\n _instances: new Map(),\n _stack: [],\n _pendingFile: null,\n _effectCounter: 0,\n _debounceTimer: null,\n _listeners: new Set(),\n\n // Phase 2: Profiling data\n _profiles: new Map(),\n _initStartTimes: new Map(),\n _profileDebounceTimer: null,\n\n // Phase 2: Reactive graph data\n _reactiveNodes: new Map(),\n _reactiveProxies: new Map(),\n\n // Phase 3: State timeline\n _stateTimeline: [],\n _stateSnapshots: new Map(),\n _timelineDebounceTimer: null,\n\n register(file) {\n const id = this._nextId++;\n const parentId = this._stack.length > 0 ? this._stack[this._stack.length - 1] : null;\n const name = file.split('/').pop()?.replace('.svelte', '') || 'Unknown';\n this._instances.set(id, { id, file, name, parentId, mounted: true, children: [] });\n if (parentId !== null) {\n const parent = this._instances.get(parentId);\n if (parent) parent.children.push(id);\n }\n this._stack.push(id);\n this._scheduleUpdate();\n return id;\n },\n\n registered(id) {\n const idx = this._stack.indexOf(id);\n if (idx !== -1) this._stack.splice(idx, 1);\n },\n\n mount(id) {\n const instance = this._instances.get(id);\n if (instance && !instance.mounted) {\n instance.mounted = true;\n this._scheduleUpdate();\n }\n },\n\n unmount(id) {\n const instance = this._instances.get(id);\n if (instance) {\n if (instance.parentId !== null) {\n const parent = this._instances.get(instance.parentId);\n if (parent) {\n parent.children = parent.children.filter(cid => cid !== id);\n }\n }\n this._removeChildren(id);\n this._cleanupReactiveNodes(id);\n this._instances.delete(id);\n }\n this._scheduleUpdate();\n },\n\n _cleanupReactiveNodes(componentId) {\n for (const [nodeId, entry] of this._reactiveNodes) {\n if (entry.meta.componentId === componentId) {\n this._reactiveNodes.delete(nodeId);\n }\n }\n },\n\n _removeChildren(parentId) {\n const parent = this._instances.get(parentId);\n if (!parent) return;\n for (const childId of [...parent.children]) {\n this._removeChildren(childId);\n this._cleanupReactiveNodes(childId);\n this._instances.delete(childId);\n }\n },\n\n _scheduleUpdate() {\n if (this._debounceTimer) clearTimeout(this._debounceTimer);\n this._debounceTimer = setTimeout(() => {\n this._sendUpdate();\n }, 100);\n },\n\n _sendUpdate() {\n const components = [];\n for (const [, instance] of this._instances) {\n if (instance.mounted) {\n components.push({\n id: instance.id,\n file: instance.file,\n name: instance.name,\n parentId: instance.parentId,\n mounted: instance.mounted,\n });\n }\n }\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:components', { components });\n }\n },\n\n getTree() {\n const components = [];\n for (const [, instance] of this._instances) {\n if (instance.mounted) {\n components.push({\n id: instance.id,\n file: instance.file,\n name: instance.name,\n parentId: instance.parentId,\n mounted: instance.mounted,\n });\n }\n }\n return components;\n },\n\n // --- Phase 2: Render Profiling ---\n\n startInit(id) {\n this._initStartTimes.set(id, performance.now());\n },\n\n endInit(id) {\n const start = this._initStartTimes.get(id);\n if (start === undefined) return;\n const initTime = performance.now() - start;\n this._initStartTimes.delete(id);\n const instance = this._instances.get(id);\n if (!instance) return;\n this._profiles.set(id, {\n componentId: id,\n file: instance.file,\n name: instance.name,\n initTime,\n renderCount: 0,\n totalRenderTime: 0,\n lastRenderTime: 0,\n lastRenderAt: Date.now(),\n });\n this._scheduleProfileUpdate();\n },\n\n recordRender(id) {\n const profile = this._profiles.get(id);\n if (!profile) {\n const instance = this._instances.get(id);\n if (!instance) return;\n this._profiles.set(id, {\n componentId: id,\n file: instance.file,\n name: instance.name,\n initTime: 0,\n renderCount: 1,\n totalRenderTime: 0,\n lastRenderTime: 0,\n lastRenderAt: Date.now(),\n });\n } else {\n profile.renderCount++;\n profile.lastRenderAt = Date.now();\n }\n this._scheduleProfileUpdate();\n },\n\n recordRenderTime(id, duration) {\n const profile = this._profiles.get(id);\n if (profile) {\n profile.totalRenderTime += duration;\n profile.lastRenderTime = duration;\n }\n },\n\n _scheduleProfileUpdate() {\n if (this._profileDebounceTimer) clearTimeout(this._profileDebounceTimer);\n this._profileDebounceTimer = setTimeout(() => {\n this._sendProfileUpdate();\n }, 500);\n },\n\n _sendProfileUpdate() {\n const profiles = Array.from(this._profiles.values());\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:profiles', { profiles });\n }\n },\n\n getProfiles() {\n return Array.from(this._profiles.values());\n },\n\n resetProfiles() {\n this._profiles.clear();\n this._scheduleProfileUpdate();\n },\n\n // --- Phase 2: Reactive Graph Tracking ---\n\n trackState(signal, name, componentId) {\n const instance = this._instances.get(componentId);\n const nodeId = componentId + ':' + name;\n this._reactiveNodes.set(nodeId, {\n signal: new WeakRef(signal),\n meta: { id: nodeId, type: 'state', name, componentId, componentFile: instance ? instance.file : '' }\n });\n },\n\n trackProxy(proxy, name, componentId) {\n const instance = this._instances.get(componentId);\n const nodeId = componentId + ':' + name;\n this._reactiveProxies.set(nodeId, new WeakRef(proxy));\n this._reactiveNodes.set(nodeId, {\n signal: new WeakRef({ v: '(proxy)', _isProxy: true }),\n meta: { id: nodeId, type: 'state', name, componentId, componentFile: instance ? instance.file : '' }\n });\n },\n\n trackDerived(signal, name, componentId) {\n const instance = this._instances.get(componentId);\n const nodeId = componentId + ':' + name;\n this._reactiveNodes.set(nodeId, {\n signal: new WeakRef(signal),\n meta: { id: nodeId, type: 'derived', name, componentId, componentFile: instance ? instance.file : '' }\n });\n },\n\n trackEffect(effect, name, componentId) {\n const instance = this._instances.get(componentId);\n const nodeId = componentId + ':' + name;\n this._reactiveNodes.set(nodeId, {\n signal: new WeakRef(effect || { v: undefined, _isEffect: true }),\n meta: { id: nodeId, type: 'effect', name, componentId, componentFile: instance ? instance.file : '' }\n });\n return effect;\n },\n\n getReactiveGraph() {\n const nodes = [];\n const edges = [];\n const signalToId = new Map();\n\n for (const [nodeId, entry] of this._reactiveNodes) {\n const signal = entry.signal.deref();\n if (!signal || !this._instances.has(entry.meta.componentId)) {\n this._reactiveNodes.delete(nodeId);\n continue;\n }\n signalToId.set(signal, nodeId);\n const node = { ...entry.meta };\n if (signal._isProxy) {\n const proxyRef = this._reactiveProxies.get(nodeId);\n const proxy = proxyRef?.deref();\n if (proxy) {\n try {\n node.value = Array.isArray(proxy) ? '[' + proxy.length + ']' : '{' + Object.keys(proxy).length + '}';\n } catch { node.value = '(proxy)'; }\n }\n } else if (node.type !== 'effect' && signal.v !== undefined && typeof signal.v !== 'symbol') {\n try {\n const v = signal.v;\n if (typeof v === 'number' || typeof v === 'string' || typeof v === 'boolean' || v === null) {\n node.value = v;\n } else {\n node.value = '(object)';\n }\n } catch { /* ignore */ }\n }\n nodes.push(node);\n }\n\n // Map proxy internal signals to their proxy node ID\n for (const [nodeId, ref] of this._reactiveProxies) {\n const proxy = ref.deref();\n if (!proxy) { this._reactiveProxies.delete(nodeId); continue; }\n const meta = this._reactiveNodes.get(nodeId)?.meta;\n if (!meta || !this._instances.has(meta.componentId)) { this._reactiveProxies.delete(nodeId); continue; }\n }\n\n // Build edges from deps\n const edgeSet = new Set();\n for (const [nodeId, entry] of this._reactiveNodes) {\n const signal = entry.signal.deref();\n if (!signal) continue;\n if (!signal.deps) continue;\n\n const visited = new Set();\n const queue = [...signal.deps];\n while (queue.length > 0) {\n const dep = queue.shift();\n if (!dep || visited.has(dep)) continue;\n visited.add(dep);\n const depId = signalToId.get(dep);\n if (depId) {\n const key = depId + '>' + nodeId;\n if (depId !== nodeId && !edgeSet.has(key)) {\n edgeSet.add(key);\n edges.push({ from: depId, to: nodeId });\n }\n } else if (dep.deps) {\n for (const d of dep.deps) queue.push(d);\n }\n }\n }\n\n return { nodes, edges };\n },\n\n sendReactiveGraph() {\n const graph = this.getReactiveGraph();\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:reactive-graph', graph);\n }\n },\n\n // --- Phase 3: State Timeline ---\n\n _pollStateValues() {\n for (const [nodeId, entry] of this._reactiveNodes) {\n if (entry.meta.type !== 'state') continue;\n if (!this._instances.has(entry.meta.componentId)) {\n this._reactiveNodes.delete(nodeId);\n continue;\n }\n const signal = entry.signal.deref();\n if (!signal || signal.v === undefined) continue;\n try {\n const currentVal = signal.v;\n const prev = this._stateSnapshots.get(nodeId);\n if (prev !== undefined && currentVal === prev) continue;\n const isPrimitive = currentVal === null || typeof currentVal !== 'object';\n let newSnapshot;\n if (isPrimitive) {\n if (prev !== undefined && prev === currentVal) continue;\n newSnapshot = currentVal;\n } else {\n const currStr = JSON.stringify(currentVal);\n const prevStr = this._stateSnapshotStrs?.get(nodeId);\n if (prevStr === currStr) continue;\n newSnapshot = JSON.parse(currStr);\n if (!this._stateSnapshotStrs) this._stateSnapshotStrs = new Map();\n this._stateSnapshotStrs.set(nodeId, currStr);\n }\n if (this._stateTimeline.length >= 500) this._stateTimeline.splice(0, this._stateTimeline.length - 499);\n this._stateTimeline.push({\n id: nodeId,\n name: entry.meta.name,\n componentFile: entry.meta.componentFile,\n oldValue: prev !== undefined ? prev : null,\n newValue: newSnapshot,\n timestamp: Date.now(),\n });\n this._stateSnapshots.set(nodeId, newSnapshot);\n this._scheduleTimelineUpdate();\n } catch { /* ignore non-serializable */ }\n }\n },\n\n _scheduleTimelineUpdate() {\n if (this._timelineDebounceTimer) clearTimeout(this._timelineDebounceTimer);\n this._timelineDebounceTimer = setTimeout(() => {\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:state-timeline', { changes: this._stateTimeline });\n }\n }, 300);\n },\n\n getStateTimeline() {\n return this._stateTimeline;\n },\n\n clearStateTimeline() {\n this._stateTimeline = [];\n this._scheduleTimelineUpdate();\n },\n\n // --- FPS Monitoring ---\n // Uses requestAnimationFrame to measure frame rate with minimal overhead.\n // Only stores timestamps - no allocations per frame beyond a single array push.\n _fpsFrameTimes: [],\n\n _fpsLoop() {\n this._fpsFrameTimes.push(performance.now());\n requestAnimationFrame(() => this._fpsLoop());\n },\n\n _sampleFps() {\n const now = performance.now();\n // Remove frame timestamps older than 1 second\n const cutoff = now - 1000;\n let i = 0;\n while (i < this._fpsFrameTimes.length && this._fpsFrameTimes[i] < cutoff) i++;\n if (i > 0) this._fpsFrameTimes.splice(0, i);\n const fps = this._fpsFrameTimes.length;\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:fps', { timestamp: Date.now(), fps });\n }\n }\n };\n\n window.__SVELTE_DEVTOOLS__ = __SVELTE_DT;\n\n // Poll state values for timeline\n setInterval(() => { __SVELTE_DT._pollStateValues(); }, 200);\n\n // FPS monitoring\n requestAnimationFrame(() => __SVELTE_DT._fpsLoop());\n setInterval(() => __SVELTE_DT._sampleFps(), 500);\n\n // Phase 3: Capture runtime errors\n window.addEventListener('error', (event) => {\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:runtime-error', {\n message: event.message,\n file: event.filename,\n line: event.lineno,\n column: event.colno,\n stack: event.error?.stack || '',\n timestamp: Date.now(),\n });\n }\n });\n window.addEventListener('unhandledrejection', (event) => {\n if (import.meta.hot) {\n const reason = event.reason;\n import.meta.hot.send('svelte-devtools:runtime-error', {\n message: reason?.message || String(reason),\n stack: reason?.stack || '',\n timestamp: Date.now(),\n });\n }\n });\n\n // Listen for reactive graph requests from server\n if (import.meta.hot) {\n import.meta.hot.on('svelte-devtools:request-reactive-graph', () => {\n __SVELTE_DT.sendReactiveGraph();\n });\n import.meta.hot.on('svelte-devtools:request-state-timeline', () => {\n import.meta.hot.send('svelte-devtools:state-timeline', { changes: __SVELTE_DT._stateTimeline });\n });\n import.meta.hot.on('svelte-devtools:clear-state-timeline', () => {\n __SVELTE_DT.clearStateTimeline();\n });\n }\n}\n", z = 1, B = 5, V = 1, H = 30;
|
|
262
|
-
function
|
|
261
|
+
var P = "virtual:svelte-devtools-runtime", ee = "\0virtual:svelte-devtools-runtime", F = "\0svelte-devtools:wrapped-client", I = "\nimport * as __svelte_original from 'svelte/internal/client';\nexport * from 'svelte/internal/client';\n\nfunction __dt() {\n return typeof window !== 'undefined' ? window.__SVELTE_DEVTOOLS__ : null;\n}\n\n// Component ID stack (parallel to runtime._stack but local to wrapper)\nconst __idStack = [];\nfunction __currentId() {\n return __idStack.length > 0 ? __idStack[__idStack.length - 1] : null;\n}\n\n// Tracks the most recently created signal for type determination in tag()\nconst __pendingSignal = { ref: null, type: null };\n\n// --- Component Lifecycle ---\n//\n// Observer must not break the observed: every devtools side-effect below is\n// wrapped in try/catch so a bug or an out-of-shape runtime cannot tear down\n// the user's app. The original Svelte function is always called, and its\n// return value always returned, regardless of devtools failures.\n\nexport function push() {\n const result = __svelte_original.push.apply(null, arguments);\n try {\n const dt = __dt();\n if (dt) {\n const file = dt._pendingFile || 'Unknown';\n dt._pendingFile = null;\n const id = dt.register(file);\n __idStack.push(id);\n dt.startInit(id);\n try {\n __svelte_original.user_effect(() => {\n return () => { try { dt.unmount(id); } catch {} };\n });\n } catch {}\n }\n } catch {}\n return result;\n}\n\nexport function pop() {\n // Capture devtools errors but ALWAYS forward to the original pop so the\n // Svelte component stack stays balanced.\n try {\n const dt = __dt();\n if (dt && __idStack.length > 0) {\n const id = __idStack.pop();\n try { dt.endInit(id); } catch {}\n try { dt.registered(id); } catch {}\n }\n } catch {}\n return __svelte_original.pop.apply(null, arguments);\n}\n\n// --- Signal Creation (type markers) ---\n\nexport function state() {\n const signal = __svelte_original.state.apply(null, arguments);\n try {\n __pendingSignal.ref = signal;\n __pendingSignal.type = 'state';\n } catch {}\n return signal;\n}\n\nexport function derived() {\n const signal = __svelte_original.derived.apply(null, arguments);\n try {\n __pendingSignal.ref = signal;\n __pendingSignal.type = 'derived';\n } catch {}\n return signal;\n}\n\nexport function proxy() {\n const p = __svelte_original.proxy.apply(null, arguments);\n try {\n __pendingSignal.ref = p;\n __pendingSignal.type = 'proxy';\n } catch {}\n return p;\n}\n\n// --- Signal Tagging (Svelte dev mode) ---\n\nexport function tag(signal, name) {\n const result = __svelte_original.tag.apply(null, arguments);\n try {\n const dt = __dt();\n const cid = __currentId();\n if (dt && cid !== null) {\n const type = (__pendingSignal.ref === signal) ? __pendingSignal.type : 'state';\n if (type === 'derived') {\n dt.trackDerived(signal, name, cid);\n } else {\n dt.trackState(signal, name, cid);\n }\n }\n __pendingSignal.ref = null;\n __pendingSignal.type = null;\n } catch {}\n return result;\n}\n\nexport function tag_proxy(proxy, name) {\n const result = __svelte_original.tag_proxy.apply(null, arguments);\n try {\n const dt = __dt();\n const cid = __currentId();\n if (dt && cid !== null) {\n dt.trackProxy(proxy, name, cid);\n }\n __pendingSignal.ref = null;\n __pendingSignal.type = null;\n } catch {}\n return result;\n}\n\n// --- Effect Tracking ---\n\nexport function user_effect() {\n const result = __svelte_original.user_effect.apply(null, arguments);\n try {\n const dt = __dt();\n const cid = __currentId();\n if (dt && cid !== null) {\n dt._effectCounter = (dt._effectCounter || 0) + 1;\n // Track the effect OBJECT (not the callback). The Svelte runtime\n // populates result.deps with the signals this effect depends on,\n // which is what getReactiveGraph() reads to build edges.\n dt.trackEffect(result, 'effect_' + dt._effectCounter, cid);\n }\n } catch {}\n return result;\n}\n\nexport function user_pre_effect() {\n const result = __svelte_original.user_pre_effect.apply(null, arguments);\n try {\n const dt = __dt();\n const cid = __currentId();\n if (dt && cid !== null) {\n dt._effectCounter = (dt._effectCounter || 0) + 1;\n dt.trackEffect(result, 'effect_pre_' + dt._effectCounter, cid);\n }\n } catch {}\n return result;\n}\n", L = "\nif (typeof window !== 'undefined' && !window.__SVELTE_DEVTOOLS__) {\n const __SVELTE_DT = {\n _nextId: 0,\n _instances: new Map(),\n _stack: [],\n _pendingFile: null,\n _effectCounter: 0,\n _debounceTimer: null,\n _listeners: new Set(),\n\n // Phase 2: Profiling data\n _profiles: new Map(),\n _initStartTimes: new Map(),\n _profileDebounceTimer: null,\n\n // Phase 2: Reactive graph data\n _reactiveNodes: new Map(),\n _reactiveProxies: new Map(),\n\n // Phase 3: State timeline\n _stateTimeline: [],\n _stateSnapshots: new Map(),\n _timelineDebounceTimer: null,\n\n register(file) {\n const id = this._nextId++;\n const parentId = this._stack.length > 0 ? this._stack[this._stack.length - 1] : null;\n const name = file.split('/').pop()?.replace('.svelte', '') || 'Unknown';\n this._instances.set(id, { id, file, name, parentId, mounted: true, children: [] });\n if (parentId !== null) {\n const parent = this._instances.get(parentId);\n if (parent) parent.children.push(id);\n }\n this._stack.push(id);\n this._scheduleUpdate();\n return id;\n },\n\n registered(id) {\n const idx = this._stack.indexOf(id);\n if (idx !== -1) this._stack.splice(idx, 1);\n },\n\n mount(id) {\n const instance = this._instances.get(id);\n if (instance && !instance.mounted) {\n instance.mounted = true;\n this._scheduleUpdate();\n }\n },\n\n unmount(id) {\n const instance = this._instances.get(id);\n if (instance) {\n if (instance.parentId !== null) {\n const parent = this._instances.get(instance.parentId);\n if (parent) {\n parent.children = parent.children.filter(cid => cid !== id);\n }\n }\n this._removeChildren(id);\n this._cleanupReactiveNodes(id);\n this._instances.delete(id);\n }\n this._scheduleUpdate();\n },\n\n _cleanupReactiveNodes(componentId) {\n for (const [nodeId, entry] of this._reactiveNodes) {\n if (entry.meta.componentId === componentId) {\n this._reactiveNodes.delete(nodeId);\n }\n }\n },\n\n _removeChildren(parentId) {\n const parent = this._instances.get(parentId);\n if (!parent) return;\n for (const childId of [...parent.children]) {\n this._removeChildren(childId);\n this._cleanupReactiveNodes(childId);\n this._instances.delete(childId);\n }\n },\n\n _scheduleUpdate() {\n if (this._debounceTimer) clearTimeout(this._debounceTimer);\n this._debounceTimer = setTimeout(() => {\n this._sendUpdate();\n }, 100);\n },\n\n _sendUpdate() {\n const components = [];\n for (const [, instance] of this._instances) {\n if (instance.mounted) {\n components.push({\n id: instance.id,\n file: instance.file,\n name: instance.name,\n parentId: instance.parentId,\n mounted: instance.mounted,\n });\n }\n }\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:components', { components });\n }\n },\n\n getTree() {\n const components = [];\n for (const [, instance] of this._instances) {\n if (instance.mounted) {\n components.push({\n id: instance.id,\n file: instance.file,\n name: instance.name,\n parentId: instance.parentId,\n mounted: instance.mounted,\n });\n }\n }\n return components;\n },\n\n // --- Phase 2: Render Profiling ---\n\n startInit(id) {\n this._initStartTimes.set(id, performance.now());\n },\n\n endInit(id) {\n const start = this._initStartTimes.get(id);\n if (start === undefined) return;\n const initTime = performance.now() - start;\n this._initStartTimes.delete(id);\n const instance = this._instances.get(id);\n if (!instance) return;\n this._profiles.set(id, {\n componentId: id,\n file: instance.file,\n name: instance.name,\n initTime,\n renderCount: 0,\n totalRenderTime: 0,\n lastRenderTime: 0,\n lastRenderAt: Date.now(),\n });\n this._scheduleProfileUpdate();\n },\n\n recordRender(id) {\n const profile = this._profiles.get(id);\n if (!profile) {\n const instance = this._instances.get(id);\n if (!instance) return;\n this._profiles.set(id, {\n componentId: id,\n file: instance.file,\n name: instance.name,\n initTime: 0,\n renderCount: 1,\n totalRenderTime: 0,\n lastRenderTime: 0,\n lastRenderAt: Date.now(),\n });\n } else {\n profile.renderCount++;\n profile.lastRenderAt = Date.now();\n }\n this._scheduleProfileUpdate();\n },\n\n recordRenderTime(id, duration) {\n const profile = this._profiles.get(id);\n if (profile) {\n profile.totalRenderTime += duration;\n profile.lastRenderTime = duration;\n }\n },\n\n _scheduleProfileUpdate() {\n if (this._profileDebounceTimer) clearTimeout(this._profileDebounceTimer);\n this._profileDebounceTimer = setTimeout(() => {\n this._sendProfileUpdate();\n }, 500);\n },\n\n _sendProfileUpdate() {\n const profiles = Array.from(this._profiles.values());\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:profiles', { profiles });\n }\n },\n\n getProfiles() {\n return Array.from(this._profiles.values());\n },\n\n resetProfiles() {\n this._profiles.clear();\n this._scheduleProfileUpdate();\n },\n\n // --- Phase 2: Reactive Graph Tracking ---\n\n trackState(signal, name, componentId) {\n const instance = this._instances.get(componentId);\n const nodeId = componentId + ':' + name;\n this._reactiveNodes.set(nodeId, {\n signal: new WeakRef(signal),\n meta: { id: nodeId, type: 'state', name, componentId, componentFile: instance ? instance.file : '' }\n });\n },\n\n trackProxy(proxy, name, componentId) {\n const instance = this._instances.get(componentId);\n const nodeId = componentId + ':' + name;\n this._reactiveProxies.set(nodeId, new WeakRef(proxy));\n this._reactiveNodes.set(nodeId, {\n signal: new WeakRef({ v: '(proxy)', _isProxy: true }),\n meta: { id: nodeId, type: 'state', name, componentId, componentFile: instance ? instance.file : '' }\n });\n },\n\n trackDerived(signal, name, componentId) {\n const instance = this._instances.get(componentId);\n const nodeId = componentId + ':' + name;\n this._reactiveNodes.set(nodeId, {\n signal: new WeakRef(signal),\n meta: { id: nodeId, type: 'derived', name, componentId, componentFile: instance ? instance.file : '' }\n });\n },\n\n trackEffect(effect, name, componentId) {\n const instance = this._instances.get(componentId);\n const nodeId = componentId + ':' + name;\n this._reactiveNodes.set(nodeId, {\n signal: new WeakRef(effect || { v: undefined, _isEffect: true }),\n meta: { id: nodeId, type: 'effect', name, componentId, componentFile: instance ? instance.file : '' }\n });\n return effect;\n },\n\n getReactiveGraph() {\n const nodes = [];\n const edges = [];\n const signalToId = new Map();\n\n for (const [nodeId, entry] of this._reactiveNodes) {\n const signal = entry.signal.deref();\n if (!signal || !this._instances.has(entry.meta.componentId)) {\n this._reactiveNodes.delete(nodeId);\n continue;\n }\n signalToId.set(signal, nodeId);\n const node = { ...entry.meta };\n if (signal._isProxy) {\n const proxyRef = this._reactiveProxies.get(nodeId);\n const proxy = proxyRef?.deref();\n if (proxy) {\n try {\n node.value = Array.isArray(proxy) ? '[' + proxy.length + ']' : '{' + Object.keys(proxy).length + '}';\n } catch { node.value = '(proxy)'; }\n }\n } else if (node.type !== 'effect' && signal.v !== undefined && typeof signal.v !== 'symbol') {\n try {\n const v = signal.v;\n if (typeof v === 'number' || typeof v === 'string' || typeof v === 'boolean' || v === null) {\n node.value = v;\n } else {\n node.value = '(object)';\n }\n } catch { /* ignore */ }\n }\n nodes.push(node);\n }\n\n // Map proxy internal signals to their proxy node ID\n for (const [nodeId, ref] of this._reactiveProxies) {\n const proxy = ref.deref();\n if (!proxy) { this._reactiveProxies.delete(nodeId); continue; }\n const meta = this._reactiveNodes.get(nodeId)?.meta;\n if (!meta || !this._instances.has(meta.componentId)) { this._reactiveProxies.delete(nodeId); continue; }\n }\n\n // Build edges from deps\n const edgeSet = new Set();\n for (const [nodeId, entry] of this._reactiveNodes) {\n const signal = entry.signal.deref();\n if (!signal) continue;\n if (!signal.deps) continue;\n\n const visited = new Set();\n const queue = [...signal.deps];\n while (queue.length > 0) {\n const dep = queue.shift();\n if (!dep || visited.has(dep)) continue;\n visited.add(dep);\n const depId = signalToId.get(dep);\n if (depId) {\n const key = depId + '>' + nodeId;\n if (depId !== nodeId && !edgeSet.has(key)) {\n edgeSet.add(key);\n edges.push({ from: depId, to: nodeId });\n }\n } else if (dep.deps) {\n for (const d of dep.deps) queue.push(d);\n }\n }\n }\n\n return { nodes, edges };\n },\n\n sendReactiveGraph() {\n const graph = this.getReactiveGraph();\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:reactive-graph', graph);\n }\n },\n\n // --- Phase 3: State Timeline ---\n\n _pollStateValues() {\n for (const [nodeId, entry] of this._reactiveNodes) {\n if (entry.meta.type !== 'state') continue;\n if (!this._instances.has(entry.meta.componentId)) {\n this._reactiveNodes.delete(nodeId);\n continue;\n }\n const signal = entry.signal.deref();\n if (!signal || signal.v === undefined) continue;\n try {\n const currentVal = signal.v;\n const prev = this._stateSnapshots.get(nodeId);\n if (prev !== undefined && currentVal === prev) continue;\n const isPrimitive = currentVal === null || typeof currentVal !== 'object';\n let newSnapshot;\n if (isPrimitive) {\n if (prev !== undefined && prev === currentVal) continue;\n newSnapshot = currentVal;\n } else {\n const currStr = JSON.stringify(currentVal);\n const prevStr = this._stateSnapshotStrs?.get(nodeId);\n if (prevStr === currStr) continue;\n newSnapshot = JSON.parse(currStr);\n if (!this._stateSnapshotStrs) this._stateSnapshotStrs = new Map();\n this._stateSnapshotStrs.set(nodeId, currStr);\n }\n if (this._stateTimeline.length >= 500) this._stateTimeline.splice(0, this._stateTimeline.length - 499);\n this._stateTimeline.push({\n id: nodeId,\n name: entry.meta.name,\n componentFile: entry.meta.componentFile,\n oldValue: prev !== undefined ? prev : null,\n newValue: newSnapshot,\n timestamp: Date.now(),\n });\n this._stateSnapshots.set(nodeId, newSnapshot);\n this._scheduleTimelineUpdate();\n } catch { /* ignore non-serializable */ }\n }\n },\n\n _scheduleTimelineUpdate() {\n if (this._timelineDebounceTimer) clearTimeout(this._timelineDebounceTimer);\n this._timelineDebounceTimer = setTimeout(() => {\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:state-timeline', { changes: this._stateTimeline });\n }\n }, 300);\n },\n\n getStateTimeline() {\n return this._stateTimeline;\n },\n\n clearStateTimeline() {\n this._stateTimeline = [];\n this._scheduleTimelineUpdate();\n },\n\n // --- FPS Monitoring ---\n // Uses requestAnimationFrame to measure frame rate with minimal overhead.\n // Only stores timestamps - no allocations per frame beyond a single array push.\n _fpsFrameTimes: [],\n\n _fpsLoop() {\n this._fpsFrameTimes.push(performance.now());\n requestAnimationFrame(() => this._fpsLoop());\n },\n\n _sampleFps() {\n const now = performance.now();\n // Remove frame timestamps older than 1 second\n const cutoff = now - 1000;\n let i = 0;\n while (i < this._fpsFrameTimes.length && this._fpsFrameTimes[i] < cutoff) i++;\n if (i > 0) this._fpsFrameTimes.splice(0, i);\n const fps = this._fpsFrameTimes.length;\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:fps', { timestamp: Date.now(), fps });\n }\n }\n };\n\n window.__SVELTE_DEVTOOLS__ = __SVELTE_DT;\n\n // Poll state values for timeline\n setInterval(() => { __SVELTE_DT._pollStateValues(); }, 200);\n\n // FPS monitoring\n requestAnimationFrame(() => __SVELTE_DT._fpsLoop());\n setInterval(() => __SVELTE_DT._sampleFps(), 500);\n\n // Phase 3: Capture runtime errors\n window.addEventListener('error', (event) => {\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:runtime-error', {\n message: event.message,\n file: event.filename,\n line: event.lineno,\n column: event.colno,\n stack: event.error?.stack || '',\n timestamp: Date.now(),\n });\n }\n });\n window.addEventListener('unhandledrejection', (event) => {\n if (import.meta.hot) {\n const reason = event.reason;\n import.meta.hot.send('svelte-devtools:runtime-error', {\n message: reason?.message || String(reason),\n stack: reason?.stack || '',\n timestamp: Date.now(),\n });\n }\n });\n\n // Listen for reactive graph requests from server\n if (import.meta.hot) {\n import.meta.hot.on('svelte-devtools:request-reactive-graph', () => {\n __SVELTE_DT.sendReactiveGraph();\n });\n import.meta.hot.on('svelte-devtools:request-state-timeline', () => {\n import.meta.hot.send('svelte-devtools:state-timeline', { changes: __SVELTE_DT._stateTimeline });\n });\n import.meta.hot.on('svelte-devtools:clear-state-timeline', () => {\n __SVELTE_DT.clearStateTimeline();\n });\n }\n}\n", R = 1, z = 5, B = 1, V = 30;
|
|
262
|
+
function H(e) {
|
|
263
263
|
return {
|
|
264
264
|
renderProfiles: e.getRenderProfiles().map((e) => ({
|
|
265
265
|
componentId: e.componentId,
|
|
@@ -273,10 +273,10 @@ function U(e) {
|
|
|
273
273
|
takenAt: Date.now()
|
|
274
274
|
};
|
|
275
275
|
}
|
|
276
|
-
function
|
|
276
|
+
function U(e, t, n) {
|
|
277
277
|
return Math.abs(e) < t ? "unchanged" : n ? e < 0 ? "improved" : "regressed" : e > 0 ? "improved" : "regressed";
|
|
278
278
|
}
|
|
279
|
-
var
|
|
279
|
+
var te = class {
|
|
280
280
|
sessions = /* @__PURE__ */ new Map();
|
|
281
281
|
active = null;
|
|
282
282
|
persistDir;
|
|
@@ -291,7 +291,7 @@ var G = class {
|
|
|
291
291
|
label: e,
|
|
292
292
|
startedAt: Date.now(),
|
|
293
293
|
persist: t,
|
|
294
|
-
startSnapshot:
|
|
294
|
+
startSnapshot: H(this.getters),
|
|
295
295
|
loadProfiles: [],
|
|
296
296
|
fpsSamples: []
|
|
297
297
|
};
|
|
@@ -311,7 +311,7 @@ var G = class {
|
|
|
311
311
|
if (!this.active) throw Error("No active session");
|
|
312
312
|
let t = this.sessions.get(this.active);
|
|
313
313
|
if (!t) throw Error("Active session missing from store");
|
|
314
|
-
return t.endedAt = Date.now(), t.endSnapshot =
|
|
314
|
+
return t.endedAt = Date.now(), t.endSnapshot = H(this.getters), this.active = null, e === "discard" ? this.sessions.delete(t.id) : (e === "disk" || t.persist) && this.persistToDisk(t), t;
|
|
315
315
|
}
|
|
316
316
|
get(e) {
|
|
317
317
|
return this.sessions.get(e) || this.loadFromDisk(e);
|
|
@@ -367,7 +367,7 @@ var G = class {
|
|
|
367
367
|
totalRenderTimeDelta: o,
|
|
368
368
|
avgRenderTimeDelta: a > 0 ? o / a : 0
|
|
369
369
|
};
|
|
370
|
-
}).filter((e) => e.renderCountDelta > 0).sort((e, t) => t.totalRenderTimeDelta - e.totalRenderTimeDelta), i = t.loadProfiles.map((e) => e.duration), a =
|
|
370
|
+
}).filter((e) => e.renderCountDelta > 0).sort((e, t) => t.totalRenderTimeDelta - e.totalRenderTimeDelta), i = t.loadProfiles.map((e) => e.duration), a = W(i), o = ne(i, 95), s = t.fpsSamples.map((e) => e.fps), c = W(s), l = s.length ? Math.min(...s) : 0, u = s.filter((e) => e < V).length;
|
|
371
371
|
return {
|
|
372
372
|
durationMs: t.endedAt - t.startedAt,
|
|
373
373
|
components: r,
|
|
@@ -399,19 +399,19 @@ var G = class {
|
|
|
399
399
|
totalRenderTimeDeltaA: o,
|
|
400
400
|
totalRenderTimeDeltaB: s,
|
|
401
401
|
diff: c,
|
|
402
|
-
verdict:
|
|
402
|
+
verdict: U(c, R, !0)
|
|
403
403
|
},
|
|
404
404
|
load: {
|
|
405
405
|
avgA: n.loadProfiles.avgDuration,
|
|
406
406
|
avgB: r.loadProfiles.avgDuration,
|
|
407
407
|
diff: l,
|
|
408
|
-
verdict:
|
|
408
|
+
verdict: U(l, z, !0)
|
|
409
409
|
},
|
|
410
410
|
fps: {
|
|
411
411
|
avgA: n.fps.avg,
|
|
412
412
|
avgB: r.fps.avg,
|
|
413
413
|
diff: u,
|
|
414
|
-
verdict:
|
|
414
|
+
verdict: U(u, B, !1)
|
|
415
415
|
}
|
|
416
416
|
};
|
|
417
417
|
}
|
|
@@ -432,26 +432,26 @@ var G = class {
|
|
|
432
432
|
}
|
|
433
433
|
}
|
|
434
434
|
};
|
|
435
|
-
function
|
|
435
|
+
function W(e) {
|
|
436
436
|
return e.length ? e.reduce((e, t) => e + t, 0) / e.length : 0;
|
|
437
437
|
}
|
|
438
|
-
function
|
|
438
|
+
function ne(e, t) {
|
|
439
439
|
if (!e.length) return 0;
|
|
440
440
|
let n = [...e].sort((e, t) => e - t);
|
|
441
441
|
return n[Math.min(n.length - 1, Math.floor(t / 100 * n.length))];
|
|
442
442
|
}
|
|
443
443
|
//#endregion
|
|
444
444
|
//#region src/mcp/issues.ts
|
|
445
|
-
var
|
|
445
|
+
var G = {
|
|
446
446
|
avgRenderTimeMs: 4,
|
|
447
447
|
renderCount: 30,
|
|
448
448
|
loadDurationMs: 200,
|
|
449
449
|
fpsDropThreshold: 40,
|
|
450
450
|
effectMaxDeps: 8
|
|
451
451
|
};
|
|
452
|
-
function
|
|
452
|
+
function re(e, t = {}) {
|
|
453
453
|
let n = {
|
|
454
|
-
...
|
|
454
|
+
...G,
|
|
455
455
|
...t
|
|
456
456
|
}, r = [];
|
|
457
457
|
for (let t of e.renderProfiles) {
|
|
@@ -463,9 +463,9 @@ function te(e, t = {}) {
|
|
|
463
463
|
summary: `${t.name} averages ${e.toFixed(2)}ms per render (${t.renderCount} renders)`,
|
|
464
464
|
file: t.file,
|
|
465
465
|
metric: {
|
|
466
|
-
avgRenderTimeMs:
|
|
466
|
+
avgRenderTimeMs: q(e),
|
|
467
467
|
renderCount: t.renderCount,
|
|
468
|
-
totalRenderTimeMs:
|
|
468
|
+
totalRenderTimeMs: q(t.totalRenderTime)
|
|
469
469
|
},
|
|
470
470
|
suggestedTool: "get_render_profile"
|
|
471
471
|
}), t.renderCount >= n.renderCount && r.push({
|
|
@@ -476,7 +476,7 @@ function te(e, t = {}) {
|
|
|
476
476
|
file: t.file,
|
|
477
477
|
metric: {
|
|
478
478
|
renderCount: t.renderCount,
|
|
479
|
-
avgRenderTimeMs:
|
|
479
|
+
avgRenderTimeMs: q(e)
|
|
480
480
|
},
|
|
481
481
|
suggestedTool: "get_component_hotspots"
|
|
482
482
|
});
|
|
@@ -488,7 +488,7 @@ function te(e, t = {}) {
|
|
|
488
488
|
summary: `${t.type} load for ${t.route} took ${t.duration.toFixed(0)}ms`,
|
|
489
489
|
file: t.file,
|
|
490
490
|
metric: {
|
|
491
|
-
durationMs:
|
|
491
|
+
durationMs: q(t.duration),
|
|
492
492
|
dataSizeBytes: t.dataSize
|
|
493
493
|
},
|
|
494
494
|
suggestedTool: "get_load_waterfall"
|
|
@@ -539,22 +539,22 @@ function te(e, t = {}) {
|
|
|
539
539
|
});
|
|
540
540
|
}
|
|
541
541
|
}
|
|
542
|
-
return r.sort(
|
|
542
|
+
return r.sort(ie);
|
|
543
543
|
}
|
|
544
|
-
var
|
|
544
|
+
var K = {
|
|
545
545
|
high: 0,
|
|
546
546
|
medium: 1,
|
|
547
547
|
low: 2
|
|
548
548
|
};
|
|
549
|
-
function
|
|
550
|
-
return
|
|
549
|
+
function ie(e, t) {
|
|
550
|
+
return K[e.severity] - K[t.severity];
|
|
551
551
|
}
|
|
552
|
-
function
|
|
552
|
+
function q(e) {
|
|
553
553
|
return Math.round(e * 100) / 100;
|
|
554
554
|
}
|
|
555
|
-
function
|
|
555
|
+
function ae(e, t = {}) {
|
|
556
556
|
let n = {
|
|
557
|
-
...
|
|
557
|
+
...G,
|
|
558
558
|
...t
|
|
559
559
|
}, r = /* @__PURE__ */ new Map(), i = /* @__PURE__ */ new Map();
|
|
560
560
|
for (let t of e.edges) r.set(t.to, (r.get(t.to) ?? 0) + 1), i.set(t.from, (i.get(t.from) ?? 0) + 1);
|
|
@@ -585,11 +585,11 @@ function re(e, t = {}) {
|
|
|
585
585
|
}
|
|
586
586
|
//#endregion
|
|
587
587
|
//#region src/mcp/server.ts
|
|
588
|
-
var
|
|
588
|
+
var J = (e) => ({ content: [{
|
|
589
589
|
type: "text",
|
|
590
590
|
text: typeof e == "string" ? e : JSON.stringify(e, null, 2)
|
|
591
591
|
}] });
|
|
592
|
-
function
|
|
592
|
+
function oe(e) {
|
|
593
593
|
let t = new c({
|
|
594
594
|
name: "vite-devtools-svelte",
|
|
595
595
|
version: "0.1.0"
|
|
@@ -605,13 +605,13 @@ function ie(e) {
|
|
|
605
605
|
effectMaxDeps: u.number().optional()
|
|
606
606
|
}
|
|
607
607
|
}, async (t) => {
|
|
608
|
-
let n = t, r = await e.getReactiveGraph(), i =
|
|
608
|
+
let n = t, r = await e.getReactiveGraph(), i = re({
|
|
609
609
|
renderProfiles: e.getRenderProfiles(),
|
|
610
610
|
reactiveGraph: r,
|
|
611
611
|
loadProfiles: e.getLoadProfiles(),
|
|
612
612
|
fpsSamples: e.getFpsSamples()
|
|
613
613
|
}, n);
|
|
614
|
-
return
|
|
614
|
+
return J({
|
|
615
615
|
count: i.length,
|
|
616
616
|
issues: i
|
|
617
617
|
});
|
|
@@ -619,20 +619,20 @@ function ie(e) {
|
|
|
619
619
|
title: "Component render hotspots",
|
|
620
620
|
description: "Top components by total render time, with render count and per-render average.",
|
|
621
621
|
inputSchema: { topN: u.number().int().min(1).max(200).optional() }
|
|
622
|
-
}, async ({ topN: t = 20 }) =>
|
|
622
|
+
}, async ({ topN: t = 20 }) => J([...e.getRenderProfiles()].map((e) => ({
|
|
623
623
|
file: e.file,
|
|
624
624
|
name: e.name,
|
|
625
625
|
componentId: e.componentId,
|
|
626
626
|
renderCount: e.renderCount,
|
|
627
|
-
totalRenderTimeMs:
|
|
628
|
-
avgRenderTimeMs:
|
|
629
|
-
lastRenderTimeMs:
|
|
627
|
+
totalRenderTimeMs: Y(e.totalRenderTime),
|
|
628
|
+
avgRenderTimeMs: Y(e.renderCount > 0 ? e.totalRenderTime / e.renderCount : 0),
|
|
629
|
+
lastRenderTimeMs: Y(e.lastRenderTime),
|
|
630
630
|
lastRenderAt: e.lastRenderAt
|
|
631
631
|
})).sort((e, t) => t.totalRenderTimeMs - e.totalRenderTimeMs).slice(0, t))), t.registerTool("get_reactive_graph_problems", {
|
|
632
632
|
title: "Reactive graph problems",
|
|
633
633
|
description: "Classified reactive graph issues: over-connected effects, orphan deriveds, isolated nodes. Returns categories instead of the full graph.",
|
|
634
634
|
inputSchema: { effectMaxDeps: u.number().int().min(1).optional() }
|
|
635
|
-
}, async ({ effectMaxDeps: t }) =>
|
|
635
|
+
}, async ({ effectMaxDeps: t }) => J(ae(await e.getReactiveGraph(), { effectMaxDeps: t }))), t.registerTool("get_load_waterfall", {
|
|
636
636
|
title: "SvelteKit load waterfall",
|
|
637
637
|
description: "Load profiles grouped by route, with timing and data size. Optionally filtered by route.",
|
|
638
638
|
inputSchema: { route: u.string().optional() }
|
|
@@ -644,14 +644,14 @@ function ie(e) {
|
|
|
644
644
|
let t = r.get(e.route) ?? [];
|
|
645
645
|
t.push(e), r.set(e.route, t);
|
|
646
646
|
}
|
|
647
|
-
return
|
|
647
|
+
return J([...r.entries()].map(([e, t]) => {
|
|
648
648
|
let n = t.map((e) => e.duration);
|
|
649
649
|
return {
|
|
650
650
|
route: e,
|
|
651
651
|
file: t[0]?.file,
|
|
652
652
|
count: t.length,
|
|
653
|
-
avgDuration:
|
|
654
|
-
maxDuration:
|
|
653
|
+
avgDuration: Y(se(n)),
|
|
654
|
+
maxDuration: Y(Math.max(...n)),
|
|
655
655
|
totalDataBytes: t.reduce((e, t) => e + t.dataSize, 0),
|
|
656
656
|
samples: t
|
|
657
657
|
};
|
|
@@ -670,7 +670,7 @@ function ie(e) {
|
|
|
670
670
|
r = r.filter((t) => t.timestamp >= e);
|
|
671
671
|
}
|
|
672
672
|
let i = r.filter((e) => e.fps < t);
|
|
673
|
-
return
|
|
673
|
+
return J({
|
|
674
674
|
threshold: t,
|
|
675
675
|
sampleCount: r.length,
|
|
676
676
|
dropCount: i.length,
|
|
@@ -681,27 +681,27 @@ function ie(e) {
|
|
|
681
681
|
title: "Render profile for a specific file",
|
|
682
682
|
description: "Returns render profile entries matching the given component file (substring match).",
|
|
683
683
|
inputSchema: { file: u.string() }
|
|
684
|
-
}, async ({ file: t }) =>
|
|
684
|
+
}, async ({ file: t }) => J(e.getRenderProfiles().filter((e) => e.file.includes(t)).map((e) => ({
|
|
685
685
|
...e,
|
|
686
|
-
totalRenderTimeMs:
|
|
687
|
-
avgRenderTimeMs:
|
|
686
|
+
totalRenderTimeMs: Y(e.totalRenderTime),
|
|
687
|
+
avgRenderTimeMs: Y(e.renderCount > 0 ? e.totalRenderTime / e.renderCount : 0)
|
|
688
688
|
})))), t.registerTool("get_project_info", {
|
|
689
689
|
title: "Project info",
|
|
690
690
|
description: "Package name/version, Svelte / SvelteKit / Vite versions, dependency lists.",
|
|
691
691
|
inputSchema: {}
|
|
692
|
-
}, async () =>
|
|
692
|
+
}, async () => J(e.getProject())), t.registerTool("get_routes", {
|
|
693
693
|
title: "SvelteKit routes",
|
|
694
694
|
description: "Static analysis of the SvelteKit routes tree.",
|
|
695
695
|
inputSchema: {}
|
|
696
|
-
}, async () =>
|
|
696
|
+
}, async () => J(e.getRoutes())), t.registerTool("get_live_components", {
|
|
697
697
|
title: "Currently mounted components",
|
|
698
698
|
description: "Component instances with file, parent, mounted status as currently mounted in the browser.",
|
|
699
699
|
inputSchema: {}
|
|
700
|
-
}, async () =>
|
|
700
|
+
}, async () => J(e.getLiveComponents())), t.registerTool("get_component_relations", {
|
|
701
701
|
title: "Component import graph",
|
|
702
702
|
description: "Static import relations between .svelte components.",
|
|
703
703
|
inputSchema: {}
|
|
704
|
-
}, async () =>
|
|
704
|
+
}, async () => J(e.getComponentRelations())), t.registerTool("start_session", {
|
|
705
705
|
title: "Start measurement session",
|
|
706
706
|
description: "Begin capturing metrics under a labelled session. Required before compare_sessions. Set `persist:true` to write to disk on end; otherwise the session lives in memory only.",
|
|
707
707
|
inputSchema: {
|
|
@@ -710,7 +710,7 @@ function ie(e) {
|
|
|
710
710
|
}
|
|
711
711
|
}, async ({ label: t, persist: n = !1 }) => {
|
|
712
712
|
let r = e.sessions.start(t, n);
|
|
713
|
-
return
|
|
713
|
+
return J({
|
|
714
714
|
id: r.id,
|
|
715
715
|
label: r.label,
|
|
716
716
|
startedAt: r.startedAt,
|
|
@@ -726,7 +726,7 @@ function ie(e) {
|
|
|
726
726
|
]).optional() }
|
|
727
727
|
}, async ({ keep: t = "memory" }) => {
|
|
728
728
|
let n = e.sessions.end(t), r = n.endedAt ? e.sessions.delta(n.id) : null;
|
|
729
|
-
return
|
|
729
|
+
return J({
|
|
730
730
|
id: n.id,
|
|
731
731
|
label: n.label,
|
|
732
732
|
startedAt: n.startedAt,
|
|
@@ -741,19 +741,19 @@ function ie(e) {
|
|
|
741
741
|
a: u.string(),
|
|
742
742
|
b: u.string()
|
|
743
743
|
}
|
|
744
|
-
}, async ({ a: t, b: n }) =>
|
|
744
|
+
}, async ({ a: t, b: n }) => J(e.sessions.compare(t, n))), t.registerTool("list_sessions", {
|
|
745
745
|
title: "List sessions",
|
|
746
746
|
description: "In-memory + on-disk sessions, most recent first.",
|
|
747
747
|
inputSchema: {}
|
|
748
|
-
}, async () =>
|
|
748
|
+
}, async () => J(e.sessions.list())), t.registerTool("load_session", {
|
|
749
749
|
title: "Load a session by id",
|
|
750
750
|
description: "Returns the full session record (including delta if ended) by id.",
|
|
751
751
|
inputSchema: { id: u.string() }
|
|
752
752
|
}, async ({ id: t }) => {
|
|
753
753
|
let n = e.sessions.get(t);
|
|
754
|
-
if (!n) return
|
|
754
|
+
if (!n) return J({ error: `Session not found: ${t}` });
|
|
755
755
|
let r = n.endedAt ? e.sessions.delta(t) : null;
|
|
756
|
-
return
|
|
756
|
+
return J({
|
|
757
757
|
...n,
|
|
758
758
|
delta: r
|
|
759
759
|
});
|
|
@@ -761,14 +761,35 @@ function ie(e) {
|
|
|
761
761
|
title: "Delete a session",
|
|
762
762
|
description: "Removes a session from memory and disk.",
|
|
763
763
|
inputSchema: { id: u.string() }
|
|
764
|
-
}, async ({ id: t }) =>
|
|
764
|
+
}, async ({ id: t }) => J({ deleted: e.sessions.delete(t) })), t;
|
|
765
765
|
}
|
|
766
|
-
function
|
|
766
|
+
function se(e) {
|
|
767
767
|
return e.length ? e.reduce((e, t) => e + t, 0) / e.length : 0;
|
|
768
768
|
}
|
|
769
|
-
function
|
|
769
|
+
function Y(e) {
|
|
770
770
|
return Math.round(e * 100) / 100;
|
|
771
771
|
}
|
|
772
|
+
var ce = "templates: {", X = "/@id/@vitejs/devtools/client/inject", le = `<script type=\\"module\\" src=\\"${X}\\"><\/script>`, Z = "</body>";
|
|
773
|
+
function ue(e) {
|
|
774
|
+
if (!e.includes("templates: {") || !e.includes(Z) || e.includes(X)) return null;
|
|
775
|
+
let t = e.indexOf(ce), n = e.indexOf(Z, t);
|
|
776
|
+
return n === -1 ? null : e.slice(0, n) + le + e.slice(n);
|
|
777
|
+
}
|
|
778
|
+
function de() {
|
|
779
|
+
return {
|
|
780
|
+
name: "vite-devtools-svelte:sveltekit-template-injector",
|
|
781
|
+
enforce: "post",
|
|
782
|
+
apply: (e, t) => t.command === "serve" && !t.isSsrBuild,
|
|
783
|
+
transform(e, t) {
|
|
784
|
+
if (!t.endsWith(".svelte-kit/generated/server/internal.js")) return;
|
|
785
|
+
let n = ue(e);
|
|
786
|
+
if (n !== null) return {
|
|
787
|
+
code: n,
|
|
788
|
+
map: null
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
}
|
|
772
793
|
//#endregion
|
|
773
794
|
//#region src/plugin.ts
|
|
774
795
|
var Q = e.dirname(i(import.meta.url));
|
|
@@ -783,17 +804,17 @@ function $(e) {
|
|
|
783
804
|
let r = t.hostname;
|
|
784
805
|
if (r === "::1" || r === "[::1]") throw Error("Blocked: loopback address");
|
|
785
806
|
if (n.isIP(r)) {
|
|
786
|
-
if (
|
|
807
|
+
if (fe(r)) throw Error(`Blocked: private IP address ${r}`);
|
|
787
808
|
} else {
|
|
788
809
|
let e = r.toLowerCase();
|
|
789
810
|
if (e === "localhost" || e.endsWith(".local") || e.endsWith(".internal")) throw Error(`Blocked: internal hostname ${r}`);
|
|
790
811
|
}
|
|
791
812
|
}
|
|
792
|
-
function
|
|
813
|
+
function fe(e) {
|
|
793
814
|
let t = e.split(".").map(Number);
|
|
794
815
|
return !!(t.length === 4 && (t[0] === 127 || t[0] === 10 || t[0] === 172 && t[1] >= 16 && t[1] <= 31 || t[0] === 192 && t[1] === 168 || t[0] === 169 && t[1] === 254 || t.every((e) => e === 0)));
|
|
795
816
|
}
|
|
796
|
-
function
|
|
817
|
+
function pe(n = {}) {
|
|
797
818
|
let { componentTracking: i = !0 } = n, o, s, c, u = r.randomUUID();
|
|
798
819
|
function f(n) {
|
|
799
820
|
if (typeof n != "string" || n.length === 0) throw Error("Invalid file path");
|
|
@@ -811,7 +832,7 @@ function se(n = {}) {
|
|
|
811
832
|
edges: []
|
|
812
833
|
}, y = [], b = [], x = [], S = [], w = [], T = [], E = null;
|
|
813
834
|
function D() {
|
|
814
|
-
return E ||= new
|
|
835
|
+
return E ||= new te({
|
|
815
836
|
persistDir: e.join(c, "node_modules", ".vite-devtools-svelte", "sessions"),
|
|
816
837
|
getters: {
|
|
817
838
|
getRenderProfiles: () => m,
|
|
@@ -985,7 +1006,7 @@ function se(n = {}) {
|
|
|
985
1006
|
},
|
|
986
1007
|
"svelte-devtools:get-module-graph": async () => M(),
|
|
987
1008
|
"svelte-devtools:get-og-preview": async (e) => N(String(e)),
|
|
988
|
-
"svelte-devtools:get-build-analysis": async () =>
|
|
1009
|
+
"svelte-devtools:get-build-analysis": async () => R(),
|
|
989
1010
|
"svelte-devtools:get-fps": async () => T,
|
|
990
1011
|
"svelte-devtools:clear-fps": async () => {
|
|
991
1012
|
T = [];
|
|
@@ -1119,7 +1140,7 @@ function se(n = {}) {
|
|
|
1119
1140
|
r.statusCode = 404, r.end("Not found");
|
|
1120
1141
|
}
|
|
1121
1142
|
});
|
|
1122
|
-
let S = () =>
|
|
1143
|
+
let S = () => oe({
|
|
1123
1144
|
getProject: () => C(c),
|
|
1124
1145
|
getRoutes: () => d(C(c).routesDir),
|
|
1125
1146
|
getLiveComponents: () => p,
|
|
@@ -1161,14 +1182,14 @@ function se(n = {}) {
|
|
|
1161
1182
|
},
|
|
1162
1183
|
resolveId(e, t) {
|
|
1163
1184
|
if (o?.command === "serve") {
|
|
1164
|
-
if (e === "virtual:svelte-devtools-runtime") return
|
|
1165
|
-
if (i && e === "svelte/internal/client" && t && !t.includes("node_modules") && !t.startsWith("\0")) return
|
|
1185
|
+
if (e === "virtual:svelte-devtools-runtime") return ee;
|
|
1186
|
+
if (i && e === "svelte/internal/client" && t && !t.includes("node_modules") && !t.startsWith("\0")) return F;
|
|
1166
1187
|
}
|
|
1167
1188
|
},
|
|
1168
1189
|
load(e) {
|
|
1169
1190
|
if (o?.command === "serve") {
|
|
1170
|
-
if (e === "\0virtual:svelte-devtools-runtime") return
|
|
1171
|
-
if (e === "\0svelte-devtools:wrapped-client") return
|
|
1191
|
+
if (e === "\0virtual:svelte-devtools-runtime") return L;
|
|
1192
|
+
if (e === "\0svelte-devtools:wrapped-client") return I;
|
|
1172
1193
|
}
|
|
1173
1194
|
},
|
|
1174
1195
|
transformIndexHtml() {
|
|
@@ -1298,7 +1319,7 @@ function se(n = {}) {
|
|
|
1298
1319
|
}
|
|
1299
1320
|
return t;
|
|
1300
1321
|
}
|
|
1301
|
-
function
|
|
1322
|
+
function R() {
|
|
1302
1323
|
let n = e.resolve(c, ".svelte-kit/output"), r = e.resolve(c, "build/client"), i = [];
|
|
1303
1324
|
for (let a of [
|
|
1304
1325
|
n,
|
|
@@ -1413,10 +1434,11 @@ export const load = async (event) => {
|
|
|
1413
1434
|
t(e, n);
|
|
1414
1435
|
};
|
|
1415
1436
|
}
|
|
1416
|
-
}
|
|
1437
|
+
},
|
|
1438
|
+
de()
|
|
1417
1439
|
];
|
|
1418
1440
|
}
|
|
1419
1441
|
//#endregion
|
|
1420
|
-
export {
|
|
1442
|
+
export { pe as svelteDevtools };
|
|
1421
1443
|
|
|
1422
1444
|
//# sourceMappingURL=index.mjs.map
|