Open-Source Wikis

/

Svelte

/

Features

/

Runes

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: true is 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 $bindable props.
  • 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 (and PROXY_PATH_SYMBOL for 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.

Runes – Svelte wiki | Factory