sveltejs/svelte
Runes
Active contributors: Rich Harris, Simon H, Dominic Gannaway
Purpose
Runes are compiler-recognised globals — $state, $derived, $effect, $props, $bindable, $inspect, $host, plus their .by / .raw / .frozen variants. The compiler detects them statically (no runtime polymorphism) and emits direct calls into the reactivity engine.
User-facing docs live at https://svelte.dev/docs/svelte/$state etc. This page documents how runes flow through the codebase.
Detection
Runes are detected during analysis in packages/svelte/src/compiler/phases/2-analyze/index.js and the per-rune visitors under packages/svelte/src/compiler/phases/2-analyze/visitors/ (e.g., CallExpression.js, Identifier.js, LabeledStatement.js). A component is in runes mode when:
- Any rune is used at module/instance scope, or
<svelte:options runes>is set, or- Compile option
runes: trueis passed.
The dev-only top of packages/svelte/src/index-client.js installs throwing global getters for each rune name so that calling $state(...) outside a Svelte file emits a friendly rune_outside_svelte error rather than ReferenceError.
Compilation
| Rune | Client emit | Server emit |
|---|---|---|
$state(value) |
state(value) from internal/client/reactivity/sources.js, then proxied via proxy() from internal/client/proxy.js if the value is plain object/array. |
Plain value (no reactivity needed). |
$state.raw(v) |
state(v) without proxy wrapping. |
Plain value. |
$state.snapshot(v) |
snapshot(v) from internal/client/proxy.js — strips the proxy. |
Identity. |
$derived(expr) |
derived(() => expr) from internal/client/reactivity/deriveds.js. |
Inlined evaluation (no memoisation needed). |
$derived.by(fn) |
derived(fn). |
fn(). |
$effect(fn) |
user_effect(fn) from internal/client/reactivity/effects.js. |
No-op (server has no effects). |
$effect.pre(fn) |
user_effect_pre(fn). |
No-op. |
$effect.root(fn) |
effect_root(fn). |
No-op. |
$effect.tracking() |
effect_tracking(). |
false. |
$props() |
props_get(...) plumbing from internal/client/reactivity/props.js. |
A plain destructured object on the renderer. |
$bindable(default) |
prop(...) writeback wrapper. |
The default value. |
$inspect(...) |
inspect(...) from internal/client/dev/inspect.js (DEV only). |
No-op. |
$inspect.trace(label) |
tracing_mode_flag integration via internal/client/dev/tracing.js. |
No-op. |
$host() |
A reference to the custom-element host node — only available inside a <svelte:options customElement> component. |
No-op (no host on the server). |
The actual call generation happens in the visitor files under packages/svelte/src/compiler/phases/3-transform/client/visitors/ — search for state, derived, user_effect, etc.
$props plumbing
The $props() rune destructures into named bindings + an optional rest. The client transformer emits one of:
let { foo, bar } = $props()→prop(...)calls per name to wire writebacks for$bindableprops.let { ...rest } = $props()→rest_props(...)to gather everything not destructured.
The runtime pieces live in packages/svelte/src/internal/client/reactivity/props.js (~12 KB). It defines prop(...), rest_props(...), legacy_rest_props(...), and the writeback derived used by $bindable.
$state and the deep-reactive Proxy
For $state(plainObject), the compiler emits state(proxy(value)). proxy() (packages/svelte/src/internal/client/proxy.js, ~430 lines) returns a Proxy with custom get / set / has / deleteProperty / defineProperty / getOwnPropertyDescriptor traps:
- Each property access reads (or lazily creates) a per-property
Source. - Each property write calls
set(source, value), marking dependents dirty. - Nested objects/arrays are wrapped recursively; primitives are stored verbatim in their Source.
STATE_SYMBOL(andPROXY_PATH_SYMBOLfor tracing) are reserved keys;proxy()short-circuits on already-proxied inputs.
$inspect.trace and tracing
Calling $inspect.trace('label') opts the surrounding effect/derived into tracing. The implementation in packages/svelte/src/internal/client/dev/tracing.js:
- Captures a stack trace at each run.
- Records which sources changed since the previous run.
- Logs a console group on each rerun.
Tracing is only available in DEV builds and only when the tracing_mode_flag is on (set by svelte/internal/flags/tracing when the compiler detects use of $inspect.trace).
Tests
- Compiler-side:
packages/svelte/tests/runtime-runes/is the main suite. - Validation: rune misuse messages (e.g., outside runes mode, on the server) are tested under
packages/svelte/tests/validator/. - Snapshot output:
packages/svelte/tests/snapshot/contains canonical compiled-output samples for each rune.
Key source files
| File | Purpose |
|---|---|
packages/svelte/src/compiler/phases/2-analyze/index.js |
Rune detection, mode determination. |
packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js |
Rune call rewriting. |
packages/svelte/src/internal/client/reactivity/sources.js |
state, set, increment. |
packages/svelte/src/internal/client/reactivity/deriveds.js |
derived. |
packages/svelte/src/internal/client/reactivity/effects.js |
user_effect, effect_root, etc. |
packages/svelte/src/internal/client/reactivity/props.js |
$props and $bindable plumbing. |
packages/svelte/src/internal/client/proxy.js |
Deep-reactive Proxy. |
packages/svelte/src/internal/client/dev/inspect.js |
$inspect log integration. |
packages/svelte/src/internal/client/dev/tracing.js |
$inspect.trace. |
Entry points for modification
To add a new rune, you need: detection in phases/2-analyze (so it's recognised in scope), validation in phases/2-analyze/visitors/<NodeType>.js, code generation in phases/3-transform/client/visitors/CallExpression.js (and phases/3-transform/server/visitors/CallExpression.js if it has a server emit), a runtime helper somewhere under internal/client/reactivity/, and a developer-facing throw in the global getter at the top of index-client.js.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.