· 7 min read

Deep Dive into Svelte (Part 2) — Analyzing Svelte's Generated Code

This article was auto-translated from Chinese. Some nuances may be lost in translation.

Introduction

As can be seen from Svelte’s core philosophy, Svelte aims to extract as much necessary information as possible during the compilation process to minimize runtime overhead. In the previous article, we explained how Svelte works from compilation to code generation. Today, let’s take a closer look at how the code generated by Svelte actually operates.

First, let’s examine 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>

Svelte component syntax is largely the same as standard HTML. Aside from template syntax additions (if, await, etc.), it is essentially fully compatible with HTML. However, the generated component is pure JavaScript. For example, the component above compiles into:

// 由 Svelte 生成,省略部分程式碼
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 natively supports SSR (Server-Side Rendering). Compiling the above code using Svelte’s SSR mode creates a function that generates an HTML string:

// 由 Svelte 生成,省略部分程式碼
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;

Examining the Generated Code (DOM)

For clarity and simplicity, we will focus only on the code generated for the dom output here, setting aside the SSR portion for now.

As you can see, the generated code consists primarily of three parts: the create_fragment function, the instance function, and the SvelteComponent class.

create_fragment

First, let’s take a look 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 containing functions with single-letter property names that might look confusing at first glance. In reality, each corresponds to actions required during different lifecycle phases:

  • c: Stands for create—the function executed when the component is initially created.
  • m: Stands for mount—the function executed when the component is mounted to the DOM.
  • p: Stands for patch—the function executed when the component updates.
  • i: Stands for intro—the function executed during the component’s transition-in animation.
  • o: Stands for outro—the function executed during the component’s transition-out animation.
  • d: Stands for destroy or detach—the function executed when the component is unmounted.

For the detailed source code and code generation logic, check out src/compiler/compile/render_dom/Block.ts.

Once you know what each letter stands for, what they do becomes much clearer:

  • Assigns the evaluation of the condition (if count != 100) to if_block.
  • During create:
    • Creates the p element.
    • Sets p.textContent to "this is text".
  • During mount:
    • If if_block is truthy, calls if_block.m() (the mounting logic).
    • Inserts t0 at the anchor position.
    • Inserts p at the anchor position.
  • During patch:
    • If the condition count != 100 evaluates to true:
      • If if_block already exists, calls if_block.p().
      • If not, calls create_if_block once and then executes if_block.m().
    • If the condition count != 100 evaluates to false:
      • It means the contents inside if_block should be removed, so it calls if_block.d(1).

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];
}

All the code inside <script> gets packed into the instance function. There are a few noteworthy details here:

  • The original code setInterval(() => count++, 1000) has been transformed into setInterval(() => $$invalidate(0, count++, count), 1000).
  • The return value is an array that returns the value of count.

Svelte gathers information about variables during static analysis, enabling it to handle dependency tracking for you. Here, $$invalidate works somewhat like setState in React. The difference is that in React you must call it manually, whereas Svelte detects and applies it automatically.

The implementation of $$invalidate looks like this (with parts omitted):

// 如果發現變數值不同,將 component 設為 dirty (代表需要更新)
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 first compares whether the value has changed; if an update has occurred, it calls the make_dirty function, adds the component to dirty_components, and schedules an update. Svelte also implements a batch update mechanism to apply as many updates 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 straightforward: it calls the init function, which contains the core logic to initialize the Svelte component, invoke create_fragment, execute instance, and mount the component onto the actual DOM.

Summary

The code generated by Svelte broadly consists of three main parts: create_fragment, instance, and SvelteComponent:

  • create_fragment: Tells Svelte how to handle each lifecycle stage of the component.

  • instance: Executes the code inside <script> and returns the context (props, variables, etc.).

  • SvelteComponent: Initializes the Svelte component via the init function.

This article walked through how to parse and understand Svelte’s generated code, along with a brief explanation of how the reactivity mechanism works behind the scenes (there are many other mechanisms not covered here, which will be explored in subsequent articles). Now, reading Svelte’s compiled code should be much easier!

For more on compilation, read The Svelte Compilation Process.

Related Posts

Explore Other Topics