| name | svelte-devtools |
| description | Svelte debug interface reference. Use when implementing or debugging Svelte state observation — $$invalidate, $capture_state, runes runtime. Tier 3: Svelte 4 doable, Svelte 5 hooks pending. |
Svelte Debug Internals
No global hook. Detection relies on DOM markers and component internals.
Detection
const isSvelte = element.__svelte !== undefined
|| element.$$ !== undefined
function getSvelteVersion(element) {
if (element.__svelte_meta) return 4
if (element.__svelte?.v) return 5
if (element.$$) return 4
return null
}
Svelte 4
Component Internals ($$)
Every Svelte 4 component instance has a $$ property:
const $$ = component.$$
State Access
State lives in $$.ctx as a flat array. Index positions are compiler-determined:
$$invalidate
The core update mechanism. Called when state changes:
const origInvalidate = component.$$.ctx[]
Dev Mode APIs ($capture_state / $inject_state)
Available when compiled with dev: true:
const state = component.$capture_state()
component.$inject_state({ count: 10 })
These are the cleanest observation APIs for Svelte 4.
Observation Approach
Wrap component initialization to intercept $$invalidate:
const origInit = svelte.init
svelte.init = (component, options, instance, ...) => {
origInit(component, options, (...args) => {
const $$ = instance(...args)
return $$
}, ...)
}
Svelte 5 (Runes)
Runtime Model
Runes compile to internal runtime calls — fundamentally different from Svelte 4:
$inspect() — Built-in Debug Rune
$inspect(count)
$inspect(count).with(fn)
Limitation: $inspect() is a compile-time rune. It must appear in component source. Cannot be dynamically injected into running applications.
DevTools Status: BROKEN
Svelte 5 shipped without external devtools hooks. Issue #11389 tracks adding proper instrumentation. Current state:
- No equivalent to
$capture_state / $inject_state
- Internal signals (
$.source) are not exposed
- No component lifecycle hooks for external consumers
- The svelte-devtools extension does not work with Svelte 5
Svelte 5 Fallback
Until hooks ship, the only external observation strategy:
const observer = new MutationObserver((mutations) => {
})
observer.observe(root, { subtree: true, childList: true, characterData: true, attributes: true })
Version Detection Strategy
Branch observer implementation based on runtime:
function createSvelteObserver(element) {
const version = getSvelteVersion(element)
if (version === 4) return new Svelte4Observer(element)
if (version === 5) return new Svelte5Observer(element)
return null
}
Key Gotchas
| Issue | Version | Description |
|---|
| Mutation vs assignment | Svelte 4 | array.push(x) won't trigger — must reassign: array = [...array, x] |
$derived not proxied | Svelte 5 | Derived values are read-only, cannot be intercepted at the setter level |
| Class instances not proxied | Svelte 5 | $state on class fields uses proxies, but nested class instances may not be deeply reactive |
| Compiler-dependent indices | Svelte 4 | $$.ctx array indices depend on compilation — not stable across builds |
| Dev-only APIs | Svelte 4 | $capture_state / $inject_state stripped in production builds |