If you have any questions or feedback, pleasefill out this form
This post is translated by ChatGPT and originally written in Mandarin, so there may be some inaccuracies or mistakes.
Understanding Svelte Deeply (1) — The Svelte Compilation Process
Before reading this article, it's expected that the readers have some experience with Svelte or other frontend frameworks and are interested in the implementation principles.
If you haven't read “How Svelte Compiles (0) — What is an Abstract Syntax Tree?”, it is recommended to read that first before proceeding here.
In today's article, we aim to answer several questions:
- Why can Svelte compile code into JavaScript?
- Why can Svelte use syntax similar to template engines (such as {#if} and {#await}), and how does it differ from general template engine syntax?
Introduction
To generate the final code, Svelte must compile components to gather the necessary information. The compilation process in Svelte involves several main stages:
- Parsing JavaScript, HTML + Svelte template syntax, and CSS syntax into an AST
- Parsing
JavaScript
(enclosed in<script>
and{}
) into an AST using acorn - Parsing HTML + Svelte template syntax (like
{#if}
,{variable}
, etc.) into an AST using a custom parser - Parsing
CSS
syntax into an AST using csstree
- Parsing
- Calling
new Component(ast)
to generate aSvelte
component, which contains key information likeinstance
,fragment
, andvars
(see src/compiler/compile/Component.ts) - Calling
renderer.render
to generatejs
andcss
The actual processing is much more complex than described above (including intro/outro control, event listening, variable tracking, etc.). You can refer to this diagram for a complete view:
1. Parsing Source Code into AST
Svelte begins by breaking down components into three main parts: HTML (along with Svelte's syntax), CSS, and JavaScript, each parsed by different parsers.
If you want to see what a Svelte component looks like after parsing, you can check it out in AST Explorer:
<script>
let count = 0;
count++;
</script>
<style>
p {
font-size: 14px;
}
</style>
<p>count is {count}</p>
The generated syntax tree (right side):
You can see that after parsing, three ASTs are generated: html
, css
, and instance
, where instance
refers to the JavaScript code contained within <script>
.
2. Generating Svelte Component (Component)
At this stage, Svelte stores necessary information from the AST in a Component
class, which includes the component's HTML (referred to as fragment
in Svelte), declared variables, the instance's AST, and more.
Next, it traverses the instance
(the part enclosed in <script>
in the image above) to determine the usage of all variables. At this point, it can already detect whether variables are declared but unused, and if there are any variables prefixed with $
that need special handling.
Then, it starts traversing the HTML part and creates a fragment
. This part can be considered one of the core logics in Svelte's compilation. Fragments can be of various types, including standard HTML tags and Svelte-specific syntax like 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 different type of fragment
, Svelte creates a corresponding class for easier management. Here are a few examples:
Element
corresponds to standard HTML tags and handles event handlers, attribute checks, and accessibility checks.- For example, if the tagName is
a
but lacks anhref
, it will trigger a warning (source code).
- For example, if the tagName is
IfBlock
handles the{#if}
and{:else}
syntax.EachBlock
handles the{#each}
syntax.
Then, Svelte adds a hash to the corresponding CSS to prevent naming conflicts, ultimately generating the css
styles.
3. Creating Fragments and Blocks
Finally, we reach the code generation stage. The entire logic for generating code is found in src/compiler/render_dom/Renderer.ts (Svelte chooses the renderer based on whether it’s SSR or DOM; we'll use DOM as an example).
First, a fragment is created, and the content for code generation is defined using the render
function within Wrapper
. For instance, Text.ts is responsible for handling text generation. The fragment continuously traverses child nodes and calls the render
function to produce the corresponding code, placing code snippets into blocks.
Next, blocks are declared, which contain numerous code fragments (e.g., code generated during mount and unmount). Ultimately, this is used to create the create_fragment
function. This part can be considered the most complex and core aspect of Svelte. You can view the full implementation in src/compiler/render_dom/index.ts
.
The code generation utilizes a library called code-red, written by author Rich Harris, to facilitate generation. This library's unique feature allows you to directly produce corresponding AST nodes with syntax like var a = 1
. For example, in the case of variableA
, it will actually become a VariableDeclaration
node. You can also use template literal syntax to easily assemble code.
For more about the code generation process, refer to the IT Ironman competition video — Generating Component Code.
For example, if I want to dynamically generate an add
function, I can write it like this:
Finally, the syntax tree is converted into code through the print
API. Let's explore the implementation in Svelte (using EachBlock.ts as an example, as others are relatively more complex) and see how the generated code is written:
// Call each_block_else.c() when the component is created
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});
}
`);
}
// Call each_block_else.m() when the component is mounted
block.chunks.mount.push(b`
if (${each_block_else}) {
${each_block_else}.m(${initial_mount_node}, ${initial_anchor_node});
}
`);
Here, the b
called is one of the APIs from code-red
; these are the codes that will eventually be generated.
Why Can Svelte Compile Code into JavaScript?
Returning to the questions at the beginning of this article, Svelte can compile and analyze the code in advance, allowing it to transform .svelte
files into JavaScript
.
Why Can Svelte Use Syntax Similar to Template Engines, and How Does It Differ from General Template Engine Syntax?
Svelte has implemented a customized parser that not only analyzes standard HTML but also processes the syntax inside {}
, generating the corresponding JavaScript
code through the aforementioned compilation process. The difference from standard template engine syntax is that Svelte's syntax is reactive
, while typical template engine syntax is static HTML. For example, in ERB:
<% unless content.empty? %>
<div>
<%= content.text %>
</div>
<% end %>
This syntax is typically rendered on the backend and returns HTML after processing. However, the syntax in Svelte:
{#if content}
<div>
{content.text}
</div>
{/if}
will update the display when content
is not empty.
Conclusion
This article attempts to outline the general process of Svelte from compilation to code generation, including parsing code into AST, creating fragments and corresponding nodes, and finally generating code through the renderer. The focus was not on the details and implementations, which will be explored in future articles. I hope readers can gain a solid understanding of the process by which Svelte generates code after finishing this article.
References
- Li Hau Tan — The Svelte Compiler Handbook: Provides a detailed explanation of the Svelte compilation process, highly recommended reading!
If you found this article helpful, please consider buying me a coffee ☕ It'll make my ordinary day shine ✨
☕Buy me a coffee