As I keep playing around with Svelte, I keep being surprised how reactive it feels. In this article, we’ll take a quick glance at the Svelte internals to see how Svelte accomplishes this under the hood.

This is important to know, because we can use this knowledge to unlock the potentials of Svelte in combination with RxJS, without all the overhead, to end up with a truly reactive architecture. When we have a better understanding of the internals, we’ll go through some examples to take a look at the possibilities.

A Svelte component

To take a look at the internals we need a small demo application, and for this article, we have a simple counter that increments after each second.

REPL
<script>
  let tick = 0
  setInterval(() => {
    tick += 1
  }, 1000)
</script>

{ tick }

To know how Svelte compiles the above code, let’s have a look at it. In the compiled code we see that Svelte wraps the increment assignment with an [$$invalidate](https://github.com/sveltejs/svelte/blob/master/src/compiler/compile/render_dom/invalidate.ts) method. This method tells the component that the value of tick has changed, and it will flag the component as “dirty”. Because of this, the component knows has to update.

/* App.svelte generated by Svelte v3.18.2 */
import {
  SvelteComponent,
  detach,
  init,
  insert,
  noop,
  safe_not_equal,
  set_data,
  text,
} from 'svelte/internal'

function create_fragment(ctx) {
  let t

  return {
    c() {
      t = text(/*tick*/ ctx[0])
    },
    m(target, anchor) {
      insert(target, t, anchor)
    },
    p(ctx, [dirty]) {
      if (dirty & /*tick*/ 1) set_data(t, /*tick*/ ctx[0])
    },
    i: noop,
    o: noop,
    d(detaching) {
      if (detaching) detach(t)
    },
  }
}

function instance($$self, $$props, $$invalidate) {
  let tick = 0

  setInterval(() => {
    $$invalidate(0, (tick += 1))
  }, 1000)

  return [tick]
}

class App extends SvelteComponent {
  constructor(options) {
    super()
    init(this, options, instance, create_fragment, safe_not_equal, {})
  }
}

export default App

The rest of the component’s code is mostly untouched. The code can be seen in the instance method. There’s also the create_fragment method which binds the variables to the view.

It’s possible to mimmick this update behavior by creating a reactive statement. A reactive statement will be executed when one of its dependant values has changed. You can create one by simply adding a $: prefix to the statement.

REPL
<script>
  let tick = 0
  setInterval(() => {
    tick += 1
  }, 1000)

  $: console.log(tick)
</script>

{ tick }

The compiled output of the instance wraps the console.log within the update lifecycle hook of the component.

function instance($$self, $$props, $$invalidate) {
  let tick = 0

  setInterval(() => {
    $$invalidate(0, (tick += 1))
  }, 1000)

  $$self.$$.update = () => {
    if ($$self.$$.dirty & /*tick*/ 1) {
      $: console.log(tick)
    }
  }

  return [tick]
}

A svelte store

Now that we know how a value gets updated, we can take it a step further by creating a Svelte Store. A store holds state and is typically used to share data between multiple components.

What’s interesting for us, is that a store is subscribable. The most important piece of the contract of a store is the subscribe method. With this method, the store can let all the consumers know that its value has changed. With this, we can set up a reactive push-based architecture for our applications.

In the implementation below, a custom store is created with the initial value of 0. Inside the store, there’s an interval to increment the store’s value after each second. The store doesn’t return a value, but it returns a callback method that will be invoked when the store’s subscription is destroyed. Inside this callback method, we can put teardown logic. In our example, we use the callback method to clear the interval timer.

REPL
<script>
  import { writable } from 'svelte/store'

  let tick = writable(0, () => {
    let interval = setInterval(() => {
      tick.update(value => value + 1)
    }, 1000)

    return () => {
      clearInterval(interval)
    }
  })

  let tickValue = 0
  tick.subscribe(v => {
    tickValue = v
  })
</script>

{ tickValue }

To update the view, we create a new variable tickValue and we use the subscribe method on the store to increment tickValue when the store’s value has changed.

If we take a look at compiled output now, we see that it hasn’t changed. Just like the first example, Svelte will just wrap the assignment of tickValue with the $$invalidate method.

function instance($$self, $$props, $$invalidate) {
  let tick = writable(0, () => {
    let interval = setInterval(() => {
      tick.update(value => value + 1)
    }, 1000)

    return () => {
      clearInterval(interval)
    }
  })

  let tickValue = 0

  tick.subscribe(v => {
    $$invalidate(0, (tickValue = v))
  })

  return [tickValue]
}

Because Svelte is a compiler, it can make our lives easier. By using the $ again, and by prefixing the store variable in the HTML, we see that the store’s value will be printed out after it has changed. This is magic! It means that we don’t have to create a variable if we want to access the store’s value.

REPL
<script>
  import { writable } from 'svelte/store'

  let tick = writable(0, () => {
    let interval = setInterval(() => {
      tick.update(value => value + 1)
    }, 1000)

    return () => {
      clearInterval(interval)
    }
  })
</script>

{ $tick }

So far, we’ve seen nothing special with the compiled output of the component. But if we take a look now, we can see new internal methods, and that the code of the component instance has been modified.

/* App.svelte generated by Svelte v3.18.2 */
import {
  SvelteComponent,
  component_subscribe,
  detach,
  init,
  insert,
  noop,
  safe_not_equal,
  set_data,
  text,
} from 'svelte/internal'

import { writable } from 'svelte/store'

function create_fragment(ctx) {
  let t

  return {
    c() {
      t = text(/*$tick*/ ctx[0])
    },
    m(target, anchor) {
      insert(target, t, anchor)
    },
    p(ctx, [dirty]) {
      if (dirty & /*$tick*/ 1) set_data(t, /*$tick*/ ctx[0])
    },
    i: noop,
    o: noop,
    d(detaching) {
      if (detaching) detach(t)
    },
  }
}

function instance($$self, $$props, $$invalidate) {
  let $tick

  let tick = writable(0, () => {
    let interval = setInterval(() => {
      tick.update(value => value + 1)
    }, 1000)

    return () => {
      clearInterval(interval)
    }
  })

  component_subscribe($$self, tick, value => $$invalidate(0, ($tick = value)))
  return [$tick, tick]
}

class App extends SvelteComponent {
  constructor(options) {
    super()
    init(this, options, instance, create_fragment, safe_not_equal, {})
  }
}

export default App

In the compiled output, we see the new component_subscribe method. To know what it does, we can take a look at the source code.

#svelte #rxjs #reactive

Unlocking reactivity with Svelte and RxJS
1.50 GEEK