Adam Solove
Blog/UI engineering
11 Mar 2017

Preact internals #1: the easy parts

An introduction and gentle first steps in a series where we’ll read the Preact codebase and try to completely understand it

By Adam Solove · Published 11 Mar 2017 · Reading time 8 min

About the series

I assume you are already familiar with the React component model and virtual dom rendering. A first React book or online course should be enough background. I also assume you know what Preact is and are interested in it either to make your applications load faster or just because you have a better chance of understanding its implementation than reading the whole React codebase. I won’t try to sell you on Preact, and this is the last bit of marketing in the series:

Preact is the fast 3kB alternative to React with the same ES6 API.

I don’t assume any familiarity with how (P)react might be implemented: learning that is the point! At suitable moments I may also link to the React codebase for comparison, but understanding React’s much more ambitious implementation is way outside of our scope.

We’ll read through the code in roughly the order of its internal dependencies, covering utilities and public interfaces first, and then later diving into the guts that depend on them. For me, the most surprising part of reading Preact’s source was finding how much of it was small, understandable files. And after getting used to that, coming face to face with the two big complicated files that do most of the work. I won’t lie: I got spooked a couple times, but I’ll try to ease into the complexity.

In today’s first installment we’ll cover the really easy parts:

Where the code lives

The interface that Preact implements from React, though quite convenient to use, is fairly confusing to think about implementing. Many of the dependencies and call stacks are circular: components render virtual nodes that describe yet more components, “render” sometimes means generating virtual nodes and sometimes means actually touching the DOM, and the implementation of the Component lifecycle is spread across several different modules.

Yet, for all the complexity of holding this in your head, most of the implementation is made up of small, clear pieces. So before we dive into the code, it might help to try to get the lay of the land:

Try to keep that outline in mind. Now we’ll dive in to reading real code with the simplest but most-frequently-imported part of Preact.

Utility functions

The src/util.js file defines helper functions needed elsewhere in the codebase. Defining them locally might seem odd. In an application codebase you might want to just import these helpers from a package like lodash. For a library, though, having our own versions means we can leave out handling edge cases we don’t care about, avoid worrying about dependencies, and make our code as small as possible.

I encourage you to read the whole file. You may recognize many of the functions, like extend and isFunction. Others are worth remembering because we’ll encounter them later on:

There: we’ve made it through our first real file in the Preact source, and nothing too bad so far. That’s it for Preact’s utility functions.

Global options and devtools integration

The src/options.js file exposes Preact’s global options. Only a few of these options are useful to regular applications, but they come up a few times in the library’s implementation, and especially in its devtools integration.

What global options does Preact support?

These global option callbacks are not intended for use in application code. They’d just make for a maintenance and testing nightmare. But they are very useful for Preact internally to interoperate with the React Devtools, a browser extension that lets you inspect a running React app.

The React Devtools work by integrating deeply with React internals like the shape of components, the interface of the reconciler, etc. Rather then recreating the Devtools for Preact, the integration in devtools/devtools.js works by implementing facades for the bits of React that the Devtools need. The exact details of that code is beyond the scope of this series, since it mostly has to do with React internals. But I do want to point out the initialization code at the bottom, which uses the global options callbacks to do things like normalize Preact vnodes into the expected shape and to notify the interface of changes to rendered components.

In your own applications, you may want to consider setting the options related to asynchronous rendering. But unless you’re working on the Preact codebase itself, leave the global callbacks alone.

With the frequently-imported utilities and options out of the way, let’s look at the first and simplest step in actually rendering things to the screen:

How JSX becomes virtual nodes

Our components need to return virtual nodes to describe the DOM they want to render. Although it isn’t required, the JSX syntax for creating virtual DOM trees is pretty convenient and widely used. Because browsers don’t understand JSX, there are two steps between it and a real, in-memory virtual DOM tree:

  1. Our code is compiled down to plain JavaScript where the JSX is replaced by calls to a function for constructing virtual DOM nodes (or “vnodes”)
  2. At run time, the function calls create a tree of vnodes that describe the HTML we want to render.

Compiling JSX down to plain JavaScript is most commonly done with Babel. To see how that works, here’s an example of code before and after Babel compiles away the JSX:

Notice that our angle-bracket JSX has been turned into function calls of the form:

h(nodeName, attributes, …children)

By default, Babel assumes that JSX will be interpretted by React, so it calls a function calledReact.createElement. Our Preact code won’t have such a function. By placing the pragma /** @jsx h **/ at the top of the file (or configuring it globally in our build process), we can change the function name that’s used. Preact’s vnode construction function is named h , so that’s what we use here.

But what is a vnode? Its definition in src/vnode.js is quite simple: a vnode must have a nodeName, which is either a string (for normal html elements) or a function representing a component. It can optionally also have an object of attributes, an array of children, and a key.

Preact’s src/h.js file exports a function for constructing vnodes that looks complicated but is actually quite simple. It’s easier to understand if I rewrite it in two pieces: first, the overall structure of the file; second, the special-case handling for processing the vnode’s children:

Notice a few special cases in the handling of the children:

I highly recommend reading the thorough and very legible test cases for this module.

And that’s all it takes for JSX to become virtual dom nodes. Just to review:

Stay tuned for future installments

In the next post, we explore what makes the React component model so useful, explore the differences between stateful and functional components, and build a mental model of how the implementation works.

And then in part three, we look at the DOM manipulation helpers used during reconciliation.


Originally published on Medium.