Version: 5.55.4 | Source commit:
fcaa8ce| License: MITThis guide covers the internal architecture, compiler pipeline, runtime internals, configuration, operations, and integration points for the Svelte framework. It is written for contributors, framework integrators, tooling authors, and advanced users who need to understand how Svelte works beneath the surface.
Table of Contents
- Architecture Overview
- System Requirements
- Configuration Reference
- Deployment & Scaling
- Operations
- Performance
- Security
- Developer Internals
- Integration Points
1. Architecture Overview
Svelte is a compile-time web framework. Unlike runtime frameworks (React, Vue), Svelte shifts the bulk of the framework work to a build step: components are compiled from .svelte source files into optimized JavaScript that directly manipulates the DOM with minimal runtime overhead.
The framework consists of two independent but cooperating systems:
| System | Role | Location |
|---|---|---|
| Compiler | Transforms .svelte → JS + CSS at build time |
packages/svelte/src/compiler/ |
| Runtime | Provides reactivity, DOM helpers, SSR, lifecycle | packages/svelte/src/internal/ |
1.1 Compiler Pipeline (3-Phase)
The Svelte compiler transforms source text through three sequential phases. Each phase operates on the output of the previous one.
.svelte source text
│
▼
┌──────────────────────────────────────┐
│ Phase 1: PARSE │
│ packages/svelte/src/compiler/ │
│ phases/1-parse/ │
│ - Tokenize + parse HTML template │
│ - Parse script blocks (acorn) │
│ - Parse style blocks │
│ - TypeScript support via │
│ @sveltejs/acorn-typescript │
│ Output: AST (Component/Module) │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Phase 2: ANALYZE │
│ packages/svelte/src/compiler/ │
│ phases/2-analyze/ │
│ - Walk AST, resolve bindings │
│ - Validate component structure │
│ - Build scope tree │
│ - Check rune usage │
│ - CSS class/selector analysis │
│ - Accessibility validation │
│ Output: Annotated AST + Scope tree │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Phase 3: TRANSFORM │
│ packages/svelte/src/compiler/ │
│ phases/3-transform/ │
│ - Generate JS (client or server) │
│ - Generate CSS with scoping hashes │
│ - Emit source maps │
│ Output: { js, css, warnings, ... } │
└──────────────────────────────────────┘
NOTE: The compiler is entirely stateless — it takes source text and options, and returns output. No global state, no file system access. All file I/O happens in the calling tool (Vite plugin, Rollup plugin, etc.).
Phase 1: Parse
The parser (packages/svelte/src/compiler/phases/1-parse/) produces a structured AST from .svelte source text. The parsing strategy is HTML-first: the template is parsed as HTML, with embedded script and expression blocks extracted during traversal.
Key files:
| File | Purpose |
|---|---|
index.js |
Entry point, orchestrates HTML + script + style parsing |
read/expression.js |
Parses {...} template expressions using acorn |
read/script.js |
Extracts <script> blocks and hands off to acorn/TypeScript |
read/style.js |
Extracts <style> blocks |
utils/fuzzymatch.js |
FuzzySet class — fuzzy string matching for error message suggestions ("did you mean X?") |
utils/html.js |
HTML entity decoding, character utilities |
utils/bracket.js |
Matching brace/bracket tracking in template expressions |
TypeScript support is provided by @sveltejs/acorn-typescript, which extends acorn's grammar to handle TypeScript syntax inside <script lang="ts"> blocks.
Phase 2: Analyze
The analysis phase (packages/svelte/src/compiler/phases/2-analyze/) walks the AST produced by the parser and enriches it with semantic information needed for code generation.
Responsibilities:
- Scope resolution — builds a nested scope tree resolving variable declarations, imports, and rune usages
- Binding analysis — determines which variables are reactive (
$state,$derived) and how they flow through the component - Rune validation — enforces correct usage of all runes (cannot call
$state()at module level without$state.raw(), etc.) - CSS analysis — identifies which CSS selectors are "used" by the template vs. unused (for dead-code elimination)
- Accessibility validation — checks against
aria-queryandaxobject-querydatabases for ARIA attribute correctness - HTML tree validation — enforces valid nesting (e.g.,
<li>inside<ul>/<ol>, no<form>inside<form>)
The FuzzySet class from fuzzymatch.js is used during analysis to suggest correct rune/attribute names when a typo is detected.
Phase 3: Transform
The transform phase (packages/svelte/src/compiler/phases/3-transform/) takes the analyzed AST and generates the final JavaScript and CSS output. There are two transform targets:
| Target | Entry Point | Output |
|---|---|---|
| Client | 3-transform/client/ |
Browser JS — directly manipulates DOM using runtime helpers |
| Server | 3-transform/server/ |
SSR JS — renders to HTML strings via server runtime |
CSS transformation applies a DJB2 hash of the component filename to generate a unique class suffix for each component's styles. This is the CSS scoping mechanism (see §1.5).
Source maps are generated using magic-string (for string manipulation with position tracking) and @jridgewell/remapping (for composing source maps across multiple transformation steps).
1.2 Runtime Architecture
The runtime is split into two environments that share interface contracts but have completely different implementations.
Client Runtime (packages/svelte/src/internal/client/)
The client runtime implements Svelte's fine-grained reactivity system based on a signal graph. Key modules:
| Module | Purpose |
|---|---|
runtime.js |
Core reactivity engine — signals, effects, reactions, dependency tracking |
render.js |
mount(), hydrate(), unmount() — component lifecycle at the DOM level |
context.js |
setContext() / getContext() implementation |
reactivity/sources.js |
$state sources — mutable reactive values |
reactivity/deriveds.js |
$derived — computed reactive values, lazy evaluation |
reactivity/effects.js |
$effect — side effects with automatic dependency tracking |
reactivity/equality.js |
Deep equality checks for derived invalidation |
dom/blocks/if.js |
{#if} block rendering |
dom/blocks/each.js |
{#each} block rendering with keying |
dom/blocks/snippet.js |
{#snippet} rendering |
dom/blocks/await.js |
{#await} block rendering |
dom/blocks/boundary.js |
Error/Svelte boundary blocks |
dom/elements/ |
Element-level helpers (events, bindings, transitions, animations) |
errors.js |
Generated runtime error definitions |
warnings.js |
Generated runtime warning definitions |
Signal graph model: $state creates sources (writable nodes). $derived creates derived nodes (read-only, computed lazily). $effect creates effect nodes (run on change). When a source changes, all downstream deriveds are marked dirty and all downstream effects are scheduled for re-run. Dependency edges are tracked automatically during execution — any source read inside a derived or effect registers itself as a dependency.
Batching: Multiple state mutations can be batched to defer effect re-runs until the end of the batch, preventing cascading intermediate renders.
Server Runtime (packages/svelte/src/internal/server/)
The server runtime implements SSR. Key characteristics:
- Renders to HTML strings (or
ReadableStreamfor streaming SSR) - Most lifecycle hooks (
onMount,beforeUpdate,afterUpdate) are noops on the server onDestroyis the one lifecycle that runs on server — used for cleanup in SSR contexts- No reactivity graph — state is read-only during server rendering
- Hydration markers are injected into the output HTML for client takeover
1.3 Hydration System
Svelte uses comment-based hydration markers to identify dynamic DOM regions during client-side takeover of server-rendered HTML.
| Marker constant | Value | Meaning |
|---|---|---|
HYDRATION_START |
[ |
Begin hydration block |
HYDRATION_START_ELSE |
[! |
Begin else branch |
HYDRATION_START_FAILED |
[? |
Begin failed state (boundary) |
HYDRATION_END |
] |
End hydration block |
The client runtime (render.js) walks the DOM looking for these markers when hydrate() is called instead of mount(). On hydration:
- Server-rendered HTML is preserved as-is where possible
- Event listeners are attached
- Reactive state is initialized
- Dynamic regions bounded by markers are reconciled with the initial state
Hydration mismatches are caught and reported via the following ignorable (non-fatal) warnings:
hydration_attribute_changed — attribute value differs between SSR and client
hydration_html_changed — raw HTML content differs between SSR and client
NOTE: These warnings indicate that SSR output diverges from what the client would render independently. They are safe to suppress in development but should be investigated in production as they represent inconsistent initial state.
1.4 CSS Scoping
Svelte generates scoped CSS by appending a unique class to every selector in a component's <style> block, and adding that class to every element in the component's template.
The scope hash is generated by the hash() function in packages/svelte/src/utils.js:
// DJB2 hash → base-36 string
// Example: "Button.svelte" → "svelte-1x2y3z"
hash(str: string): string
The hash input is derived from the component's filename (configurable via cssHash compiler option). This means:
- Same file = same hash across builds (deterministic)
- Different files = different hashes (no collisions)
- Hash is a short base-36 string prepended with
svelte-
CSS selectors are transformed at compile time:
/* Input (in Button.svelte) */
button { color: red; }
/* Output */
button.svelte-abc123 { color: red; }
Template elements receive the class at compile time, not runtime — adding zero runtime overhead.
1.5 Event Delegation
Rather than attaching individual event listeners to each DOM element, Svelte delegates a fixed set of events to the document root. This reduces memory usage and listener overhead for large component trees.
Delegated events (attached once at document root):
beforeinput, click, change, dblclick, contextmenu,
focusin, focusout, input, keydown, keyup,
mousedown, mousemove, mouseout, mouseover, mouseup,
pointerdown, pointermove, pointerout, pointerover, pointerup,
touchend, touchmove, touchstart
Passive events (registered with { passive: true }):
touchstart, touchmove
The can_delegate_event(name) utility in packages/svelte/src/utils.js checks whether an event name is in the delegated set. Events not in this list receive per-element listeners.
1.6 HTML Tree Validation
The compiler validates HTML nesting rules at compile time (html-tree-validation.js in the analyze phase).
Autoclosing children (elements that implicitly close when another is opened):
li, dt, dd, p, rt, rp, optgroup, option,
thead, tbody, tfoot, tr, td, th
Disallowed nesting (hard errors):
<form>inside<form><a>inside<a><button>inside<button>- Heading elements nested inside each other (
<h1>inside<h2>, etc.)
NOTE: Custom elements (tag names containing a
-, e.g.,<my-button>) bypass all HTML tree validation. This is intentional — custom element nesting rules are not standardized and vary by implementation.
1.7 Namespace Support
Svelte handles three XML namespaces:
| Constant | Value | Usage |
|---|---|---|
NAMESPACE_HTML |
http://www.w3.org/1999/xhtml |
Default namespace |
NAMESPACE_SVG |
http://www.w3.org/2000/svg |
<svg> subtrees |
NAMESPACE_MATHML |
http://www.w3.org/1998/Math/MathML |
<math> subtrees |
The compiler detects when it's inside an SVG or MathML context (using is_svg(name) / is_mathml(name) from utils.js) and adjusts element creation calls accordingly. SVG/MathML elements must be created with document.createElementNS() rather than document.createElement().
Template flags control this behavior at runtime:
| Flag | Value | Meaning |
|---|---|---|
TEMPLATE_FRAGMENT |
1 |
Template is a document fragment |
TEMPLATE_USE_IMPORT_NODE |
2 |
Use document.importNode() |
TEMPLATE_USE_SVG |
4 |
Template is SVG namespace |
TEMPLATE_USE_MATHML |
8 |
Template is MathML namespace |
1.8 Version Disclosure
When Svelte mounts in a browser, it adds its version to a global set:
// packages/svelte/src/internal/disclose-version.js
window.__svelte = window.__svelte || { v: new Set() };
window.__svelte.v.add(VERSION);
This allows browser devtools and diagnostics to detect which version(s) of Svelte are running on a page (multiple versions can coexist).
2. System Requirements
2.1 Runtime Requirements (for framework users)
| Requirement | Minimum | Notes |
|---|---|---|
| Node.js | 18.x | Required for build tooling; not present in browser bundles |
| pnpm | 9.0.0+ | Required for monorepo development; npm/yarn work for consuming the package |
| Browsers | Modern | ES2020+ output by default; exact targets depend on Vite/Rollup config |
2.2 Browser Support
Svelte's compiled output uses:
document.createElement/createTextNode/createCommentelement.cloneNode(true)(for template cloning)addEventListener/removeEventListener- ES2020 features (optional chaining, nullish coalescing,
Promise.allSettled) Proxy(for fine-grained$stateobject reactivity)WeakMap,WeakRef(for memory-safe reactivity bookkeeping)
Minimum practical browser support: Chrome 80+, Firefox 78+, Safari 14+, Edge 80+.
For legacy browser support, downstream build tools must transpile and polyfill the compiled output.
2.3 Server-Side Rendering Requirements
SSR is pure JavaScript with no DOM dependency. The server runtime requires:
- A Node.js (or compatible) runtime
- No browser globals (
window,document) — these are explicitly not referenced in the server runtime
2.4 Development Machine Requirements
For contributing to the Svelte monorepo:
node --version # must be >= 18
pnpm --version # must be >= 9.0.0
Optional but recommended:
- Playwright browsers for e2e tests:
pnpm exec playwright install - TypeScript language server for IDE support
3. Configuration Reference
3.1 compile(source, options) — Compiler API
The primary compiler entry point. Compiles a .svelte component to JavaScript and CSS.
import { compile } from 'svelte/compiler';
const result = compile(source: string, options: CompileOptions): CompileResult;
CompileOptions (all fields optional unless noted):
| Option | Type | Default | Description |
|---|---|---|---|
filename |
string |
undefined |
Source filename. Used for CSS scoping hash and source map sources. Should be set in all real usage. |
name |
string |
'Component' |
Component class name in the generated JS. |
generate |
'client' | 'server' | false |
'client' |
Target environment. false returns AST only (no code generation). |
dev |
boolean |
false |
Enable development mode. Adds runtime checks, detailed error messages, $inspect support. |
accessors |
boolean |
false |
(Legacy) Generate getters/setters for props on component instances. Svelte 5 uses $bindable instead. |
immutable |
boolean |
false |
(Legacy) Assume props are immutable objects. |
hydratable |
boolean |
false |
Emit hydration markers for SSR takeover. Must match server-side generate: 'server' option. |
legacy |
boolean | object |
false |
Enable Svelte 4 compatibility mode. Accepts { componentApi: 4 } to use the Svelte 4 component API. |
customElement |
boolean |
false |
Compile as a custom element (Web Component). |
namespace |
'html' | 'svg' | 'mathml' |
'html' |
Root namespace of the component. Set to 'svg' for SVG-only components. |
cssHash |
(args: { hash, css, name, filename }) => string |
internal | Override CSS scope class generation. Default uses DJB2 hash of filename. |
preserveComments |
boolean |
false |
Include HTML comments in compiled output. |
preserveWhitespace |
boolean |
false |
Preserve insignificant whitespace between elements. |
runes |
boolean | undefined |
undefined |
Force runes mode (true) or legacy mode (false). undefined = auto-detect from source. |
modernAst |
boolean |
false |
Return the modern (Svelte 5) AST format from parse(). |
CompileResult:
{
js: {
code: string;
map: SourceMap | null;
};
css: {
code: string;
map: SourceMap | null;
} | null;
warnings: Warning[];
metadata: {
runes: boolean; // whether the component uses runes
};
}
3.2 compileModule(source, options) — Module Compiler
Compiles .svelte.js / .svelte.ts module files (files that use Svelte runes but are not components — no template).
import { compileModule } from 'svelte/compiler';
const result = compileModule(source: string, options: ModuleCompileOptions): CompileResult;
ModuleCompileOptions:
| Option | Type | Default | Description |
|---|---|---|---|
filename |
string |
undefined |
Source filename |
generate |
'client' | 'server' |
'client' |
Target environment |
dev |
boolean |
false |
Development mode |
3.3 parse(source, options) — Parser API
Parses a .svelte source string to AST without code generation.
import { parse } from 'svelte/compiler';
const ast = parse(source: string, options?: ParseOptions): AST.Root;
ParseOptions:
| Option | Type | Default | Description |
|---|---|---|---|
filename |
string |
undefined |
Source filename for error messages |
modern |
boolean |
false |
Return modern AST format (Svelte 5 node types) |
The returned AST.Root contains:
html— fragment node with the component's template treeinstance—<script>block AST (if present)module—<script context="module">/<script module>AST (if present)css—<style>block AST (if present)
3.4 preprocess(source, preprocessors, options) — Preprocessor API
Runs one or more preprocessors over a .svelte source string before compilation.
import { preprocess } from 'svelte/compiler';
const result = await preprocess(
source: string,
preprocessors: PreprocessorGroup | PreprocessorGroup[],
options?: { filename?: string }
): Promise<Processed>;
PreprocessorGroup: An object with optional markup, script, and style async functions. Each receives the relevant portion of the source and returns { code, map?, dependencies? }.
Processed:
{
code: string; // transformed source
map: SourceMap | null;
dependencies: string[]; // files to watch for changes
toString(): string;
}
Common preprocessors consumed via this API:
svelte-preprocess— TypeScript, SCSS, PostCSS, Pug support@sveltejs/vite-plugin-svelte— handles preprocessing transparently in Vite setups
3.5 migrate(source, options) — Migration API
Migrates Svelte 4 component syntax to Svelte 5 runes syntax.
import { migrate } from 'svelte/compiler';
const result = migrate(source: string, options?: MigrateOptions): {
code: string;
// migration may not be complete for all patterns
};
IMPORTANT: The migration tool handles the most common Svelte 4 patterns but may not cover all edge cases. Always review migrated output manually, especially for complex reactive statements (
$:) and slot usage.
3.6 svelte.config.js — Project Configuration
The canonical project configuration file, consumed by SvelteKit and @sveltejs/vite-plugin-svelte.
// svelte.config.js
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Preprocessors run before the Svelte compiler
preprocess: vitePreprocess(),
// Svelte compiler options (passed to compile())
compilerOptions: {
// Options from §3.1, minus filename/name (set per-file by the plugin)
runes: true, // force runes mode project-wide
dev: false, // overridden by plugin based on Vite mode
},
// Vite plugin options (§3.7)
vitePlugin: {
inspector: true,
},
// SvelteKit-specific options (if using SvelteKit)
kit: {
adapter: adapter(),
},
};
export default config;
3.7 Vite Plugin Options (@sveltejs/vite-plugin-svelte)
When using Svelte with Vite directly (without SvelteKit):
// vite.config.js
import { defineConfig } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte';
export default defineConfig({
plugins: [
svelte({
// All svelte.config.js options are accepted here too.
// These take precedence if svelte.config.js also exists.
include: /\.svelte$/, // which files to process
exclude: [], // which files to skip
extensions: ['.svelte'], // treated as Svelte files
emitCss: true, // extract CSS into separate chunks
hot: true, // HMR (auto-disabled in SSR mode)
preprocess: [], // PreprocessorGroup[]
compilerOptions: {}, // CompileOptions (§3.1)
onwarn(warning, defaultHandler) {
// filter or transform compiler warnings
if (warning.code === 'a11y-click-events-have-key-events') return;
defaultHandler(warning);
},
}),
],
});
3.8 Complete Runes Reference
Runes are special compiler directives — not real function calls, but syntax recognized by the Svelte compiler.
State creation runes:
| Rune | Description |
|---|---|
$state(initial) |
Reactive state. Objects/arrays are deeply reactive via Proxy. |
$state.raw(initial) |
Reactive state without deep proxy wrapping. Assignment triggers update; mutation does not. |
$state.snapshot(value) |
Takes a non-reactive deep copy of a $state value. |
$state.eager(initial) |
State that immediately runs effects (vs. default batched/deferred). |
$derived(expression) |
Computed value, re-evaluated when dependencies change. |
$derived.by(() => expression) |
Like $derived but accepts a function (for multi-statement deriveds). |
Props runes:
| Rune | Description |
|---|---|
$props() |
Declare component props. Destructure to access individual props. |
$props.id() |
Get the component's stable identity string (for aria-labelledby, etc.). |
$bindable(default?) |
Mark a prop as bindable (allows bind:prop from parent). |
Effect runes:
| Rune | Description |
|---|---|
$effect(() => { ... }) |
Runs after DOM update when dependencies change. Cleanup via returned function. |
$effect.pre(() => { ... }) |
Like $effect but runs before DOM update. |
$effect.tracking() |
Returns true if called inside a tracking context (effect/derived). |
$effect.root(() => { ... }) |
Creates an isolated effect root not tied to component lifecycle. |
$effect.pending() |
Returns true if any $state.eager updates are pending. |
Utility runes:
| Rune | Description |
|---|---|
$inspect(value) |
Dev-mode only. Logs value and its dependencies to console. |
$inspect(value).with(fn) |
Custom inspector callback instead of console.log. |
$inspect.trace(label?) |
Traces which reactive sources triggered the current effect. |
$host() |
Inside custom elements only — returns the host DOM element. |
4. Deployment & Scaling
4.1 SSR Deployment
Svelte components compiled with generate: 'server' output a module with a render() function:
// Compiled server output (simplified)
export function render(props, options) {
const html = /* ... server-rendered HTML string ... */;
const head = /* ... <head> content ... */;
return { html, head };
}
SvelteKit handles this automatically via its adapter system. For manual SSR setups:
import Component from './Component.svelte';
// Server handler (Express/Node example)
app.get('/', (req, res) => {
const { html, head } = Component.render({ prop: 'value' });
res.send(`
<!DOCTYPE html>
<html>
<head>${head}</head>
<body>${html}</body>
</html>
`);
});
// Client hydration
import { hydrate } from 'svelte';
import Component from './Component.svelte';
hydrate(Component, {
target: document.body,
props: { prop: 'value' },
});
IMPORTANT: The
hydratable: truecompiler option must be set for both the server and client builds. Without it, hydration markers are not emitted andhydrate()falls back to full client-side rendering.
4.2 Static Site Generation
For fully static output (no server runtime), prerender pages at build time. With SvelteKit:
// src/routes/+page.js
export const prerender = true;
For standalone Svelte without SvelteKit, the pattern is:
- Compile components with
generate: 'server' - Run server rendering in a Node.js build script
- Write HTML files to disk
- Serve as static files
4.3 Custom Element Compilation
Svelte components can be compiled to native Web Components (custom elements):
// compile option
compile(source, {
customElement: true,
name: 'my-button', // must contain a hyphen
});
Or declare per-component in the <svelte:options> tag:
<svelte:options customElement="my-button" />
Custom element behavior:
- Props become observed attributes (with type coercion)
<style>is encapsulated in a Shadow DOM- Lifecycle maps to
connectedCallback/disconnectedCallback $host()rune provides access to the host element- HTML tree validation is disabled for custom elements (see §1.6)
Custom element options (via <svelte:options customElement={{ ... }}):
<svelte:options customElement={{
tag: 'my-button',
shadow: 'open', // 'open' | 'none'
props: {
label: {
reflect: true, // sync prop changes back to attribute
type: 'String', // 'Boolean' | 'Number' | 'String' | 'Array' | 'Object'
attribute: 'label' // attribute name (defaults to lowercased prop name)
}
},
extend: (customElementClass) => class extends customElementClass {
// custom lifecycle hooks
}
}} />
4.4 Adapter Pattern (SvelteKit)
SvelteKit uses adapters to deploy to different hosting targets. An adapter is a package that:
- Receives the SvelteKit build output
- Transforms it for the target platform
- Writes deployment files
Adapter interface (for custom adapter authors):
/** @type {import('@sveltejs/kit').Adapter} */
export default function myAdapter(options = {}) {
return {
name: 'adapter-my-target',
async adapt(builder) {
// builder.generateManifest(), builder.copy(), builder.prerender(), etc.
},
supports: {
read: () => true, // whether adapter supports FS reads in load functions
},
};
}
4.5 Output Targets
Svelte's compiled output can target multiple module formats via the downstream bundler configuration. The Svelte compiler itself outputs ES module syntax; transformation to CJS/UMD/IIFE is handled by Rollup/Vite/webpack.
For libraries (not applications), the recommended distribution format is ESM with exports map in package.json, following Svelte's own package structure.
4.6 Multi-Version Coexistence
Multiple versions of Svelte can technically coexist on a single page (each adds its version to window.__svelte.v). However, this is not recommended for production:
- Each version carries its own runtime overhead
- Component trees from different versions cannot share context or stores
- Event delegation tables are per-version and may conflict
5. Operations
5.1 Monorepo Structure
svelte-monorepo/ ← root package (name: "svelte-monorepo")
packages/
svelte/ ← main package (published as "svelte")
src/
compiler/ ← compiler source
phases/
1-parse/
2-analyze/
3-transform/
client/
server/
utils/
internal/ ← runtime source
client/
dom/
blocks/
elements/
reactivity/
server/
flags/ ← experimental flag modules
index.js ← public exports (non-compiler)
index.d.ts ← TypeScript type definitions
package.json
rollup.config.js ← build config for this package
sites/
svelte.dev/ ← documentation site
learn.svelte.dev/ ← interactive tutorial
playgrounds/
sandbox/ ← Vite-based REPL/playground
package.json ← root workspace config
pnpm-workspace.yaml ← pnpm workspace definition
5.2 Build System
The package is built with Rollup (not Vite). Vite is used only for the playground/sandbox.
Root-level scripts (package.json at monorepo root):
| Script | Command | Purpose |
|---|---|---|
build |
pnpm -r build |
Build all packages recursively |
check |
svelte-check |
Type-check all .svelte files |
lint |
eslint . |
Run ESLint |
format |
prettier --write . |
Format all files |
test |
vitest |
Run unit tests |
bench |
vitest bench |
Run benchmarks |
bench:compare |
custom | Compare benchmark results |
bench:debug |
custom | Benchmark with debugging |
changeset:version |
changeset version |
Bump version from changesets |
changeset:publish |
changeset publish |
Publish to npm |
Package-level scripts (packages/svelte/package.json):
| Script | Purpose |
|---|---|
build |
rollup -c then pnpm generate |
dev |
rollup -c -w (watch mode) |
check |
TypeScript type checking |
generate |
Run all code generation scripts |
generate:version |
Write src/version.js from package.json |
generate:types |
dts-buddy — generate .d.ts from source |
prepublishOnly |
Full build + type generation before publish |
knip |
knip — detect dead exports/imports |
Build process in detail:
# Full build of packages/svelte
cd packages/svelte
pnpm build
# 1. rollup -c → bundles src/ → dist/
# 2. pnpm generate → code-gen (version.js, error messages, types)
# 3. dts-buddy → generates TypeScript declarations
The Rollup configuration (packages/svelte/rollup.config.js) produces multiple output bundles corresponding to the package exports map (see §3.8 below).
5.3 Code Generation
Several source files in packages/svelte/src/ are generated and should not be hand-edited:
| Generated file | Generator | Content |
|---|---|---|
src/version.js |
generate:version |
export const VERSION = '5.55.4' |
src/internal/client/errors.js |
generate:errors |
Runtime error message functions |
src/internal/client/warnings.js |
generate:warnings |
Runtime warning functions |
src/compiler/errors.js |
generate:errors |
Compiler error message functions |
src/compiler/warnings.js |
generate:warnings |
Compiler warning functions |
Error and warning functions follow a pattern:
// Generated — do not edit
export function invalid_rune_usage(node, rune) {
// throws CompileDiagnostic with URL link
e(node, "invalid_rune_usage", `The \`${rune}\` rune can only be used inside a .svelte or .svelte.js file\nhttps://svelte.dev/e/invalid_rune_usage`);
}
5.4 Testing Strategy
Tests live in packages/svelte/tests/ and are organized by category:
| Category | Directory | Runner | Purpose |
|---|---|---|---|
| Runtime | tests/runtime/ |
Vitest | Component behavior in jsdom |
| CSS | tests/css/ |
Vitest | CSS scoping, selector transforms |
| Parser | tests/parser/ |
Vitest | AST output for given inputs |
| Validator | tests/validator/ |
Vitest | Compiler error/warning triggering |
| Hydration | tests/hydration/ |
Vitest | SSR → client hydration correctness |
| Server | tests/server/ |
Vitest | SSR output correctness |
| Motion | tests/motion/ |
Vitest | svelte/motion tweens/springs |
| Transition | tests/transition/ |
Vitest | svelte/transition functions |
| e2e | tests/e2e/ |
Playwright | Full browser interaction tests |
Run tests:
# All tests
pnpm test
# Specific category (Vitest filter)
pnpm test -- --reporter=verbose runtime
# e2e (requires Playwright browsers)
pnpm exec playwright install
pnpm test:e2e
Each test category follows a fixture pattern: a directory of input files (input.svelte, input.js) and expected output files (output.js, expected.css). The test runner compiles the input and diffs against the expected output.
To update expected outputs after intentional changes:
pnpm test -- --update-snapshots
5.5 Changesets Workflow
Version management uses changesets (from @changesets/cli).
Adding a changeset (required for any user-visible change):
pnpm changeset
# Interactive prompt:
# 1. Select packages changed (svelte)
# 2. Select semver bump (major/minor/patch)
# 3. Write summary of change
# Creates .changeset/random-name.md
Release process:
# 1. Accumulate changesets from PRs
# 2. Version bump (updates CHANGELOG.md, package.json)
pnpm changeset:version
# 3. Review CHANGELOG, commit
git commit -am "chore: version packages"
# 4. Publish to npm
pnpm changeset:publish
# Internally runs: pnpm prepublishOnly && npm publish
5.6 Linting Configuration
ESLint is configured with a custom rule:
no_compiler_imports — Prevents any file under packages/svelte/src/ (excluding compiler/) from importing anything from packages/svelte/src/compiler/. This enforces the hard separation between compiler and runtime — the runtime must not depend on the compiler.
Location: eslint-plugin/ or inline in eslint.config.js.
Prettier handles formatting. Configuration is in .prettierrc at the repository root:
- Tab width: configurable (check
.prettierrc) - Single quotes vs double: per config
- No semicolons: per config
5.7 Benchmarking
Svelte maintains a benchmark suite for tracking reactivity and rendering performance:
# Run benchmarks
pnpm bench
# Compare two commits
pnpm bench:compare
# Debug benchmark with verbose output
pnpm bench:debug
Benchmarks use Vitest's bench API and measure:
- Reactivity update propagation throughput
- DOM rendering time for large
{#each}blocks - Keyed vs. non-keyed reconciliation
5.8 Continuous Integration
The repository uses GitHub Actions. Key CI checks:
| Check | Trigger | Purpose |
|---|---|---|
| Type check | PR, push | tsc over all packages |
| Lint | PR, push | ESLint + Prettier check |
| Unit tests | PR, push | Full Vitest suite |
| e2e tests | PR, push | Playwright browser tests |
| Benchmark | Scheduled | Regression detection |
6. Performance
6.1 Event Delegation Model
Delegating events to the document root instead of per-element provides significant memory savings for large component trees. A list of 10,000 items with click handlers uses 1 document listener instead of 10,000.
The delegation layer dispatches to the correct handler via a lookup table keyed by the target element, walking up the DOM tree until a handler is found (event bubbling is preserved semantically).
Implications for integrators:
event.stopPropagation()works as expected within Svelte trees- Events from non-Svelte DOM (e.g., vanilla JS children) are not captured by the delegation table
- The delegation table is set up once when any component using a given event type is first mounted
6.2 CSS Scoping Strategy
Zero-runtime-cost CSS scoping: the scope class (e.g., .svelte-abc123) is added to DOM elements by the compiled code at element creation time, not by runtime class manipulation. There is no className mutation after initial render.
CSS specificity is unchanged — scope class adds one class-level specificity point, same for all selectors in the file.
CSS dead code elimination: The compiler tracks which selectors are referenced in the template. Unused selectors are removed from the output CSS entirely (with a dev-mode warning). This is done in the analyze phase.
6.3 Reactivity Optimization
Fine-grained updates: Only the exact DOM nodes that depend on a changed signal are updated. There is no virtual DOM diffing — the signal graph encodes exactly which DOM operation corresponds to which reactive value.
Lazy derivation: $derived values are not computed until read. If a derived is never read in a given render cycle, it is never computed.
Batching: Multiple $state mutations in a single synchronous block are batched — effects run once after all mutations, not once per mutation.
Keyed {#each} blocks: Using a key expression ({#each items as item (item.id)}) enables efficient list reconciliation. Unkeyed each blocks rebuild their DOM on reorder. A known performance regression was identified in issue #18120: keyed {#each} is currently slower than non-keyed in certain scenarios — track for fix.
6.4 Hydration Performance
Svelte's hydration avoids full re-rendering:
- Static HTML from SSR is preserved as-is
- Only dynamic regions (bounded by hydration markers) are visited
- Event listeners are attached in a single pass over the hydration markers
$stateis initialized from the rendered values where possible
6.5 Tree-Shaking
Each export in the Svelte package is side-effect-free (marked in package.json with "sideEffects": false or equivalent). Bundlers can eliminate unused exports:
- A project that uses no transitions imports nothing from
svelte/transition - SSR builds tree-shake away client-only DOM manipulation code
- The
esm-envpackage providesBROWSER/SERVERconstants that enable static dead-code elimination by bundlers
6.6 Template Cloning
For elements rendered in {#each} loops, Svelte uses document.importNode() (when TEMPLATE_USE_IMPORT_NODE flag is set) to clone a pre-built template fragment instead of creating elements one by one with createElement. This is faster for complex subtrees.
7. Security
7.1 HTML Escaping
User-provided strings rendered inside {expression} template interpolations are HTML-escaped by default. The escape function is escape_html() in the runtime.
Characters escaped: &, <, >, ", '
<!-- Safe: user input is escaped -->
<p>{userInput}</p>
<!-- UNSAFE: raw HTML injection — only use with trusted content -->
<p>{@html trustedHtmlString}</p>
WARNING:
{@html ...}bypasses HTML escaping entirely and injects raw HTML into the DOM. Never use{@html}with unsanitized user input. If user content must be rendered as HTML, sanitize it with a library likeDOMPurifybefore passing to{@html}.
7.2 Trusted Types Support
Svelte includes support for the Trusted Types API, a browser security feature that prevents DOM XSS by enforcing that only "trusted" values can be assigned to dangerous sinks like innerHTML.
The @types/trusted-types dev dependency provides TypeScript types for the Trusted Types API. Svelte's runtime respects Trusted Types policies when setting innerHTML for {@html} blocks — it will call the active policy's createHTML() method if a policy is enforced.
To use Svelte with a Trusted Types CSP:
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types svelte-policy;
7.3 CSS Injection Prevention
The CSS scoping mechanism (§1.4) means component styles cannot leak out and affect other components' elements. This prevents:
- Third-party component styles from overriding application styles unexpectedly
- User-provided class names from triggering unintended style application
However, CSS scoping is not a security boundary — it's a specificity mechanism. Dynamic style bindings (style:property={value}) that include user input should be validated:
<!-- Potentially dangerous if `userColor` is not validated -->
<div style:color={userColor}>...</div>
CSS custom properties set via --property={value} bindings are safe — they cannot inject new CSS rules, only set values.
7.4 XSS Prevention in Templates
Svelte's compile-time template processing provides strong XSS protection:
| Template syntax | Safety |
|---|---|
{expression} |
Safe — HTML-escaped |
{@html expression} |
Unsafe — raw injection, use with caution |
on:click={handler} |
Safe — compiles to addEventListener, not inline script |
href={value} |
Safe for https:// URLs; javascript: is blocked in dev mode |
style:prop={value} |
Safe — CSS property values, not rule injection |
CAUTION: Svelte does not sanitize
hrefattribute values in production builds beyond emitting a dev-mode warning forjavascript:URLs. Validate user-provided URLs server-side before binding tohref.
7.5 svelte/internal Import Guard
packages/svelte/src/internal/index.js deliberately throws a runtime error when imported:
// svelte/internal — DO NOT IMPORT
// This module existed in Svelte 4 but is private in Svelte 5.
// The internal API changed incompatibly between versions.
// Import from svelte, svelte/store, svelte/motion, etc. instead.
throw new Error('...');
This is enforced at runtime (not just as a TypeScript type error) to prevent libraries and user code from depending on private internals that may change without notice.
The ESLint rule no_compiler_imports enforces the equivalent boundary at development time within the monorepo itself.
7.6 Content Security Policy
Svelte's compiled output does not use eval() or Function() constructor by default — all reactivity and DOM manipulation is compiled to static code. This makes Svelte compatible with strict CSP script-src without unsafe-eval.
Exception: Some legacy compatibility features in Svelte 4 mode may require unsafe-eval. Svelte 5 runes mode is unsafe-eval-free.
8. Developer Internals
8.1 Complete Repository Structure
packages/svelte/
src/
compiler/
phases/
1-parse/
index.js ← parser entry point
read/
expression.js ← template expression parser
script.js ← <script> block parser
style.js ← <style> block parser
utils/
fuzzymatch.js ← FuzzySet for error suggestions
html.js ← HTML entity utils
bracket.js ← bracket matching
2-analyze/
index.js ← analysis entry point
css/ ← CSS analysis
a11y.js ← accessibility validation
html-tree-validation.js ← nesting validation
3-transform/
client/ ← client-side code generation
server/ ← server-side code generation
css/ ← CSS transform + scoping
utils/
compile_diagnostic.js ← CompileDiagnostic class
mapped_code.js ← MappedCode class
errors.js ← GENERATED compiler errors
warnings.js ← GENERATED compiler warnings
index.js ← svelte/compiler exports
internal/
client/
dom/
blocks/
if.js ← {#if} rendering
each.js ← {#each} rendering
snippet.js ← {#snippet} rendering
await.js ← {#await} rendering
boundary.js ← error boundary rendering
key.js ← {#key} block
html.js ← {@html} rendering
svelte-element.js ← <svelte:element>
svelte-component.js ← <svelte:component>
elements/
events.js ← event handling + delegation
bindings/ ← bind: directives
transitions.js ← transition: directives
animations.js ← animate: directives
attachments.js ← {@attach} directive
reactivity/
sources.js ← $state implementation
deriveds.js ← $derived implementation
effects.js ← $effect implementation
equality.js ← deep equality
runtime.js ← signal graph core
render.js ← mount/hydrate/unmount
context.js ← setContext/getContext
errors.js ← GENERATED runtime errors
warnings.js ← GENERATED runtime warnings
server/
index.js ← SSR rendering
context.js ← server context
flags/
async.js ← experimental async flag
legacy.js ← legacy mode flag
tracing.js ← effect tracing flag
disclose-version.js ← window.__svelte.v registration
index.js ← throws error (private guard)
constants.js ← all bitflag constants
utils.js ← shared utility functions
index.js ← public API (non-compiler)
index.d.ts ← TypeScript declarations
store/ ← svelte/store package
motion/ ← svelte/motion package
transition/ ← svelte/transition package
animate/ ← svelte/animate package
easing/ ← svelte/easing package
events/ ← svelte/events package
attachments/ ← svelte/attachments package
reactivity/ ← svelte/reactivity package
legacy/ ← svelte/legacy package
dist/ ← built output (gitignored)
package.json
rollup.config.js
8.2 Package Exports Map
The packages/svelte/package.json exports map defines all public entry points:
| Export | Condition | Purpose |
|---|---|---|
. |
browser → client, default → server |
Main runtime (auto-selects client/server) |
./compiler |
— | Compiler API (compile, parse, etc.) |
./store |
— | Svelte store primitives (writable, readable, derived) |
./motion |
— | tweened, spring motion stores |
./transition |
— | Built-in transitions (fade, fly, slide, etc.) |
./animate |
— | Built-in animations (flip) |
./easing |
— | Easing functions |
./events |
— | on() utility for event handling |
./attachments |
— | {@attach} directive utilities |
./reactivity |
— | Standalone reactive primitives |
./reactivity/window |
— | Window-level reactive values |
./server |
— | Explicit server runtime import |
./legacy |
— | Svelte 4 compatibility API |
./internal |
— | Throws error — private, do not use |
./internal/client |
— | Internal client runtime (semi-public for tooling) |
./internal/server |
— | Internal server runtime (semi-public for tooling) |
./internal/flags/* |
— | Feature flag modules |
8.3 Bitflag Constants
Svelte uses integer bitflags extensively for compact state representation. Understanding these is essential for reading the compiler and runtime source.
{#each} block flags (EACH_*):
| Constant | Value | Meaning |
|---|---|---|
EACH_ITEM_REACTIVE |
1 |
Items are reactive signals |
EACH_INDEX_REACTIVE |
2 |
Index is reactive |
EACH_IS_CONTROLLED |
4 |
Each block is "controlled" (no wrapping element) |
EACH_IS_ANIMATED |
8 |
Each uses animate: directive |
EACH_ITEM_IMMUTABLE |
16 |
Items treated as immutable |
Props flags (PROPS_*):
| Constant | Value | Meaning |
|---|---|---|
PROPS_IS_IMMUTABLE |
1 |
Props object is immutable |
PROPS_IS_RUNES |
2 |
Parent is in runes mode |
PROPS_IS_UPDATED |
4 |
Props have been updated at least once |
PROPS_IS_BINDABLE |
8 |
Prop is bindable ($bindable) |
PROPS_IS_LAZY_INITIAL |
16 |
Initial value is lazily evaluated |
Transition flags (TRANSITION_*):
| Constant | Value | Meaning |
|---|---|---|
TRANSITION_IN |
1 |
Transition plays on enter |
TRANSITION_OUT |
2 |
Transition plays on exit |
TRANSITION_GLOBAL |
4 |
Global transition (not scoped to block) |
Usage pattern in source:
// Setting flags
const flags = EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE; // = 3
// Checking flags
if (flags & EACH_IS_ANIMATED) { ... }
// Clearing a flag
flags &= ~EACH_ITEM_REACTIVE;
8.4 Signal Graph Implementation
The core reactivity engine in runtime.js implements a push-pull signal graph:
Node types:
- Source (writable signal) — created by
$state. Has avalueand a set of subscribers. - Derived (computed signal) — created by
$derived. Has a computation function, cached value, and dirty flag. - Effect — created by
$effect. Has a side-effect function and cleanup return value.
Dependency tracking:
- A global "current consumer" pointer is set when running a derived or effect
- Any source read during execution registers the current consumer as a subscriber
- Reads outside a tracking context (no current consumer) do not register subscriptions
Update cycle:
- Source value changes → mark all subscribers dirty
- Schedule microtask to flush effects
- In flush: run dirty effects in topological order
- Effects re-run their function, re-registering dependencies (stale dependencies are pruned)
- Cleanup functions from previous run are called before re-run
Effect scheduling:
- Effects are scheduled via
Promise.resolve().then()(microtask queue) $effect.preruns before DOM updates (synchronous flush)$effectruns after DOM updates (async flush)
8.5 Compiler AST Node Types
The Svelte AST (returned by parse()) uses these primary node types:
Fragment nodes:
Root— top-level component ASTFragment— ordered list of child nodes
Template nodes:
Element— HTML elements (<div>,<span>, etc.)Component— Svelte components (<MyComponent>)SvelteComponent—<svelte:component>SvelteElement—<svelte:element>SvelteSelf—<svelte:self>(recursive component)SvelteFragment—<svelte:fragment>SvelteHead—<svelte:head>SvelteBody—<svelte:body>SvelteWindow—<svelte:window>SvelteDocument—<svelte:document>SvelteOptions—<svelte:options>Text— static text nodeExpressionTag—{expression}interpolationHtmlTag—{@html expression}raw HTMLConstTag—{@const x = ...}local constantDebugTag—{@debug variable}debug helperRenderTag—{@render snippet()}snippet invocationAttachTag—{@attach action}attachment
Block nodes:
IfBlock—{#if}...{:else if}...{:else}...{/if}EachBlock—{#each}...{:else}...{/each}AwaitBlock—{#await}...{:then}...{:catch}...{/await}KeyBlock—{#key}...{/key}SnippetBlock—{#snippet name()}...{/snippet}
Attribute nodes:
Attribute— regular HTML attributeSpreadAttribute—{...spread}props spreadBindDirective—bind:valueClassDirective—class:active={...}StyleDirective—style:color={...}EventHandler—on:click={handler}(legacy) oronclick={handler}TransitionDirective—transition:fadeAnimateDirective—animate:flipUseDirective—use:actionLetDirective—let:slot(legacy slots)AttachDirective—{@attach}
8.6 Key Utility Classes
CompileDiagnostic (packages/svelte/src/compiler/utils/compile_diagnostic.js:51):
The base class for all compiler errors and warnings. Extends Error with:
code— machine-readable error code (e.g.,invalid_rune_usage)start/end— source position ({ line, column, character })filename— source file path- URL to
svelte.dev/e/{code}orsvelte.dev/w/{code}for documentation
InternalCompileError extends CompileDiagnostic for compiler bugs (not user errors).
MappedCode (packages/svelte/src/compiler/utils/mapped_code.js:75):
Represents a code string with an associated source map. Used during code generation to accumulate transformed code while maintaining position mapping back to source. Built on magic-string.
FuzzySet (packages/svelte/src/compiler/phases/1-parse/utils/fuzzymatch.js:124):
A fuzzy string matching data structure used for "did you mean X?" suggestions in error messages. When the user writes $stat instead of $state, FuzzySet finds the closest matching rune name.
8.7 Error System Architecture
Compiler errors follow a structured pattern:
// In errors.js (generated)
export function invalid_rune_usage(node, rune) {
e(node, "invalid_rune_usage", `...message...\nhttps://svelte.dev/e/invalid_rune_usage`);
}
// Usage in analyzer
import * as e from '../errors.js';
e.invalid_rune_usage(node, '$state');
Error codes are organized by category:
| Prefix | Category |
|---|---|
options_* |
Compiler option validation |
rune_* |
Rune usage errors |
props_* |
Props-related errors |
state_* |
State-related errors |
bind_* |
Binding errors |
css_* |
CSS errors |
block_* |
Block syntax errors |
animation_* |
Animation directive errors |
attribute_* |
Attribute errors |
snippet_* |
Snippet errors |
legacy_* |
Legacy Svelte 4 feature errors in Svelte 5 mode |
typescript_* |
TypeScript-specific errors |
8.8 Experimental Flags
Experimental features are controlled via flag modules in packages/svelte/src/internal/flags/:
| Flag module | Feature | Status |
|---|---|---|
async.js |
Async component support ({#await} improvements, async snippets) |
Experimental |
legacy.js |
Legacy Svelte 4 compatibility behaviors | Stable (used by legacy option) |
tracing.js |
Effect dependency tracing ($inspect.trace) |
Dev-mode feature |
These flags can be toggled at the application entry point before any Svelte components are imported:
// Enable experimental async support
import { set_async_mode_flag } from 'svelte/internal/flags/async';
set_async_mode_flag(true);
IMPORTANT: Experimental flags are subject to breaking changes without semver consideration. Do not use in production without accepting this risk.
8.9 Ignorable Runtime Warnings
The following runtime warnings are commonly suppressed in production or specific development scenarios. They indicate non-critical conditions:
| Warning code | Description | When to suppress |
|---|---|---|
await_waterfall |
Nested {#await} blocks create a waterfall of requests |
When waterfall is intentional |
await_reactivity_loss |
Promise inside {#await} is not reactive |
When using non-reactive promises intentionally |
state_snapshot_uncloneable |
$state.snapshot() found an uncloneable value |
When snapshots include complex objects |
binding_property_non_reactive |
Binding targets a property that isn't reactive | When binding to computed/external properties |
hydration_attribute_changed |
Attribute differs between SSR and client | When difference is intentional (e.g., random IDs) |
hydration_html_changed |
Raw HTML differs between SSR and client | When SSR and client content legitimately differ |
ownership_invalid_binding |
A component binds to a prop it doesn't own | When intentional cross-component binding is needed |
ownership_invalid_mutation |
A component mutates state it doesn't own | When intentional shared state mutation is needed |
Suppress specific warnings in svelte.config.js or via the onwarn plugin option:
// svelte.config.js
export default {
compilerOptions: {
// warnings are suppressed per-component via svelte-ignore comment
},
};
<!-- Suppress in a specific file -->
<!-- svelte-ignore ownership_invalid_mutation -->
{parent.value = 'changed'}
8.10 Contributing Guide
Repository setup:
git clone https://github.com/sveltejs/svelte
cd svelte
pnpm install # install all workspace dependencies
pnpm build # initial build (required before tests)
Development workflow:
cd packages/svelte
pnpm dev # Rollup watch mode — rebuilds on source changes
# In another terminal
pnpm test # Run tests (watches on source changes with Vitest)
Making a change:
- Edit source in
packages/svelte/src/ - Build:
pnpm build(or watch mode handles it) - Add/update tests in
packages/svelte/tests/ - Update snapshots if needed:
pnpm test -- --update-snapshots - Add a changeset:
pnpm changeset(required for all user-visible changes) - Run linting:
pnpm lint && pnpm check
Test fixture pattern:
Most test categories use input/output fixture directories:
tests/runtime/samples/my-test-case/
main.svelte ← input component
_config.js ← test configuration (expected behavior)
tests/css/samples/basic/
input.svelte ← input component
output.css ← expected CSS output
Compiler error tests:
tests/validator/samples/rune-invalid-location/
input.svelte ← input that should produce an error
errors.json ← expected error codes and messages
9. Integration Points
9.1 Vite Plugin Integration
@sveltejs/vite-plugin-svelte is the primary integration layer between Svelte and Vite-based toolchains.
Plugin lifecycle:
transformhook — intercepts.svelteand.svelte.jsfiles- Runs preprocessors (if configured)
- Calls
compile()orcompileModule()with the preprocessed source - Returns
{ code, map }for Vite's module graph
handleHotUpdatehook — manages HMR- Re-compiles changed
.sveltefiles - Injects HMR update payload using Svelte's HMR API
- Invalidates dependent modules in Vite's module graph
- Re-compiles changed
- CSS extraction — the plugin separates CSS from the JS output and registers it as a virtual module (
?svelte&type=style)
Plugin configuration is detailed in §3.7.
9.2 Rollup Plugin Integration
For non-Vite Rollup builds, use rollup-plugin-svelte:
// rollup.config.js
import svelte from 'rollup-plugin-svelte';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import css from 'rollup-plugin-css-only';
export default {
input: 'src/main.js',
output: { file: 'public/bundle.js', format: 'iife' },
plugins: [
svelte({
compilerOptions: { dev: !production },
preprocess: sveltePreprocess(),
}),
css({ output: 'bundle.css' }),
nodeResolve({ browser: true, exportConditions: ['browser'] }),
],
};
IMPORTANT: When resolving Svelte's package, ensure the
browserexport condition is set. Without it, the server runtime is loaded in browser bundles. In Rollup:exportConditions: ['browser']. In Webpack: setresolve.conditionNames.
9.3 Preprocessors
Preprocessors transform .svelte source before the Svelte compiler sees it. They enable TypeScript, SCSS, PostCSS, and other language transformations.
Preprocessor API contract:
interface PreprocessorGroup {
name?: string;
markup?: (options: {
content: string;
filename?: string;
}) => Processed | Promise<Processed>;
script?: (options: {
content: string;
markup: string;
attributes: Record<string, string | boolean>;
filename?: string;
}) => Processed | Promise<Processed>;
style?: (options: {
content: string;
markup: string;
attributes: Record<string, string | boolean>;
filename?: string;
}) => Processed | Promise<Processed>;
}
interface Processed {
code: string;
map?: string | object; // source map
dependencies?: string[]; // files to watch
attributes?: Record<string, string | boolean>; // updated attributes
}
Multiple preprocessors are applied in sequence. Each receives the output of the previous:
preprocess([
vitePreprocess(), // TypeScript, SCSS
myCustomPreprocessor(), // custom transformation
])
9.4 Store Interoperability
svelte/store provides the store contract used by many Svelte libraries. Any object implementing this contract is "store-compatible":
interface Readable<T> {
subscribe(run: (value: T) => void, invalidate?: () => void): () => void;
}
interface Writable<T> extends Readable<T> {
set(value: T): void;
update(updater: (value: T) => T): void;
}
Stores and runes interop: In Svelte 5 runes mode, stores are auto-subscribed with $store prefix syntax (Svelte 4 behavior). Alternatively, use $state with manual subscription:
// Adapting an external store to $state
import { myStore } from './external.js';
import { onDestroy } from 'svelte';
let value = $state(undefined);
const unsubscribe = myStore.subscribe(v => { value = v; });
onDestroy(unsubscribe);
9.5 svelte/reactivity — Standalone Reactivity
The svelte/reactivity package exports reactive primitives for use outside of Svelte components:
import { SvelteMap, SvelteSet, SvelteURL, SvelteDate } from 'svelte/reactivity';
import { MediaQuery } from 'svelte/reactivity/window';
// Reactive Map — mutations trigger updates
const map = new SvelteMap();
map.set('key', 'value');
// Reactive window media query
const isSmall = new MediaQuery('(max-width: 600px)');
These are subclasses of their standard counterparts with Svelte's reactivity wired in.
9.6 svelte/attachments — DOM Attachments
The {@attach} directive and svelte/attachments package provide a composable alternative to use: actions:
import { on, createAttachmentKey } from 'svelte/attachments';
// Create a reusable attachment
const tooltip = createAttachmentKey();
// Use in template
// <div {@attach tooltip}>...</div>
Attachments are functions that receive the DOM element and return an optional cleanup function. They compose naturally with the @attach attachment syntax.
9.7 TypeScript Integration
Svelte provides full TypeScript support:
Component typing:
// MyComponent.svelte — TypeScript in <script lang="ts">
<script lang="ts">
interface Props {
name: string;
count?: number;
}
const { name, count = 0 }: Props = $props();
</script>
Generic components:
<script lang="ts" generics="T extends { id: string }">
const { items }: { items: T[] } = $props();
</script>
SvelteComponent base type (for dynamic <svelte:component>):
import type { SvelteComponent, ComponentType } from 'svelte';
let component: ComponentType<SvelteComponent<{ name: string }>> = MyComponent;
SvelteComponentTyped<Props, Events, Slots> is the Svelte 4 typed base class, preserved for compatibility in svelte/legacy. In Svelte 5, components are typed via their props interface directly.
Type generation: dts-buddy generates .d.ts files from the Svelte package source during the build (pnpm generate:types). This produces accurate types for all exported APIs including the compiler.
Known Issues & Active Bugs
Based on open GitHub issues at source commit fcaa8ce:
| Issue | Title | Affected area | Status |
|---|---|---|---|
| #17027 | lifecycle_outside_component in SSR builds |
SSR / lifecycle | Open |
| #18095 | $effect.pending() broken with $state.eager |
Reactivity | Open |
| #18096 | Detached DOM subtrees retained after destroy | Memory / DOM | Open |
| #18120 | Keyed each worse performance than non-keyed | Performance | Open |
| #8089 | a11y mouse events warning with focusin/focusout | A11y warnings | Open |
| #17327 | video muted attribute not rendering in SSR |
SSR attributes | Open |
| #18119 | @const referencing let slot prop regression |
Compiler | Open |
| #18097 | Spurious derived_inert warnings |
Reactivity warnings | Open |
| #18103 | Memory leak with $effect + parent prop |
Memory | Open |
| #18094 | DOM leak on unresolved promises | Memory / DOM | Open |
| #18092 | Style hashing with filename causes leaks | CSS / Memory | Open |
NOTE: These issues were open at the time of document generation. Check the Svelte GitHub repository for current status.
Quick Reference
Key File Locations
| What | Where |
|---|---|
| Compiler entry | packages/svelte/src/compiler/index.js |
| Runtime client entry | packages/svelte/src/internal/client/runtime.js |
| Runtime server entry | packages/svelte/src/internal/server/index.js |
| All bitflag constants | packages/svelte/src/constants.js |
| All utility functions | packages/svelte/src/utils.js |
| Public type definitions | packages/svelte/src/index.d.ts |
| Package exports map | packages/svelte/package.json (exports field) |
| Rollup build config | packages/svelte/rollup.config.js |
| Monorepo workspace | pnpm-workspace.yaml |
Dependency Summary
| Dependency | Role | Type |
|---|---|---|
acorn |
JavaScript parser | Runtime |
@sveltejs/acorn-typescript |
TypeScript extension for acorn | Runtime |
magic-string |
String manipulation with source maps | Runtime |
@jridgewell/remapping |
Source map composition | Runtime |
@jridgewell/sourcemap-codec |
Source map encode/decode | Runtime |
zimmerframe |
AST walking utility | Runtime |
esrap |
AST → JS code printer | Runtime |
is-reference |
Check if AST node is a reference | Runtime |
locate-character |
Position lookup in strings | Runtime |
aria-query |
ARIA role/attribute database | Runtime |
axobject-query |
AXObject accessibility database | Runtime |
clsx |
Class name utility | Runtime |
devalue |
Serialize complex JS values (SSR) | Runtime |
esm-env |
BROWSER/SERVER constants | Runtime |
rollup |
Compiler bundle build | Dev |
vitest |
Unit testing | Dev |
@playwright/test |
E2E testing | Dev |
dts-buddy |
TypeScript declaration generation | Dev |
typescript |
Type checking | Dev |