Who is this for?
Anyone who
- is interested in the Svelte compilation process
- wants to get started in reading Svelte source code
Overview
The Svelte compilation process can be broken down into 4-steps
- Parsing source code into Abstract Syntax Tree (AST)
- Tracking references and dependencies
- Creating code blocks and fragments
- Generate code
Which sums out by the following pseudocode:
const source = fs.readFileSync('App.svelte');
const ast = parse(source);
const component = new Component(ast);
const renderer =
options.generate === 'ssr' ? SSRRenderer(component) : DomRenderer(component);
const { js, css } = renderer.render();
fs.writeFileSync('App.js', js);
fs.writeFileSync('App.css', css);
1. Parsing source code into AST
const ast = parse(source);
The Svelte syntax is a superset of HTML. Svelte implements its own parser for the Svelte syntax, which handles:
- HTML syntax
- Curly brackets
{ data }
- Logic blocks
{#each list as item}
The Svelte parser handles specially for and
tags.
When the parser encounters a tag, it uses acorn to parse the content within the tag. When the parser sees a
tag, it uses css-tree to parse the CSS content.
Besides, the Svelte parser differentiates instance script, , and module script,
.
The Svelte AST look like:
{
html: { type: 'Fragment', children: [...] },
css: { ... },
instance: { context: 'default', content: {...} },
module: { context: 'context', content: {...} },
}
You can try out the Svelte parser in ASTExplorer. You can find the Svelte parser under HTML > Svelte.
Where can I find the parser in the source code?
The parsing starts here, which the parser is implemented in src/compiler/parse/index.ts.
Where can I learn about parsing in JavaScript?
My previous article, “JSON Parser with JavaScript” introduces the terminology and guides you step-by-step on writing a parser for JSON in JavaScript.
If this is the your first time learning about parser, I highly recommend you to read that.
2. Tracking references and dependencies
const component = new Component(ast);
In this step, Svelte traverses through the AST to track all the variable declared and referenced and their depedencies.
a. Svelte creates a Component
instance.
The Component
class stores information of the Svelte component, which includes:
- HTML fragment,
fragment
- instance script and module script AST and their lexical scopes,
instance_scope
andmodule_scope
- instance variables,
vars
- reactive variables,
reactive_declarations
- slots,
slots
- used variable names to prevent naming conflict when creating temporary variables
- warnings and errors
- compile options and ignored warnings
b. Traverse the instance script and module script AST
Component
traverses the instance script and module script AST to find out all the variables declared, referenced, and updated within the instance script and module script.
Svelte identifies all the variables available before traversing the template. When encountering the variable during template traversal, Svelte will mark the variable as referenced
from template.
c. Traverse the template
Svelte traverses through the template AST and creates a Fragment tree out of the template AST.
Each fragment node contains information such as:
– expression and dependencies
Logic blocks, {#if}
, and mustache tags, { data }
, contain expression and the dependencies of the expression.
– scope
{#each}
and {#await}
logic block and let:
binding create new variables for the children template.
Svelte creates a different Fragment node for each type of node in the AST, as different kind of Fragment node handles things differently:
- Element node validates the attribute, bindings, content and event handlers.
- Slot node registers the slot name to the
Component
. - EachBlock node creates a new scope and tracks the
key
,index
and the name of the list to be iterated. - …
d. Traverse the instance script AST
After traversing through the template, Svelte now knows whether a variable is ever being updated or referenced in the component.
With this information, Svelte tries make preparations for optimising the output, for example:
- determine which variables or functions can be safely hoisted out of the
instance
function. - determine reactive declarations that does not need to be reactive
e. Update CSS selectors to make style declarations component scope
Svelte updates the CSS selectors, by adding .svelte-xxx
class to the selectors when necessary.
At the end of this step, Svelte has enough information to generate the compiled code, which brings us to the next step.
Where can I find this in the source code?
You can start reading from here, which the Component
is implemented in src/compiler/compile/Component.ts.
Where can I learn about traversing in JavaScript?
Bear with my shameless plug, my previous article, “Manipulating AST with JavaScript” covers relevant knowledge you need to know about traversing AST in JavaScript.
3. Creating code blocks and fragments
const renderer =
options.generate === 'ssr' ? SSRRenderer(component) : DomRenderer(component);
In this step, Svelte creates a Renderer
instance which keeps track necessary information required to generate the compiled output. Depending on the whether to output DOM or SSR code (see generate
in compile options), Svelte instantiates different Renderer
respectively.
DOM Renderer
DOM Renderer keeps track of a list of blocks and context.
A Block contains code fragments for generate the create_fragment
function.
Context tracks a list of instance variables which will be presented in the $$.ctx
in the compiled output.
In the renderer, Svelte creates a render tree out of the Fragment tree.
Each node in the render tree implements the render
function which generate codes that create and update the DOM for the node.
SSR Renderer
SSR Renderer provide helpers to generate template literals in the compiled output, such as add_string(str)
and add_expression(node)
.
Where can I find the Renderer
in the source code?
The DOM Renderer is implemented in src/compiler/compile/render_dom/Renderer.ts, and you can check out the SSR Renderer code in src/compiler/compile/render_ssr/Renderer.ts.
4. Generate code
const { js, css } = renderer.render();
Different renderer renders differently.
The DOM Renderer traverses through the render tree and calls the render
function of each node along the way. The Block
instance is passed into the render
function, so that each node inserts the code into the appropriate create_fragment
function.
The SSR Renderer, on the other hand, relies on different node handlers to insert strings or expressions into the final template literal.
The render function returns js
and css
which will be consumed by the bundler, via rollup-plugin-svelte for rollup and svelte-loader for webpack respectively.
Svelte runtime
To remove duplicate code in the compiled output, Svelte provide util function which can be found in the src/runtime/internal, such as:
- dom related utils, eg:
append
,insert
,detach
- scheduling utils, eg:
schedule_update
,flush
- lifecycle utils, eg:
onMount
,beforeUpdate
- animation utils, eg:
create_animation