The Svelte compiler: How it works
Tags: svelte, javascript, compiler, webdev
Published: 2020-02-14 09:28:41

---
title: "The Svelte compiler: How it works"
published: true
description: Learn how to the compiler works internally
tags: svelte, javascript, compiler, webdev
---

> Want to learn more about Svelte? I just launched a few [short video courses](https://joshuanussbaum.podia.com) ✨

Most front-end frameworks rely on a diffing engine that syncs the visual DOM with an in-memory copy of the DOM. 

[Svelte](https://github.com/sveltejs/svelte) is different. It's a compiler. It generates code (JavaScript) that updates the visual tree directly, *without* diffing.

Think of it as converting html like `<h1>Hello World</h1>` into:

```javascript
const element = document.createElement('h1')
element.textContent = "Hello World"
document.body.appendChild(element)
```

Now, why would you want to do that?

Because of data binding.

It means we can write `<h1>{someValue}</h1>` declaratively and and we don't need to write imperative statements like `element.textContent = someValue` every time `someValue` changes. Svelte generates the synchronization code for us.

# How the compiler works

The compiler takes in `.svelte` files, parses them into an AST [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree), analyzes the tree, and generates Javascript and CSS.

*Disclaimer*: the examples below are simplified for brevity.

# Parsing tags

Recall that the anatomy of a `.svelte` file is similar to a `.html` file:

```html
<script>// js goes here</script>

<style>/* css goes here */<style>

<!-- More (visual) html tags here -->
<h1>...</h1>
<p>...</p>
```

The first step is to parse the document and create 3 buckets for tags: `<script>` tags, `<style>` tags, and visual tags (everything else).

## Parsing CSS

The `<style>` tags are parsed out so that we can add a unique prefix to each CSS rule.

For example:

```css
h1 {
  color: teal;
}
```

Turns into:

```css
h1.random-code-abc123 {
  color: teal;
}
```

The unique prefix is added to avoid clashing with CSS rules defined in other components. 

The package [css-tree](https://www.npmjs.com/package/css-tree) is used to walk the CSS and inspect the expressions.

```javascript
import {parse, walk, generate} from 'css-tree'

// parse CSS source to AST
const input = '.example { color: teal }'
const ast = parse(input)
const randomPrefix = 'xyz123'
const selectors = []

// traverse AST and looking for selectors
walk(ast, node => {
  // check if this node is a selector
  if (node.type === 'Selector') {
    // capture this node, so we can modify it later
    selectors.push(node)
  }
})

// modify the AST
selectors.forEach(selector => {
  // add a `ClassSelector` with name `.xyz123`
  // it will turn `.example` into `.example.xyz123`
  selector.children.insertData({
    type: 'ClassSelector',
    name: randomPrefix
  })
})

// generate CSS text from AST
const output = generate(ast)

// print the CSS text
console.log(output)
//> .example.xyz1234{color:teal}
```

## Parsing JavaScript

Svelte parses the `<script>` tags to extract `exports` statements (which are props) and to find reactive statements. 

The JavaScript source code is turned into AST using the [acorn](https://www.npmjs.com/package/acorn) package.

For example, say you define a prop `export let name`. All the `export let` statements can be located by walking the AST with [estree-walker](https://www.npmjs.com/package/estree-walker):

```javascript
import {parse} from 'acorn'
import {walk} from 'estree-walker'

// define source code with 2 exported props
const sourceCode = "export let title, color"

// parse the source code
// enable `sourceType: 'module'` since want to allow exports 
const ast = parse(sourceCode, {sourceType: 'module'})
 
// walk the AST
walk(ast, {
  enter(node) {
    // check if this node is a "named export"
    if (node.type === 'ExportNamedDeclaration') {

      // named exports can have many names, so map the ids
      const props = node.declaration.declarations.map(declaration => declaration.id.name)

      // print 'em
      console.log(`We got props: ${props.join(', ')}`)
      //> We got props: title, color
    }
  }
})
```

## Parsing visual tags

The remaining tags are visual tags like `<h1>`, `<p>`, etc..

Svelte uses it's own tag parser, but you can use [parse5](npmjs.com/package/parse5) to do the same job:

```javascript
import { parseFragment } from 'parse5'

const source = "<h1 class='snazzy'>Hello World!</h1>"
const fragment = parseFragment(source)

fragment.childNodes.forEach(node => {
  console.log(node)
})
```

It outputs:

```javascript
{
  nodeName: 'h1',
  tagName: 'h1',
  attrs: [ { name: 'class', value: 'snazzy' } ],
  namespaceURI: 'http://www.w3.org/1999/xhtml',
  childNodes: [
    {
      nodeName: '#text',
      value: 'Hello World!',
      parentNode: ...
    }
  ] 
}
```

This gives us the complete tree of our HTML document, which we'll use to generate the equivalent JavaScript code.

# Putting it all together

Say we have a simple `.svelte` file like this:

```svelte
<script>
  export let name;

  function handleClick(e) {
    e.preventDefault()
    alert(`Hello ${name}!`)
  }
</script>

<h1 class="snazzy" on:click=handleClick>Hello {name}!</h1>
```

The compiler generate a `.js` that looks like this:

```javascript
// target: this is the target element to mount the component
// props: a list of props, defined with `export let`
export default function component({ target, props }) {
  // code generated to extract the props into variables:
  let { name } = props; 

  // all functions are copied directly from the <script> tag
  function handleClick(e) {
    e.preventDefault();
    alert(`Hello ${name}!`);
  }

  // variables are declared for each element and text node:
  let e0, t1, b2, t3;

  // returns an object with lifecycle functions to create, mount, detach and update the component. 
  return {
    // called when the components is created
    // creates elements/nodes, adds attributes and wires up event handlers
    create() {
      e0 = document.createElement("h1")
      t1 = document.createTextNode("Hello ")
      b2 = document.createTextNode(name)
      t3 = document.createTextNode("!")

      e0.setAttribute("class", "snazzy")
      e0.addEventListener("click", handleClick)
    },

    // called when the component is mounted to the `target`
    // it just appends things
    mount() {
      e0.appendChild(t1)
      e0.appendChild(b2)
      e0.appendChild(t3)

      target.append(e0)
    },

    // called to change the value of props
    update(changes) {
      // check if name changed
      if (changes.name) {
        // update `name` variable and all binding to `name`
        b2.data = name = changes.name
      }
    },

    // called to remove the component from the DOM
    detach() {
      e0.removeEventListener("click", handleClick)
      target.removeChild(e0)
    }
  };
}
```

Now we can mount this component into the DOM:

```javascript
import MyComponent from './component'

// instantiate the component
const component = MyComponent({
  target: document.body,
  props: {name: "World"}
})

// create the nodes
component.create()

// append the nodes into the target
component.mount()
```

## Summary


Svelte is a compiler that parses `.svelte` files, analyzes them and then generates a JavaScript file. The JavaScript file contains the logic to mount the component, handle events, and patch the DOM when values change.

For learning sake, I built a tiny version of the compiler: https://github.com/joshnuss/micro-svelte-compiler
It only does a fraction of the real compiler, but it's a useful learning tool.

**TIP #1**: If you want to see further examples, take a look at the `JS Tab` in the [Svelte REPL](https://svelte.dev/repl).
**TIP #2**: [AST Explorer](https://astexplorer.net/) is a great learning tool for inspecting AST.

Happy coding!

✌

# Want more?

If you'd like to learn more about Svelte, check out my [short video courses](https://joshuanussbaum.podia.com) ✨