Understanding Svelte in Depth (1) - Svelte Compilation Process
Before reading this article, it is expected that the readers have experience with Svelte or other frontend frameworks and are interested in implementation principles.
If you haven't read "How Svelte Compiles (0) - What is an Abstract Syntax Tree?" yet, it is recommended to read it before diving into this article.
In this article, we aim to answer a few questions:
- Why can Svelte compile code into JavaScript?
- Why can Svelte use template engine-like syntax ({#if} {#await}, etc.), and how is it different from regular template engines?
Introduction
To generate the final code, Svelte needs to compile the components once to obtain the necessary information. The compilation process in Svelte consists of several stages:
- Parsing JavaScript, HTML + Svelte template syntax, and CSS syntax into an Abstract Syntax Tree (AST)
- Parsing JavaScript (enclosed in
<script>
tags or within{}
) using acorn to generate the AST - Parsing HTML + Svelte template syntax ({#if}, {variable}, etc.) using a custom parser from Svelte's compiler to generate the AST
- Parsing CSS syntax using csstree to generate the AST
- Parsing JavaScript (enclosed in
- Calling
new Component(ast)
to generate the Svelte component, which includes information such asinstance
,fragment
,vars
, etc. (src/compiler/compile/Component.ts) - Calling
renderer.render
to generate the JavaScript and CSS
The actual flow and processing are more complex than described above (including intro, outro control, event listeners, variable tracking, etc.). The entire process can be referred to using this diagram:
1. Parsing Source Code into AST
First, Svelte divides the components into three main parts: HTML (including Svelte syntax), CSS, and JavaScript, which are parsed using different parsers.
If you want to see how a Svelte component looks after being parsed, you can check it out on AST Explorer:
<script>
let count = 0;
count++;
</script>
<style>
p {
font-size: 14px;
}
</style>
<p>count is {count}</p>
The generated syntax tree (right-hand side):
After parsing, three ASTs are generated: html
, css
, and instance
. The instance
refers to the JavaScript code enclosed in <script>
tags.
2. Generating Svelte Components (Component)
At this stage, Svelte stores the necessary information from the AST in the Component
class. This information includes the component's HTML (named fragment
in Svelte), declared variables, and the instance's AST.
Next, the instance
(the part enclosed by <script>
tags in the image above) is traversed to determine the usage of all variables. At this point, it is possible to detect variables that are declared but not used and variables with $
as a prefix that need to be handled.
Then, the HTML part is traversed, and a fragment
is created. This part can be considered one of the core logics in the Svelte compilation. Fragments can be of various types, including regular HTML tags and Svelte syntax such as if
and await
.
// https://github.com/sveltejs/svelte/blob/master/src/compiler/compile/nodes/shared/map_children.ts
function get_constructor(type) {
switch (type) {
case 'AwaitBlock': return AwaitBlock;
case 'Body': return Body;
case 'Comment': return Comment;
case 'EachBlock': return EachBlock;
case 'Element': return Element;
case 'Head': return Head;
case 'IfBlock': return IfBlock;
case 'InlineComponent': return InlineComponent;
case 'KeyBlock': return KeyBlock;
case 'MustacheTag': return MustacheTag;
case 'Options': return Options;
case 'RawMustacheTag': return RawMustacheTag;
case 'DebugTag': return DebugTag;
case 'Slot': return Slot;
case 'Text': return Text;
case 'Title': return Title;
case 'Window': return Window;
default: throw new Error(`Not implemented: ${type}`);
}
}
For each type of fragment
, Svelte creates a corresponding class for easier handling. The implementation details of each class are not explained here. Let's take a few examples:
Element
corresponds to regular HTML tags and handles event handlers, attribute checks, and a11y checks.- For example, when the
tagName
isa
buthref
is not added, a warning is thrown (source code).
- For example, when the
IfBlock
handles the syntax of{#if}
and{:else}
.EachBlock
handles the syntax of{#each}
.
Next, Svelte hashes the corresponding CSS to prevent naming conflicts and generates the CSS styles.
3. Creating Fragments and Blocks
Finally, we come to the stage of generating the code. The logic for generating the code is defined in src/compiler/render_dom/Renderer.ts (Svelte chooses the renderer based on whether it is SSR or DOM; here, we use DOM as an example).
First, a fragment is created, and the code generation content is defined using the render
function in the Wrapper
. For example, Text.ts handles the generation of text. The fragment continuously traverses the child nodes and calls the render
function to generate the corresponding code, which is then placed in a block.
Next, a block is declared, which contains many code fragments (e.g., code to be generated when mounting or unmounting), which will be used to create the create_fragment
function. This part can be considered the most core and complex part of Svelte. The entire implementation can be viewed in src/compiler/render_dom/index.ts
.
The code generation part uses code-red, written by the author Rich Harris, to facilitate generation. The special feature of this library is that it can directly generate the corresponding AST nodes using syntax like var a = 1
. For example, the variableA
in the example will actually become a VariableDeclaration
node. It can also combine template literals to conveniently generate code.
For more information on code generation, you can refer to the IT Ironman video - Generating Component Code:
For example, if I want to dynamically generate an add
function, I can write it like this:
Finally, the print
API is used to convert the syntax tree into code. Let's take a look at the implementation in Svelte (using EachBlock.ts as an example, as other code generation parts are relatively complex) and see how the code generation is written:
// Called during component creation to invoke each_block_else.c()
block.chunks.create.push(b`
if (${each_block_else}) {
${each_block_else}.c();
}
`);
if (this.renderer.options.hydratable) {
block.chunks.claim.push(b`
if (${each_block_else}) {
${each_block_else}.l(${parent_nodes});
}
`);
}
// Called during component mounting to invoke each_block_else.m()
block.chunks.mount.push(b`
if (${each_block_else}) {
${each_block_else}.m(${initial_mount_node}, ${initial_anchor_node});
}
`);
The b
being called is one of the APIs of code-red
. These are the code snippets that will be generated.
Why can Svelte compile code into JavaScript?
Returning to the question at the beginning of this article, because Svelte compiles and analyzes the code in advance, it can compile .svelte
files into JavaScript.
Why can Svelte use template engine-like syntax, and how is it different from regular template engines?
Svelte has implemented a custom parser that can parse not only regular HTML but also syntax inside {}
. Through the aforementioned compilation process, it generates the corresponding JavaScript code. The difference from regular template engine syntax is that Svelte's syntax is reactive, while regular template engine syntax is static HTML. For example, let's take ERB as an example:
<% unless content.empty? %>
<div>
<%= content.text %>
</div>
<% end %>
This syntax is typically used to render HTML on the backend and return it. However, in Svelte's syntax:
{#if content}
<div>
{content.text}
</div>
{/if}
the UI will be updated when content
is not empty.
Conclusion
This article attempted to explain the general flow of Svelte from compilation to code generation, including parsing the code into an AST, creating fragments and corresponding nodes, and finally generating code through the renderer. It didn't delve much into the details and implementation, which will be explored in future articles. We hope that after reading this article, readers will have a better understanding of the process of code generation in Svelte.
References
- Li Hau Tan - The Svelte Compiler Handbook: Provides a detailed explanation of the Svelte compilation process. Highly recommended!