The basic structure of Frontend frameworks

Will T. - Jun 15 '22 - - Dev Community

This is a series about exploring Frontend frameworks' internals. Its purpose is to shed some light on the way these frameworks operate under the hood. It may not be suitable for beginners or those who don't often need to know what's happening underneath their code.

Motivation

Depending on which land you come from:

  • Vue 3: Have you ever wondered why watchEffect can magically re-run every time the reactive states inside it change without explicitly being told? In React, you have to explicitly tell useEffect of its dependencies.
  • React: Have you ever wondered why stale states/closures happen when using hooks? Why does the order of hooks matter and you are forced to always use Hooks at the top level of your React function?
  • Solid: Why does this React-like component function only run once and still manage to have the same (or even better) functionalities? Why is the signal getter a function? And why the hell is it so fast?
  • etc.

If you have asked yourself such questions and want to dig deeper to find the answers, you will find this series interesting.

This series will tap into the internals of these libraries/frameworks, part by part, until we can understand and build a simplified version of each library/framework by ourselves. We might not be able to reach that end goal, but we'll surely learn a lot in the process.

And in this first part of the series, let's look into the basic structure of Frontend frameworks.

The basic structure of Frontend frameworks

The currently popular Frontend frameworks can be divided into two categories: those with Virtual DOM and those without.

First, we'll go with the Frontend libraries/frameworks that use Virtual DOM (e.g. Vue, React). The basic structure of these frameworks can be depicted as such:

The basic structure of Frontend frameworks with Virtual DOM

At a high level, the roles of each unit in the structure is as follows:

  1. Compiler: Templates or JSX are fed into the compiler, which then outputs render function code.
  2. Virtual DOM Renderer/Reconciler: This unit is in charge of invoking the render functions and managing the virtual DOM tree (e.g. "reacting" to state changes and updating the virtual DOM accordingly). React calls this component the reconciler (the source code is here), while Vue calls it the runtime renderer. It is called the reconciler because one of its main job is to perform Virtual DOM "diffing", or "reconciliation".
  3. Reactivity (State): This unit handle reactivity and states in each component and throughout the whole application. It is often decoupled from every other unit in this structure, which is why you can create reusable "hooks" without the presence of any render functions.
  4. Native Renderer: This unit "takes render instructions" from the virtual DOM renderer/reconciler and then decides how to actually render according to its targeted environment. For example, if its target is the Web, it will render and update the actual DOM. When targeting the Web, Vue uses @vue/runtime-dom which is "abstracted away" most of the time, and React uses react-dom which is not abstracted away and requires you to explicitly import and use it. Both allow creating a custom renderer when targeting non-DOM environments:

What about the frameworks that don't use Virtual DOM? Well, the virtual DOM renderer/reconciler and the virtual DOM tree simply no longer exist:

The basic structure of Frontend frameworks without Virtual DOM

There's a few points worth noting here:

  1. Don't let the directions of the arrows fool you. Looking at the diagram above, you might think that the reactivity unit needs to directly call the renderer, and the renderer needs to know about the methods of the reactivity system. But that's not how reactivity works. We'll continue to explore and understand this later in the series.

In fact, you can completely replace the reactivity system with another without changing the renderer and vice versa. For example:

  1. The template/JSX unit, the compiler, and the native renderer are the ones that will most definitely be affected when the targeted environments change. For example, you can't write <div>Hello World</div> in the template/JSX when your targeted environment is Canvas or native mobile apps.
  2. This is still a simplified structure. For instance, we haven't taken into account the scheduler, which is a very important part of these frontend frameworks. Later in the series, when we've discussed the motivation behind a scheduler, we'll have an updated diagram which includes the scheduler.

Now that we've explored the basic structure of frontend frameworks, we'll proceed to explore each of the units inside the structure for each library/framework. My plan is to explore those aforementioned units in Vue 3 as it is currently the framework I'm using extensively for my projects (though I have also used React, Solid, and Svelte to a large degree and I do plan to include them in this series).

Conclusion

In this part of the series, we've explored the basic structure of Frontend frameworks. In the next part, we'll continue to explore Vue 3's reactivity with an oversimplified versions of ref and watchEffect.

I hope that you find this useful somehow. If you have any suggestion or advice, please don't hesitate to reach out to me in the comment section.

. . . . . . . . . . . .