Skip to main content Accessibility Feedback

Reef version 13 just launched

Over the weekend, I released version 13 of Reef, my tiny state-based UI library.

Unlike most UI frameworks, Reef…

  • Weighs just 2.6kb minified and gzipped, with zero dependencies.
  • Supports simple templating with JavaScript strings or template literals. No JSX!
  • Can be loaded with a <script> element or ES module import. You never need to touch command line to use it.
  • Includes just a handful of small utility libraries.

The goal of the project was to provide the benefits of DOM diffing and reactive data with an authoring approach that’s as close to vanilla JS as you can get.

A quick demo…

Reef is a tiny utility library with three core functions: signal(), render(), and component().

Create reactive data with the signal() method. Pass in any value, and Reef will emit a reef:signal event whenever you update it.

// Create a signal
let data = signal({
	greeting: 'Hello',
	name: 'World'
});

// Emits a reef:signal event
data.greeting = 'Hi';

Safely render UI from an HTML string with the render() method. Pass in an element (or element selector) and your HTML string. Reef will sanitize your HTML, then diff the DOM and update only the things that are different.

let name = 'world';
render('#app', `<h1>Hello, ${name}!</h1>`);

Automatically update your UI when data changes with the component() method. Pass in an element (or element selector) and a template function. Reef will listen for reef:signal events and and automatically run the render() function.

// Create a signal
let data = signal({
	greeting: 'Hello',
	name: 'World'
});

// Create a template function
function template () {
	let {greeting, name} = data;
	return `<p>${greeting}, ${name}!</p>`;
}

// Create a component
// Renders into the UI, and updates whenever the data changes
component('#app', template);

// The UI will automatically update
data.greeting = 'Hi';
data.name = 'Universe';

Try this demo on CodePen →

What’s new in version 13?

The authoring experience in version 13 is very similar to previous versions, but includes a few breaking changes.

  • The signal() method was previously called store(). I changed to the function name to better align with industry norms.
  • The reef:signal event now passes along the name and value of the updated property rather than the entire data object. This is useful for selectively making updates to the UI based on which data has changed.
  • There’s a built-in data store (like Redux). It was previously called setter(), but has been renamed store() to more accurately describe what it is and does.
  • When using on* event listeners, you can no longer pass in arguments. This was done for XSS attack/security reasons. I still recommend event delegation in most cases.
  • I added a focus() method that can be used to set focus on an element after a UI render happens. This can improve accessibility in certain (limited) situations.
  • Changes were made to the render() method’s DOM diffing under-the-hood to improve performance.
  • You can now render elements into the UI, and then exclude them from being diffed later with a [reef-ignore] attribute. This is useful when blending state-based UI with traditional DOM libraries.
  • I added support for a [key] attribute. This is useful for more efficient DOM diffing when creating unique, valid IDs is difficult.

How’s performance?

I generally don’t recommend building entire UIs with JavaScript if you can avoid it. I view Reef (and other state-based UI libraries) as a progressive enhancement in most cases.

However…

I decided to run a performance test against Preact for this version, since it’s React-like, but more performant.

I used the CDN-hosted ES module versions of both libraries for the test, and used the performance.now() method to test how long an identical set of tasks takes each library.

For the test, I generated a table with 10,000 randomly generated rows. Rather than generating them all at once, I’d generate 1,000 rows at a time, and periodically swap 500 rows around or delete 125 rows to force the libraries to diff the DOM frequently.

Many UI libraries will batch multiple updates into a single render for performance reasons (Reef does), so I ran two versions of the test:

  1. All of the tasks run in sequence. This generally results in one single render of the UI.
  2. A 500ms delay between each task to trigger lots of diffing and re-rendering of the UI.

In most cases, Reef was faster than Preact.

In Chromium, For an initial UI render, Reef was 3x faster than Preact at about 300ms. With all of the delays and diffing, Reef was 1 second faster, completing all of the tasks in about 9.5 seconds versus 10.5 for Preact.

In Firefox, Preact was 1 second faster at rendering the delayed tasks with lots of diffing. In WebKit, Reef was again faster.

You can get the code and run your own tests here.

My suspicion is that Chrome and WebKit have done more JS rendering engine optimization than Firefox. Jason Miller, who built Preact, has an incredible knowledge of how browser rendering engines work under-the-hood and has done a lot to optimize for them. It’s wild how nearly identically Preact renders in every browser.

All that said, I personally think Reef is fantastically performant, to the point that I’d have zero hesitation recommending it for professional projects at this point.

If you want to learn more about Reef, head over to ReefJS.com.