DEV Community

Cover image for A few reasons why I love Solid.js
Joe Pea
Joe Pea

Posted on

A few reasons why I love Solid.js

Solid is such an amazing reactive declarative tool for composing and manipulating DOM with simple reactive declarative templates! By far the best component system that currently exists for web. The Solid dev experience is really good, and Ryan is so meticulous with performance: you'll be on the shoulder of a giant that can go anywhere.

Here are some reasons why.

The Solid playground sets a new high bar for all other frameworks by not only making it very easy to share snippets of how to do things with Solid, but by explaining (via it's compile output) why and how Solid is super fast at reactive templating despite the clean and simple declarative API.

Solid's magic is right here in plain sight, in the visible compile output of any playground example:

https://playground.solidjs.com

This sort of openness is the type of thing that can change a technology segment for the better.

Not only is what you see in the playground open, but Solid openly invites reactive library developers to challenge boundaries: Solid's JSX compiler allows reactive authors to power JSX templates with their own reactive primitives. This invites any author to challenge Solid at its own speed, and sets up a foundation for open innovation. See that here:

https://github.com/ryansolid/dom-expressions

Solid effectively changes the component world because it is so open, that other frameworks will have no choice but to adapt or else fall behind in the dust of innovation. Just like React was impactful in it's debut, Solid is the next big move in how reactive declarative UI trees can be manipulated with raw speed while not sacrificing dev experience.

As an example of no sacrifice of dev experience, in Solid we can animate anything declaratively by passing animated values directly into templates and modifying any state as we wish, even in rapid animation loops (like loops found in games and other rich experiences).

In contrast, animating state or props in an animation loop in a React component is considered a bad practice and can easily lead to performance issues. For example, react-three-fiber (React components that render with Three.js for 3D experiences such as games) mentions specifically not to do this in it's Performance Pitfalls guide:

Never, ever, setState animations!
Avoid forcing a full component (+ its children) through React and its diffing mechanism 60 times per second.

Solid makes declarative templating a first class citizen without the performance caveats. Write everything declaratively and rest assured it'll be compiled to an essentially-vanilla fast equivalent. Animate props at 60fps as much as you want in Solid!

All parts of Solid are independently reusable, which makes it possible to build a variety of different types of projects with it, and due to the simplicity of Solid's reactive system it is very easy to hook any other state system into Solid components. For example see how simple Storeon's Solid bindings are:

https://github.com/storeon/solidjs

In contrast, one can not independently import React's state system and use it standalone, and one often has a difficult time integrating external state systems into React components (just ask the Mobx team what sorts of problems they faced with double rendering, for example).

On top of things being more difficult in React, they are simply more verbose and difficult to understand with the strange Hooks rules that often trip up newcomers in a way that is much less than desirable. You'll write more in React, and you'll have less-understandable code.

Solid is very modular: one can use its reactive primitives while skipping out on declarative templating (for example) to create a reactive state machine, or to create a reactive backend server, both of which could have nothing to do with making UIs. Such projects only need to import APIs like createSignal, createStore, or createMutable and leave everything else behind.

In Solid, DOM is a first-class citizen: the DOM is not hidden behind an abstract virtual dom and therefore is fully accessible. It's just DOM! JSX expressions give you elements exactly as you would intuitively expect, which means it is very easy to interop with any DOM library you can think of. In the following example we simply pass a div created from a JSX expression to jQuery, while the content of the div's template is reactive:

// Make a reactive variable (signal):
const [count, setCount] = createSignal(0)

// Increment the count value every second:
setInterval(() => setCount(count() + 1), 1000)

// Use count in a template:
const div = <div>The count is: {count()}</div>

// The JSX expression gave us back the *real* div element,
// now we can pass it to jQuery or any other DOM API:
jQuery(div).whatever()

console.log(div instanceof HTMLDivElement) // true!

// Even compose the DOM:
const info = <section>Info: {div}</section>

console.log(info instanceof HTMLElement) // true!
Enter fullscreen mode Exit fullscreen mode

You see! The div is... an actual div! It's just DOM! This makes things easy! We have two benefits here:

  1. We simply got a div and can do any regular DOM thing we want with it.
  2. The div's content is updated automatically any time the value of count changes.

We get the best of both worlds: DOM and reactive declarative templating, all in one!

Because of Solid's simple reactive and fast templating, plus the fact that it's just DOM!, Solid is the perfect fit for use with custom elements or any other DOM-based project.

To contrast, LitElement's lit-html template expressions don't return DOM back to you. Lit is a DOM library that gets in the way more than it should. For example:

import {html} from 'lit-html';

const div = html`<div>Hello World</div>`;

console.log(div instanceof HTMLDivElement) // false!

jQuery(div).foo() // ERROR
Enter fullscreen mode Exit fullscreen mode

As an example of how Solid fits well with DOM projects, LUME Element, a system for making custom elements in a simple and concise way with reactive templating, uses Solid at its core:

http://github.com/lume/element

This results in being able to make custom elements with the speed of vanilla JS, without sacrificing experience, without the more difficult-to-maintain imperative code that would otherwise be required with initial plain vanilla JS.

LUME's 3D Webgl-powered HTML elements are simple, reactive, and fast (despite being written declaratively, because declarative templating should not ever be a performance issue!) thanks to Solid underneath.

Here's a 3D WebGL scene written in HTML:

https://codepen.io/trusktr/pen/dypwZNP

(LUME is still alpha, please complain about everything. :)

TLDR: Solid is the currently the best way to make composable UI components without sacrifice of dev experience (without limitations on when to use declarative templating, without complicated function scope rules, without unneeded verbosity). The API will be very easy to work with for anyone who knows DOM and would like to integrate with existing applications (f.e. legacy jQuery applications).

All this with the most speed as a bonus!

Knowing Ryan, he will keep Solid at the bleeding edge of performance and ability.

Like SSR for SEO and fast loading? Solid's has you covered.

Like TypeScript? Solid has you covered.

Like to write plain HTML with no build system? Solid has you covered! You can use the html template tag instead of JSX. Here's an example on CodePen:

https://codepen.io/trusktr/pen/eYWNrMJ

import html from 'solid-js/html'
import {createSignal} from 'solid-js'

const name = createSignal('Amadar')

// It's just DOM!
const div = html`<div>Hello name is ${name}</div>`

// ... change name later ...

jQuery(div).foo() // It works!

// Even compose the DOM:
const card = html`<section>Profile: ${div}</section>`

console.log(card instanceof HTMLElement) // true!
Enter fullscreen mode Exit fullscreen mode

You need routing? You're covered:

https://github.com/rturnq/solid-router
https://github.com/mduclehcm/solid-router

You need CSS? You're covered:

https://github.com/solidjs/solid-styled-components
https://github.com/solidjs/solid-styled-jsx
Emotion has been ported to Solid (link not available yet)

Need a way to bootstrap a starter Solid application? There you go:

https://github.com/solidjs/solid-start

Solid is just too good, and it is all true!

Discussion (19)

Collapse
lexlohr profile image
Alex Lohr

What I like the best about SolidJS is that its output is predictable. You don't get that with most transpiled libraries.

Collapse
trusktr profile image
Joe Pea Author • Edited on

Yeah, it's almost like hand-written DOM code. Of course it has some of the needed machine-generated parts for variables, etc, but basically it is easy to understand. I think it can be improved though.

Solid was inspired by Surplus. I think Surplus's output is incredibly nice, essentially like human written after compile:

github.com/adamhaile/surplus

I actually learned about this template compilation concept from Surplus, and it makes so much sense seeing Surplus output (that's how I always explain the concept to people, even for Solid, because it makes sense to people who write DOM code the same way manually, the code is very similar to regular hand-written DOM code).

I believe Solid's output got more complicated for two reasons:

  1. due to the performance improvements it implements
  2. The fact that Solid's dom-expressions hooks allow for any reactive fine-grained library to be used for the reactive backing of the JSX templates, so the output has to be generic.
    • For example, see here for Mobx- and Knockout-backed JSX and html template tags: github.com/ryansolid/dom-expressio...
    • Solid JSX is just one flavor, and it happens to use Solid's own reactive primitives, but any primitives other libraries can be hooked in.

So, I knew Solid was the next step already having Surplus in mind, but Surplus really helped nail the concept with it's ultra simple output.

@ryansolid If the output was friendlier, people could also more easily learn how to write imperative code from it. For example, instead of using particular JSX-only APIs like insert, what would the output look like if we wrote the imperative equivalent by hand using only the documented reactive APIs? Would it be possible to do this, while keeping performance?

Also, can we make the dom-expressions hooks be compile time instead if runtime, so that we can also make the output cleaner that way? In that case, default output would be solid specific and as simple as possible.

Collapse
ryansolid profile image
Ryan Carniato

The short answer is no. You'd bloat out component size significantly. There is runtime code here specific to handling the loose nature of JavaScript. There is normalization that needs to happen. The helpers are written to reduce closures.

I'm unsure what you mean by compile time vs runtime here. Dom Expressions internals are all replaced by Solid's by the time the output is bundled by Solid. The build I do before it is published to NPM makes everything Solid specific.

So other than perhaps more readable variable names this already where it should be.

Thread Thread
lexlohr profile image
Alex Lohr

If the code is gzipped, it should make hardly any difference. But it still bloats the code so reading it takes more time, so I applaud your decision.

Thread Thread
trusktr profile image
Joe Pea Author

I see yeah, code re-use through functions is the way to go. What I'm curious about, but I think the answer is no, is if you removed dom-expressions and had only specific functions directly in solid-js and no moduleName option for the Babel preset for example, would it make any difference? Would it be effectively identical to what dom-expressions already requires anyway? Or is there something you would remove if it were solid-specific and not allowing anyone else to hook in?

Thread Thread
ryansolid profile image
Ryan Carniato

Some compiler options that I don't use. But almost nothing on the runtime side. The core file that I use to compile it to the specific libraries is tuned to Solid's API to the point that it basically just disappears in Solid's build step. That isn't true 100% for the others but I mean it's like an extra function wrapper etc..

Dom Expressions almost expects the runtime to be overridden for those custom cases like say MobX supporting class components. It provides a differenct createComponent function. I'd never do anything to compromise Solid's size or performance. The most awkward thing might be some redirection of shared Hydration state with the libraries being separate but I think I have that dialed pretty nice as well. Maybe the way I shadow imports. Like I expose them automatically through solid-js/web when they exist in solid-js but in the end bundled program again that all disappears.

Collapse
lexlohr profile image
Alex Lohr

I think imperative code would be longer and thus would take more time to read. Also, you can find insert here: github.com/ryansolid/dom-expressio...

It's pretty straightforward, you really don't want to expand that.

Thread Thread
trusktr profile image
Joe Pea Author

Yeah, you're right that I can just look up the functions to see what they do. I think that seeing them inlined in Surplus output was what really made it magical, as if I'd written that code already (thinking of the JSX mapped directly to the DOM code). Functions are good for code re-use to minimize size of course.

Would removing the generic nature of dom-expressions' ability to cater to any reactive framework make Solid's specific output simpler?

Thread Thread
svicalifornia profile image
Shawn Van Ittersum

Please don't remove the ability to use dom-expressions with other libraries. Some of us prefer MobX and Ryan's integration mobx-jsx


github.com/ryansolid/mobx-jsx

Thread Thread
lexlohr profile image
Alex Lohr

I don't think there will be changes to DOM-expressions. If so, there would more likely be a fork into the solid-project.

Some of us have been discussing writing a more optimized transpiler exclusively for DOM expressions' JSX flavor written in rust or Go.

Thread Thread
svicalifornia profile image
Shawn Van Ittersum

Using Rust (for WASM?) sounds like a great idea.

Sorry if this is off-topic, but it seems like a Rust implementation might also enable (down the road) a port of dom-expressions to native app components, as an alternative to React Native. Has that been considered yet?

Would there be any interest in extending dom-expressions to server as a wrapper around native component solutions such as Tabris?

github.com/eclipsesource/tabris-js
npmjs.com/package/tabris-component

Thread Thread
trusktr profile image
Joe Pea Author

Stuff like that in Rust already exists. Sycamore is an example, and there are some others too.

Thread Thread
trusktr profile image
Joe Pea Author

Sidenote, I started asdom for AssemblyScript:

GitHub logo lume / asdom

Use DOM APIs in AssemblyScript

asdom

Use DOM APIs in AssemblyScript (TypeScript compiled to WebAssembly).

This allows us to write WebAssembly applications that can manipulate the DOM, and with potential for more speed!

Early Stages!

Work in progress (probably may always be), but right now it's early and many APIs need to be added.

Supported APIs so far

See the outline of supported APIs.

Usage

First you should get familiar with AssemblyScript and learn how to run AssemlyScript code in a browser.

The following asc compiler options are required: --exportRuntime --exportTable.

The --explicitStart option is required only if any of your ES modules use DOM APIs at their top level (read the comments in the below example).

In your JavaScript code that loads your Wasm module, import Asdom and pass its wasmImports to your Wasm module's imports. The following snippet assumes the use of native ES Modules in the browser and a


Next I'd like to add a JSX transform for AS. It will compile to something like Solid.js does with fine-grained updates of the DOM, but written in AS.

The final experience will be similar to Solid. I will be porting my LUME custom elements for 3D (which use Solid underneath) to AS after that.

In the end, it will be possible to compile this stuff, written in TypeScript, to either JS for existing apps, or to Wasm when that makes sense (using tsc or asc compilers, respectively).

I think compiling to Wasm will make sense for computationally expensive apps like 3D games with physics, transforms, collisions, etc, where the gains from doing that math stuff in Wasm will far outweigh the coat of DOM bindings.

In upcoming Wasm specs, Wasm will be able to interop with DOM much more quickly (essentially like JS), at which point I'm hoping that this infrastructure I'm making ahead of time will be beneficial for regular non-computationally-heavy DOM apps.

I'm betting Wasm, even on MVP with slower bindings, will be a good fit for something like LUME. React-three-fiber or A-frame could benefit too I think.

Thread Thread
svicalifornia profile image
Shawn Van Ittersum

Interesting stuff!

However, I wonder if 3D games and other visualizations would be best served by DOM elements or by raw pixels in a canvas element. But then we'd compare that to Three.js, WebGL, etc.

For DOM elements driven by WASM, I was thinking more about (very) large dashboards with lots of figures changing constantly, and perhaps certain parts of the display being highlighted and zoomed as needed to direct user attention. That kind of content, while not as demanding as 3D games, could potentially benefit from a faster implementation of Ryan's DOM expressions.

Collapse
uminer profile image
Moshe Uminer

Every once in a while I go look at the progress in the lume repository. My one gripe is the fact that docs feel somewhat bare, and I have difficulty figuring out how to go beyond simple examples. Of course, authoring a library like that is no mean feat, and docs are always difficult to write. But do wish I had a better way of learning the library.

Commenting because

(LUME is still alpha, please complain about everything. :)

🙂

Collapse
trusktr profile image
Joe Pea Author • Edited on

Hey, thanks for the feedback. I am slowly working on docs. Initially I spent more time on the JSDoc parser (because everything else didn't fit my need), and now I am focusing more on content.

But also, if you have any questions, you can also come directly to me. :)

What particular areas do you get blocked on? I can focus on documenting those parts next.

Also wish I had more hands! I'm also working on github.com/lume/glas (port of Three.js to AssemblyScript), and now also github.com/lume/asdom (DOM bindings for AssemblyScript). Eventually this will come together into custom elements written in AssemblyScript (hence running as WebAssembly with access to DOM and WebGL for max speed).

Collapse
uminer profile image
Moshe Uminer

What particular areas do you get blocked on? I can focus on documenting those parts next.

I'm not sure right now... Generally though, I can't feel that I "know" what to reach for when I want something, if I don't know everything that's in my toolbox. It's also basically my first foray into 3d, so 🀷.

Is glas meant to replace solid in lume? Or replace lume itself? I know you have lot's of projects, but I don't understand how they all fit together.

But also, if you have any questions, you can also come directly to me. :)

I'll definitely take you up on that. I'll see you on discord 😄.

Thread Thread
trusktr profile image
Joe Pea Author • Edited on

Yeah, i can see how already having 3D knowledge (f.e. knowing how Three.js works) would help.

Currently the custom elements use Three.js underneath for the WebGL rendering. Glas will replace the underlying Three.js as the lower level WebGL rendering mechanism, while the custom elements on top will remain the same. Essentially, if you're only using the HTML elements, you shouldn't notice a difference, only the WebGL rendering mechanism that is abstracted underneath will change. You would only notice any difference if you are reaching inside of the elements to grab the Three.js instances to do something custom that is not supported by the element interfaces yet. For example, if you were to access element.three, that gives you the Three.js object that the element manipulates, and that part will change to be glas objects. TLDR: If you stick to LUME element attributes and properties (except for .three, which I may not document) then you'll not notice anything.

Collapse
estebangs profile image
Esteban

I'm sold on Solid Js, but how do include CSS and JS dependencies (example: getmdl.io/started/index.html) in dev and production build?

I have only found instructions on using Tailwindcss, which is not what I want to work with.