Svelte 3 Under the hood

5 minute read

With the recent release of Svelte v3, it seemed a good time to dive under the hood and explain exactly what it is doing. For those that don’t know, Svelte is a js library that takes a very different approach than what I have used before. Let’s see how Svelte describes itself:

Svelte is a radical new approach to building user interfaces. Whereas traditional frameworks like React and Vue do the bulk of their work in the browser, Svelte shifts that work into a compile step that happens when you build your app.

We are going to look at a simple compoment, and break down the code that is generated by Svelte.

Our first component

Svelte components are different to other frameworks, in that it combines the HTML, CSS and JS all in one file. Here is a simple hello world example:

Our component
1<script>
2  let name = 'world';
3</script>
4
5<h1>Hello {name}!</h1>

Despite its simplicity, we are going to break down exactly what it is.

1let name = 'world';

All we are doing here is declaring a variable name with a default value of world. Behind the scenes however, this becomes a value that can be used throughout the Svelte component.

1<h1>Hello {name}!</h1>

Here, we have a standard h1 tag that is saying a friendly hello to the world. With this, we now have a component that will automatically update the dom whenever name changes.

To use this component, we would then simply make a new instance of it and supply it with some options.

Component initialization
1new SimpleComponent({
2  target: '.simple',
3});

The target defines the location in the html to mount the component. There are many more options, but we will keep it simple for now.

Under the hood

Now that we have a component, let’s dive into what Svelte is doing with this component once compiled. It’s important to note that we may not dive into all of Svelte’s internal functions, instead focusing on the core components that make up our output.

Svelte output
 1/* App.svelte generated by Svelte v3.2.2 */
 2import {
 3	SvelteComponent,
 4	append,
 5	detach,
 6	element,
 7	init,
 8	insert,
 9	noop,
10	safe_not_equal,
11	text
12} from "svelte/internal";
13
14function create_fragment(ctx) {
15	var h1, t0, t1, t2;
16
17	return {
18		c() {
19			h1 = element("h1");
20			t0 = text("Hello ");
21			t1 = text(name);
22			t2 = text("!");
23		},
24
25		m(target, anchor) {
26			insert(target, h1, anchor);
27			append(h1, t0);
28			append(h1, t1);
29			append(h1, t2);
30		},
31
32		p: noop,
33		i: noop,
34		o: noop,
35
36		d(detaching) {
37			if (detaching) {
38				detach(h1);
39			}
40		}
41	};
42}
43
44let name = 'world';
45
46class App extends SvelteComponent {
47	constructor(options) {
48		super();
49		init(this, options, null, create_fragment, safe_not_equal, []);
50	}
51}
52
53export default App;

Although this file may seem daunting, we will do our best to point out the genius simplicity of the output. Let’s focus on the imports first.

Generated imports
 1import {
 2	SvelteComponent,
 3	append,
 4	detach,
 5	element,
 6	init,
 7	insert,
 8	noop,
 9	safe_not_equal,
10	text
11} from "svelte/internal";

One of the genius elements of Svelte is the use of it’s internal package. This allows for performant bundles through the use of tree-shaking and code-splitting. These imports provide wide ranging functionality, from creating HTML elements (element) to doing… nothing (noop).

Let’s jump down the end of the file, before we work through the more complex areas.

Generated class
1let name = 'world';
2
3class App extends SvelteComponent {
4	constructor(options) {
5		super();
6		init(this, options, null, create_fragment, safe_not_equal, []);
7	}
8}

Wait, that’s interesting. Our simple name variable declaration has been almost copied over to the output. This is another wonderful advantage to the Svelte 3 design. The use of standard JS syntax means our compiled code can try it’s hardest to re-use as much as possible. The rest of this section is defining a simple class, using the filename as the class name. It extends from SvelteComponent to get all of the required functionality, then implements a simple constructor to initialize the component.

The options argument of the constructor consists of the same arguments we passed in earlier.

1{
2  target: '.simple'
3}

With a more advanced component, this would contain the component’s properties and many other options. The init function call is doing all of the heavy lifting here. It will create our fragment, mount the component onto the page and a lot more.

The create_fragment function is the last generated piece of code for our simple component. Let’s take another look at it.

Generated create_fragment
 1function create_fragment(ctx) {
 2	var h1, t0, t1, t2;
 3
 4	return {
 5		c() {
 6			h1 = element("h1");
 7			t0 = text("Hello ");
 8			t1 = text(name);
 9			t2 = text("!");
10		},
11
12		m(target, anchor) {
13			insert(target, h1, anchor);
14			append(h1, t0);
15			append(h1, t1);
16			append(h1, t2);
17		},
18
19		p: noop,
20		i: noop,
21		o: noop,
22
23		d(detaching) {
24			if (detaching) {
25				detach(h1);
26			}
27		}
28	};
29}

Confusing, right? The purpose of this function is supply a bunch of helpers for managing our component. Let’s take a look at the first one and break down it’s purpose.

Generated constructor
1c() {
2  h1 = element("h1");
3  t0 = text("Hello ");
4  t1 = text(name);
5  t2 = text("!");
6}

Much easier to break down. We can see the content of our component being built up here. A h1 tag, and our hello message. This function c, is shorthand for create.

Onto the next one:

Generated mount call
1m(target, anchor) {
2  insert(target, h1, anchor);
3  append(h1, t0);
4  append(h1, t1);
5  append(h1, t2);
6}

In this case, m is shorthand for mount. It’s simply inserting top level elements and building their node tree as expected.

We will skip p, i, and o for this tutorial, due to them being unused. This means there is only one more piece of the code to look at!

Generated destroy call
1d(detaching) {
2  if (detaching) {
3    detach(h1);
4  }
5}

Again when looked at in isolation is quite self-explanatory. d is for destroy, and is called when the component is being destroyed. It simply checks if it’s detaching, and calls detach. Simple!

That’s it! Obviously, the more complex the component the more complex the generated code. Hopefully this gives some insight into the underlying beauty of Svelte. If you want to read more, here are some helpful links to get you started: