Introduction
From Svelte's core principles, it can be understood that Svelte aims to extract necessary information from the compilation process to minimize dynamic overhead. The previous article explained how Svelte works from compilation to code generation. Today, let's take a look at how the generated code from Svelte works.
Let's start by examining a simple Svelte component:
<script>
import { onMount } from 'svelte';
let count = 1;
onMount(() => {
setInterval(() => count++, 1000);
})
</script>
{#if count != 100}
<span>{count}</span>
{/if}
<p>
this is text
</p>
The syntax of Svelte components is similar to regular HTML, except that it includes template-like syntax (such as if
, await
). Basically, it is fully compatible with HTML. However, the generated component is in JavaScript. For example, the above component will be compiled into:
// Generated by Svelte, omitted some code
import { onMount } from "svelte";
function create_if_block(ctx) {
let span;
let t;
return {
c() {
span = element("span");
t = text(/*count*/ ctx[0]);
},
m(target, anchor) {
insert(target, span, anchor);
append(span, t);
},
p(ctx, dirty) {
if (dirty & /*count*/ 1) set_data(t, /*count*/ ctx[0]);
},
d(detaching) {
if (detaching) detach(span);
}
};
}
function create_fragment(ctx) {
let t0;
let p;
let if_block = /*count*/ ctx[0] != 100 && create_if_block(ctx);
return {
c() {
if (if_block) if_block.c();
t0 = space();
p = element("p");
p.textContent = "this is text";
},
m(target, anchor) {
if (if_block) if_block.m(target, anchor);
insert(target, t0, anchor);
insert(target, p, anchor);
},
p(ctx, [dirty]) {
if (/*count*/ ctx[0] != 100) {
if (if_block) {
if_block.p(ctx, dirty);
} else {
if_block = create_if_block(ctx);
if_block.c();
if_block.m(t0.parentNode, t0);
}
} else if (if_block) {
if_block.d(1);
if_block = null;
}
},
i: noop,
o: noop,
d(detaching) {
if (if_block) if_block.d(detaching);
if (detaching) detach(t0);
if (detaching) detach(p);
}
};
}
function instance($$self, $$props, $$invalidate) {
let count = 1;
onMount(() => {
setInterval(() => $$invalidate(0, count++, count), 1000);
});
return [count];
}
class App extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, {});
}
}
Svelte also supports SSR (Server-Side Rendering), so if you use Svelte's SSR feature to compile the above code, it will create a function that generates an HTML string.
// Generated by Svelte, omitted some code
import { onMount } from "svelte";
const App = create_ssr_component(($$result, $$props, $$bindings, slots) => {
let count = 1;
onMount(() => {
setInterval(() => count++, 1000);
});
return `${count != 100 ? `<span>${escape(count)}</span>` : ``}
<p>this is text
</p>`;
});
export default App;
Observing the Generated Code (dom)
For the sake of explanation, let's focus on the generated code for dom
and skip the SSR
part.
The generated code can be mainly divided into three parts: create_fragment
function, instance
function, and SvelteComponent
class.
create_fragment
Let's start by looking at create_fragment
:
function create_fragment(ctx) {
let t0;
let p;
let if_block = /*count*/ ctx[0] != 100 && create_if_block(ctx);
return {
c() {
if (if_block) if_block.c();
t0 = space();
p = element("p");
p.textContent = "this is text";
},
m(target, anchor) {
if (if_block) if_block.m(target, anchor);
insert(target, t0, anchor);
insert(target, p, anchor);
},
p(ctx, [dirty]) {
if (/*count*/ ctx[0] != 100) {
if (if_block) {
if_block.p(ctx, dirty);
} else {
if_block = create_if_block(ctx);
if_block.c();
if_block.m(t0.parentNode, t0);
}
} else if (if_block) {
if_block.d(1);
if_block = null;
}
},
i: noop,
o: noop,
d(detaching) {
if (if_block) if_block.d(detaching);
if (detaching) detach(t0);
if (detaching) detach(p);
}
};
}
create_fragment
returns an object with several single-letter properties that represent different lifecycle hooks:
- c: Represents
create
, which means the function to be executed when the component is initially created. - m: Represents
mount
, which means the function to be executed when the component is mounted to the DOM. - p: Represents
patch
, which means the function to be executed when the component is updated. - i: Represents
intro
, which means the function to be executed when the component is transitioning in. - o: Represents
outro
, which means the function to be executed when the component is transitioning out. - d: Represents
destroy
ordetach
, which means the function to be executed when the component is unmounted.
Knowing the meaning of each letter, their respective tasks become clearer:
- Assign the result of the conditional expression (
if count != 100
) toif_block
. - In the
create
phase:- Create the
p
element. - Set
p.textContent
to "this is text".
- Create the
- In the
mount
phase:- If
if_block
istrue
, callif_block.m()
(the mount function). - Insert
t0
into the anchor. - Insert
p
into the anchor.
- If
- In the
patch
phase:- If the condition
count != 100
is true:- If
if_block
already exists, callif_block.p()
. - If not, call
create_if_block
once and then executeif_block.m()
.
- If
- If the condition
count != 100
is false:- It means that the content inside
if_block
needs to be removed, so callif_block.m()
.
- It means that the content inside
- If the condition
instance
Next, let's look at the instance
function:
function instance($$self, $$props, $$invalidate) {
let count = 1;
onMount(() => {
setInterval(() => $$invalidate(0, count++, count), 1000);
});
return [count];
}
The code inside the <script>
tag is placed inside the instance
function. Here are a few notable points:
- The original code
setInterval(() => count++, 1000)
is transformed intosetInterval(() => $$invalidate(0, count++, count), 1000)
. - The return value is an array containing the value of
count
.
Svelte can infer variable dependencies during static analysis, allowing it to track changes. The $$invalidate
function here works somewhat similar to setState
in React. The difference is that in Svelte, you have to manually add it, whereas in React, it is automatically detected and handled for you.
Here's how $$invalidate
is implemented (omitting some code):
// If the variable value is different, mark the component as dirty (indicating that it needs to be updated)
if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) {
if (!$$.skip_bound && $$.bound[i]) $$.bound[i](value);
if (ready) make_dirty(component, i);
}
function make_dirty(component, i) {
if (component.$$.dirty[0] === -1) {
dirty_components.push(component);
schedule_update();
component.$$.dirty.fill(0);
}
component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));
}
Every time setInterval
triggers count++
, $$invalidate
is called. It compares the old and new values of count
and marks the component as dirty if there is a change. Then, it calls the make_dirty
function, which adds the component to dirty_components
and schedules an update. Svelte also implements a batch update mechanism, where it tries to update as much as possible within a single frame.
SvelteComponent
class App extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, {});
}
}
The implementation of SvelteComponent
is quite simple. It calls the init
function, which initializes the Svelte component. It invokes the create_fragment
function and executes the instance
function to mount the component to the DOM.
Summary
The generated code from Svelte consists of three main parts: create_fragment
, instance
, and SvelteComponent
.
create_fragment
: Specifies how each lifecycle of the Svelte component should be handled.instance
: Executes the code inside the<script>
tag and returns the context (props, variables, etc.).SvelteComponent
: Initializes the Svelte component through theinit
function.
This article explains how to interpret the generated code from Svelte and briefly describes how the reactive mechanism behind it is achieved (there are many other mechanisms not mentioned here, which will be explained in future articles). Now everyone can understand the generated code from Svelte!
For more Svelte-related articles, you can refer to this link.