Solid is a declarative JavaScript library for creating user interfaces. It does not use a Virtual DOM. Instead it opts to compile its templates down to real DOM nodes and wrap updates in fine grained reactions. This way when your state updates only the code that depends on it runs.
Key Features
- Real DOM with fine-grained updates (No Virtual DOM! No Dirty Checking Digest Loop!).
- Declarative data
- Simple composable primitives without the hidden rules.
- Function Components with no need for lifecycle methods or specialized configuration objects.
- Render once mental model.
- Fast!
- Small! Completely tree-shakeable Solid's compiler will only include parts of the library you use.
- Supports and is built on TypeScript.
- Supports modern features like JSX, Fragments, Context, Portals, Suspense, Streaming SSR, Progressive Hydration, Error Boundaries and Concurrent Rendering.
- Works in serverless environments including AWS Lambda and Cloudflare Workers.
- Webcomponent friendly and can author Custom Elements
- Context API that spans Custom Elements
- Implicit event delegation with Shadow DOM Retargeting
- Shadow DOM Portals
- Transparent debugging: a
<div>
is just a div.
Learn more on the Solid Website and come chat with us on our Discord
The Gist
import { render } from "solid-js/web";
const HelloMessage = props => <div>Hello {props.name}</div>;
render(() => <HelloMessage name="Taylor" />, document.getElementById("hello-example"));
A Simple Component is just a function that accepts properties. Solid uses a render
function to create the reactive mount point of your application.
The JSX is then compiled down to efficient real DOM expressions:
import { render, template, insert, createComponent } from "solid-js/web";
const _tmpl$ = template(`<div>Hello </div>`);
const HelloMessage = props => {
const _el$ = _tmpl$.cloneNode(true);
insert(_el$, () => props.name);
return _el$;
};
render(
() => createComponent(HelloMessage, { name: "Taylor" }),
document.getElementById("hello-example")
);
That _el$
is a real div element and props.name
, Taylor
in this case, is appended to its child nodes. Notice that props.name
is wrapped in a function. That is because that is the only part of this component that will ever execute again. Even if a name is updated from the outside only that one expression will be re-evaluated. The compiler optimizes initial render and the runtime optimizes updates. It's the best of both worlds.
Want to see what code Solid generates:
Quick Start
You can get started with a simple app by running the following in your terminal:
> npx degit solidjs/templates/js my-app
> cd my-app
> npm i
> npm run dev
Or for TypeScript:
> npx degit solidjs/templates/ts my-app
> cd my-app
> npm i
> npm run dev
This will create a minimal client-rendered application powered by Vite.
Or you can install the dependencies in your own project. To use Solid with JSX (recommended) run:
> npm install solid-js babel-preset-solid
The easiest way to get setup is add babel-preset-solid
to your .babelrc, or babel config for webpack, or rollup:
"presets": ["solid"]
For TypeScript remember to set your TSConfig to handle Solid's JSX by:
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "solid-js",
}
Documentation
Check out the Documentation website.
Examples
Browser Support
The last 2 versions of modern evergreen browsers and Node LTS.
Come chat with us on Discord
Contributors
Open Collective
Support us with a donation and help us continue our activities. [Contribute]
Become a sponsor and get your logo on our README on GitHub with a link to your site. [Become a sponsor]
1.3.0 - 2022-01-05
New Features
HTML Streaming
This release adds support for HTML streaming. Now we not only stream data after the initial shell but the HTML as it finishes. The big benefit is that now for cached results, or times when the network are slow we no longer have to show the placeholder while waiting for JavaScript bundle to load. As soon as the HTML is available it will be streamed and inserted.
With it comes new streaming API renderToStream
. This is a universal API designed to handle both Node and Web writable streams. It returns an object that mirrors a Readable stream on both platforms that has both pipe
(node) and pipeTo
(web). The benefit of this pipe
API is the user can choose when to insert the content in the output stream whether soon as possible, or onCompleteShell
, or onCompleteAll
. This decouples Solid's rendering a from the stream a bit but leaves things open to performance improvements in the future.
// node
const stream = renderToStream(() => <App />).pipe(res);
// web
const stream = renderToStream(() => <App />).pipeTo(writable);
Error Boundaries on the Server
We've added support for Error Boundaries on the Server for all rendering methods(renderToString
, renderToStringAsync
, renderToStream
). Errors can be caught both from synchronous rendering and from errors that happen in Resource resolution. However, Our approach doesn't guarentee all errors are handled on the server as with streaming it is possible that the Error Boundary has already made it to the browser while a nested Suspense component hasn't settled. If an Error is hit it will propagate up to the top most Suspense Boundary that hasn't been flushed yet. If it is not handled by an Error Boundary before that it will abort rendering, and send the Error to the browser to propagate up to the nearest Error Boundary.
This works now but there is more to explore here in improving Error handling in general with SSR. So look forward to feedback on the feature.
Isolated Server Render/Hydration Contexts
Sometimes you want to server render and hydrate multiple Solid apps on the same page. Maybe you are using the Islands architecture with something like Astro. We now have the ability to pass a unique renderId
on all our server rendering methods and to the hydrate
function. This will isolate all hydration and resource resolution. This means we can use things like server side Suspense in these solutions.
Also now you only need to include the Hydration Script once on the page. Each Island will be responsible for initializing it's own resources.
// on the server
const html = renderToString(() => <Island1 />, { renderId: "island1" });
// for the browser
hydrate(() => <Island1 />, mountEl, { renderId: "island1" });
createReaction
This new primitive is mostly for more advanced use cases and is very helpful for interopt with purely pull based systems (like integrating with React's render cycle). It registers an untracked side effect and returns a tracking function. The tracking function is used to track code block, and the side effect is not fired until the first time any of the dependencies in the tracking code is updated. track
must be called to track again.
const [s, set] = createSignal("start");
const track = createReaction(() => console.log("something"));
// next time s changes run the reaction
track(() => s());
set("end"); // "something"
set("final"); // no-op as reaction only runs on first update, need to call track again.
This primitive is niche for certain use cases but where it is useful it is indispensible (like the next feature which uses a similar API).
External Sources (experimental)
Ever wanted to use a third party reactive library directly in Solid, like MobX, Vue Reactivity, or Kairo. We are experimenting with adding native support so reactive atoms from these libraries can be used directly in Solid's primitives and JSX without a wrapper. This feature is still experimental since supporting Transitions and Concurrent Rendering will take some more effort. But we have added enableExternalSource
enable this feature. Thanks @3Shain for designing this solution.
import { Reaction, makeAutoObservable } from "mobx";
import { enableExternalSource } from "solid-js";
import { render } from "solid-js/web";
let id = 0;
enableExternalSource((fn, trigger) => {
const reaction = new Reaction(`externalSource@${++id}`, trigger);
return {
track: x => {
let next;
reaction.track(() => (next = fn(x)));
return next;
},
dispose: () => {
reaction.dispose();
}
};
});
class Timer {
secondsPassed = 0;
constructor() {
makeAutoObservable(this);
}
increase() {
this.secondsPassed += 1;
}
reset() {
this.secondsPassed = 0;
}
}
// component driven directly off MobX
function App() {
const timer = new Timer();
setInterval(() => {
timer.increase();
}, 1000);
return <button onClick={() => timer.reset()}>Seconds passed: {timer.secondsPassed}</button>;
}
render(() => <App />, document.getElementById("app"));
refetchResources
(experimental)
In efforts to allow for scaling from simple resources up to cached solutions we are adding some experimental features to createResource
to work with library writers to develop the best patterns. Caching is always a tricky problem and with SSR and streaming being part of the equation the core framework needs at minimum to provide some hooks into orchestrating them.
Sometimes it's valuable to trigger refetch
across many resources. Now you can.
import { createResource, refetchResources } from "solid-js";
const userCache = {};
function MyComponent(props) {
const [data] = createResource(
() => props.id,
(userId, { refetching }) => {
const cached = userCache[userId];
// return cached value if available and not refetching
if (cached && !refetching) return cached;
return fetchUser(userId);
}
);
}
// somewhere else
refetchResources();
You can also pass a parameter to refetchResources
to provide additional information to the refetching
info of the fetcher. This could be used for conditional cache invalidation. Like only refetch resources related to users
. This mechanism requires a bit of wiring but the idea is you'd wrap createResource
in maybe a createQuery
and implement your own conventions around resource cache management. Still working out how this should work best, but the goal is to provide the mechanisms to support resource caches without being responsible for their implementation.
To opt-out being part of the global refetch createResource now takes a globalRefetch
option that can be set to false. In addition to a new option to disable refetchResources
there is no an onHydrated
callback that takes the same arguments as the fetcher. When a resource is restored from the server the fetcher is not called. However, this callback will be. This is useful for populating caches.
Improvements
Better TypeScript Support
Thanks to the tireless efforts of several contributors we now have significantly better types in Solid. This was a huge effort and involved pulling in maintainers of TypeScript to help us work through it. Thank you @trusktr for spearheading the effort.
Better SourceMaps
Work has been done to improve sourcemaps by updating babel-plugin-dom-expressions
to better preserve identifiers from the JSX. Thanks to @LXSMNSYC for exploring and implementing this.
Breaking Changes/Deprecations
startTransition
no longer takes callback as a second argument
Instead it returns a promise you can await. This works better for chaining sequences of actions.
const [start, isPending] = useTransition();
start(() => doSomething()).then(() => allDone());
Resource fetcher info object replaces getPrev
To streamline API for refetch we are slightly updating the createResource
:
const [data] = createResource(sourceSignal, (source, { value, refetching }) => {});
For those using existing 2nd argument:
const [data] = createResource(sourceSignal, (source, getPrev) => {
const value = getPrev();
});
// becomes
const [data] = createResource(sourceSignal, (source, { value }) => {});
Deprecating Legacy Streaming APIs
pipeToNodeWritable
and pipeToWritable
are deprecated. They will still work for now with basic usage but some of the more advanced options didn't map over to the new APIs directly and have been removed. Move to using renderToStream
.
Bug Fixes
- Fixed browser extensions modifying the head breaking hydration.
- Fixed reinserting
<html>
on hydration from document. - Fixed over-executing on multi-select with
createSelector
. - Fixed event delegation conflicting with document event listeners.
- Fixed self owning source infinite recursion.
- Fixed faulty treesplitting for hydration in client only render.
- Fixed return type of
preload
on lazy components to always be a promise. - Fixed compile error with leading white space after opening tags when generating ssr.