Skip to content

How to Create Your Own Custom Renderer in SolidJS

Intro

In this article, we will explore what custom renderers are, the problem they solve, and how SolidJS has created a custom renderer that is quite unique because of its simplicity and ease of use.

We will also learn how to create a custom renderer in SolidJS, and how to use it in our applications. Custom renderers have a long history with all the major frameworks in the market like Renderer2 in Angular, createRenderer in Vue, and React Reconciler in React.

They are used to build awesome technologies and mind-blowing libraries. SolidJS recently added the ability to create a custom renderer with Solid Universal Renderer.

Note: We also have a great article about How to create a custom renderer for React that you can check out!

Custom Renderers in SolidJS

To better understand how custom renderers in SolidJS work, we have to understand it first in React and Vue (VDOM frameworks). In React, the library itself (React.js) doesn’t know anything about the DOM. It doesn’t understand what a div or h1 means. React converts all of the components into a JavaScript object that represents the UI in the memory (Virtual DOM), and then update the related nodes when the state changes in specific components.

That’s it! This is how React and Vue work under the hood. The React Renderer (which is a different package called “react-dom”) then works on rendering this Virtual DOM on the screen, and updates it when it receives updates from React.js. The same thing happens in Vue.

From here, the custom renderers have come to live. You aren’t limited to using only the official Renderer for the framework, but you can also build your own.. We will learn more about them in the proceeding sections. But for now, you just need to know that there is no virtual DOM in SolidJS. But it modifies directly on the DOM. This is the only difference between the Custom Renderers in SolidJS and React or Vue.

Real-world Custom Renderers

In this section, I’ll show you the most popular custom renderers that are used in many production applications in different frameworks. One of the most popular custom renderers in the industry is React Native (yes, it’s a React custom renderer for Android and iOS platforms). React-three-fiber is one of the most famous examples of React Reconciler in production. It converts the JSX VDOM to WebGL graphics. React-pdf is a React renderer for creating PDF files on the browser and server. TroisJS is a Vue custom renderer for Three.js and WebGL. There are many, many more examples.

HTML Nodes

Everything you see on the screen in the web browser is a node; the HTML element is a node, the attribute of that HTML element is a node, for example:

<img src="logo.png" /> // <- this node’s type is an element
	// ^
	// this attribute is a node

Also, the text between and opening and closing tags of this HTML element is a node:

<h1>  // <- this h1 is type element
	Hello world  // <- this text is type text and it's a separate node
</h1>

Let’s code a SolidJS custom renderer

The process is going to be similar to React, but SolidJS Universal is even easier than React Reconciler because it has fewer options.

Create a new file to write our renderer and import the createRenderer from SolidJS universal:

import { createRenderer } from 'solid-js/universal';

Second, let’s create an empty renderer:

const renderer = createRenderer({
	// Add the renderer properties here
})

Renderer Options

Let’s break all of these options down in detail:

createElement

This option is responsible for handling the creation of a new element node. It takes only one parameter which is the element type and it could be something like h1, p, divetc

createElement(type) {
    return document.createElement(type);
}

And here we can play with the JSX elements for fun. For example, we can add our custom elements like:

function component() {
	return (
    <div>
	    <customLikeButton />
    </div>
	)
}

Technically, there is no HTML component called customLikeButton. But here in this option, we can resolve this type as we desire.

createTextNode

This option is responsible for handling creating a new text node. It takes only one parameter which is the text itself.

createTextNode(text) {
    return document.createTextNode(text);
}

replaceText

This option is responsible for handling updating the text node when it gets updates. It takes two parameters; the actual text node that has been updated, and the new text.

replaceText(node, text) {
    node.data = text;
},

insertNode

This option is responsible for inserting or injecting a new node in the UI and the DOM. It takes three parameters, the parent node, the node itself, and a reference node (anchor) in the parent node.

insertNode(parent, node, anchor) {
    parent.insertBefore(node, anchor);
}

removeNode

This option is responsible for removing a node from the DOM. It takes two parameters, the parent node, and the node which needs to be removed

removeNode(parent, node) {
    parent.removeChild(node);
}

setProperty

This one is a little bit more complicated than the previous ones because it handles more categories. It is responsible for handling the attributes or properties of the element such as the classNames, style, and events like onClick and onSubmit.

setProperty(node, name, value) {
    if (name === "style") Object.assign(node.style, value);
    else if (name.startsWith("on")) node[name.toLowerCase()] = value;
    else if (["classNames", "textContent"].has(name)) node[name] = value;
    else node.setAttribute(name, value);
  }

isTextNode

This option is used internally to determine if this node is a text node or not.

isTextNode(node) {
    return node.type === 3;
}

Note: the node.type in the line above is a pure JavaScript code. In JavaScript nodes have 12 types with 12 numbers each representing a type. For example, 1 represents an element node, and 2 represents an attribute node. For more info see the HTML DOM Element nodeType on W3Schools and Node.nodeType - Web APIs | MDN.

getParentNode

This option is being used internally in SolidJS renderer to get the parent node of a given node.

getParentNode(node) {
    return node.parentNode;
}

getFirstChild

This option is being used internally in SolidJS renderer to get the first node of a given parent node.

getFirstChild(node) {
    return node.firstChild;
}

getNextSibling

This option is being used internally in SolidJS renderer to get the next node of a given node in the DOM tree.

getNextSibling(node) {
    return node.nextSibling;
}

Renderer method

There are a lot of methods for this renderer, but the most important one is render which we will replace the SolidJS web one with.

import renderer from "./renderer";

renderer.render(() => <App />, document.getElementById("root"));

There are additional methods available:

  • effect
  • memo
  • createComponent
  • createElement
  • createTextNode
  • insertNode
  • insert
  • spread
  • setProp
  • mergeProps

The performance of SolidJS Universal Renderer

Ryan Carniato, the creator of SolidJS, created a test on the SolidJS Universal Renderer in his stream of Benchmarking and Custom Renderers on his YouTube channel, and it was pretty interesting. It wasn’t much slower than the SolidJS web renderer at only a few milliseconds slower. To test it, you can use the js-framework-benchmark repo, which is an amazing tool you can use to compare the performance of different JavaScript frameworks.

Conclusion

In this article, we learned about the custom renderers in SolidJS and how to create one. We also learned about the performance of SolidJS Universal Renderer and how it compares to the SolidJS web renderer. I hope you enjoyed this article and learned something new. For more SolidJS resources, check out our solidjs.framework.dev where you can find all cool courses, tutorials, and libraries for SolidJS. Also you can create your next SolidJS project, try our SolidJS starter kit from Starter.dev it has a lot of tools pre-configured for you. If you have any questions or suggestions, please feel free to send them to us or reach out to us on Twitter.