A declarative, efficient, and flexible JavaScript library for building user interfaces.

Overview

Solid

Build Status Coverage Status NPM Version Discord Subreddit subscribers

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! Almost indistinguishable performance vs optimized painfully imperative vanilla DOM code. See Solid on JS Framework Benchmark.
  • 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.
  • 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.

Top 5 Things You Should Know about Solid

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:

Try it Online

Getting Started

npm init solid <project-type> <project-name> is available with npm 6+.

You can get started with a simple app with the CLI with by running:

> npm init solid app my-app

Or for a TypeScript starter:

> npm init solid app-ts my-app

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

Resources

No Compilation?

Dislike JSX? Don't mind doing manual work to wrap expressions, worse performance, and having larger bundle sizes? Alternatively in non-compiled environments you can use Tagged Template Literals or HyperScript.

You can run them straight from the browser using SkyPack:

<html>
  <body>
    <script type="module">
      import { createSignal, onCleanup } from "https://cdn.skypack.dev/solid-js";
      import { render } from "https://cdn.skypack.dev/solid-js/web";
      import html from "https://cdn.skypack.dev/solid-js/html";

      const App = () => {
        const [count, setCount] = createSignal(0),
          timer = setInterval(() => setCount(count() + 1), 1000);
        onCleanup(() => clearInterval(timer));
        return html`<div>${count}</div>`;
      };
      render(App, document.body);
    </script>
  </body>
</html>

Remember you still need the corresponding DOM Expressions library for these to work with TypeScript. Tagged Template Literals Lit DOM Expressions or HyperScript with Hyper DOM Expressions.

Browser Support

The last 2 versions of modern evergreen browsers and Node LTS.

Testing Powered By SauceLabs

Community

Come chat with us on Discord

Contributors

Open Collective

Support us with a donation and help us continue our activities. [Contribute]

Sponsors

Become a sponsor and get your logo on our README on GitHub with a link to your site. [Become a sponsor]

Status

Solid is mostly feature complete for its v1.0.0 release. The next releases will be mostly bug fixes and API tweaks on the road to stability.

Comments
  • Help Me Understand Documentation Holes

    Help Me Understand Documentation Holes

    I really would appreciate feedback here. It is so hard for me to see it since I've been so close to this for so long and I've developed apps in this manner for 10 years. I'm going to be the worst person to write the documentation since it is all so second nature for me. Yet, I am most likely going to be the person writing the documentation since I understand how everything works so well.

    So can you please comment on anythings you found confusing about Solid as you've been trying it out, using it in your demo projects, integrating it with 3rd party libraries. And if you did find a solution can you comment on what you feel would have been the easiest way to describe it to you. Even if it takes me some time to compile all of this I think it would also highlight some early gotcha's for people trying the library.

    I know I need to do a lot better here but this is a bit of a blindspot for me. Thank you.

    help wanted good first issue documentation 
    opened by ryansolid 116
  • SSR Specification

    SSR Specification

    I've had many people ask me about SSR. In fact, it's probably the most requested feature. So far I've made it mechanically possible to do rehydration in the client for non-asynchronous rendered cases. Using JSDOM or similar we can run Solid on the server to generate fake DOM nodes that can be stringified.

    I don't know enough about the topic of SSR and Isomorphic Libraries to know beyond that how they are expected to work. Nor am I the best suited to spearhead this as I have no reason, outside of requests, to look into this. It's just not an area where I do work. I've been working hard to improve clientside performance to a point that TTI speed and Service worker caching makes the whole thing mute for high-performance Web Apps and PWAs. But I know there are other use cases out there, and that the research especially being done with React and Next.js is impressive. But I'm much more incentivized to show far we can optimize SPAs.

    Currently, I am not really clear even what the essential building blocks are. Routing obviously comes to mind. But I'm unclear on isomorphic patterns there. How are lifecycles and asynchronous requests handled? Lazy loaded components? How do things differ between static site generation and otherwise? What sort of tooling is useful? If anyone has experience in these areas it would be invaluable.

    I realize people are looking for libraries and frameworks for opinions here, but I haven't formed them. I will work with anyone interested to make Solid be able to handle the mechanicals. Unless others help take it on though I don't see SSR progressing in the next 6 months. I have a lot of other work to get done to make Solid better.

    What can I do to Help?

    Join the conversation. I'm free to chat at length about this on gitter. Post in this thread what you'd like to see. Or any Solid specific insights you have from your experience with other SSR libraries. If you think you might have answers to any of my questions above please shed some light on the issues.

    Thank you.

    enhancement help wanted 
    opened by ryansolid 45
  • Revised Component types (children, ref)

    Revised Component types (children, ref)

    Based on recent discussion on #typescript. This change summary is longer than the diff. 🙂

    • Rename past Component type to ~~ComponentWithChildren~~ ParentComponent.

      • Motivation: Most components don't actually support children. This causes a type error upon accidental passing of children / forces you to think about which components support children.
      • Also, many users on #help/#typescript have asked for how to type such a component.
      • This is a slight breaking change (users may need to change some instances of Component type to ParentComponent to avoid type errors) so should probably wait for Solid 1.4.
    • Two-parameter FlowComponent requires children and makes it easy to give a custom type for children, as in many flow control components.

    • New Component type does not have automatic children property (like React's preferred VoidFunctionalComponent), offering another natural way to type props.children:

      Component<{children: JSX.Element}>
      
    • New VoidComponent forces not having children prop, to make it extra clear how to type this.

    • ParentProps, FlowProps, and VoidProps type helpers for the props argument to ParentComponent, FlowComponent, and VoidComponent.

    • Sadly none of these are great when you need to parameterize by a generic type, but still good starting points for people typing simple components.

    • ~~props argument in both Component and ComponentWithChildren automatically cast to readonly (shallow one level only), to avoid accidental assignment to props.foo (usually a getter) while still allowing passing mutables in props.~~ Removed; see below.

      • ~~Add Props<T> helper for this transformation.~~
      • Inspired by this React plan.
    • Add @LXSMNSYC's Ref<T> type so it's easy to type props.ref:

      Component<{ref: Ref<Element>}>
      

      Fixes #778.

    An alternative would be to leave Component as is, and rename the new Component type to VoidComponent. This would preserve backward compatibility and match React. But see this discussion for good arguments why (in React) VoidComponent is better than straight Component. And React 18 is changing the type of FunctionalComponent to what was VoidFunctionalComponent.

    typescript 
    opened by edemaine 39
  • Update Logo/Theme

    Update Logo/Theme

    I am not a visual artist. I used a logo generator. I think at some point in the near future Solid needs to get a real logo. Now I'm not necessarily inviting an open free for all as I don't want to waste anyone's time but I could definitely benefit from some help.

    enhancement help wanted 
    opened by ryansolid 33
  • Updating solid-js and babel-preset-solid to 0.24.0 break my buttons

    Updating solid-js and babel-preset-solid to 0.24.0 break my buttons

    Describe the bug After upgrading the places I used onClick are broken.

    The examples: https://github.com/steelbrain/linter-ui-default/blob/2b2f2ca1ed449d4ec8e0f076020aec2831388ce8/lib/tooltip/message.tsx#L101

          <div className={`linter-excerpt ${message.severity}`}>
            {
              // fold butotn if has message description
              message.description && (
                <a href="#" onClick={async () => await toggleDescription()}>
                  <span className={`icon linter-icon icon-${state.descriptionShow ? 'chevron-down' : 'chevron-right'}`} />
                </a>
              )
            }
    	<div>
    

    To Reproduce

    Expected behavior Click should work

    Reproduction This is reproducible inside Atom. I cannot seem to be able to make a separate reproduction outside Atom. https://github.com/steelbrain/linter-ui-default/

    Downgrading Solid completely fixed the issue!

    Additional context

    bug 
    opened by aminya 32
  • Duplicate edges in signal graph

    Duplicate edges in signal graph

    If I understand correctly, when signal value is accessed, it doesn't perform a check when the edge already exists:

    1. Accessing current() value: https://github.com/ryansolid/solid/blob/8fe0c11360387a325d64e040a756585a69f6da63/src/signals.ts#L189-L192
    2. Creating an edge: https://github.com/ryansolid/solid/blob/8fe0c11360387a325d64e040a756585a69f6da63/src/signals.ts#L405-L439

    Without such checks, use cases that involve sorting/filtering in data tables will create insanely huge graphs.

    For example, simple sort() on 5000 items will create ~110000 edges:

    let i = 0;
    
    class Item {
      _a = Math.random();
    
      get a() {
        i++;
        return this._a;
      }
    }
    
    const items = [];
    for (let i = 0; i < 5000; i++) {
      items.push(new Item());
    }
    
    items.sort((a, b) => a.a - b.a);
    console.log(i);
    
    enhancement 
    opened by localvoid 32
  • Help in making a `etch-solid` library

    Help in making a `etch-solid` library

    Plan

    I plan to use solid for Atom projects. However, Atom has its own DOM library called etch, which is used in the core of Atom as well as many of its packages. etch has some limitations, it has an average performance, and forces a certain style of programming. https://github.com/atom/etch

    Implementation

    To make the transition seamless, I want to make a drop-in replacement library that uses solid as its backend but has the same API as etch. Could you help me with this?

    I have made a demo of this etch-solid library: https://codesandbox.io/s/etch-solid-pwonz?file=/src/index.jsx

    Details The documentation of etch gives an overview of the API https://github.com/atom/etch

    These are the source code of the API (initialize, update, destroy): https://github.com/atom/etch/blob/master/lib/component-helpers.js and here the dom function: https://github.com/atom/etch/blob/9b90d6ad88b90b8a273992c540879b915d9967a2/lib/dom.js#L4

    P.S: The other approach is to use meta-programming to transpile etch code to solid code. However, this can be complex, but I am open to that approach too. Something like this babel plugin or this transformer

    Benchmarks

    Details In the benchmarks, etch is behind solid so this can be beneficial in general (~25% improvement). This can enhance the responsiveness of the editor. https://krausest.github.io/js-framework-benchmark/2020/table_chrome_84.0.4147.89.html

    image

    The last dramatic change for improving the performance happened back in 2017 by introducing etch. Now it is the time to replace that with a faster modern framework. Even if we do not use it for text rendering, other parts of the Atom can benefit from it. https://blog.atom.io/2017/06/22/a-new-approach-to-text-rendering.html https://blog.atom.io/2018/01/10/the-state-of-atoms-performance.html

    P.S:

    • I am not sure if this repository is the best place to have this issue. Feel free to transfer it or move the discussion here
    question 
    opened by aminya 31
  • Redefining `batch`, and the concept of a new batched effect

    Redefining `batch`, and the concept of a new batched effect

    Describe the bug

    import { createSignal, batch } from "solid-js";
    
    const [count, setCount] = createSignal(0);
    
    batch(() => {
      console.log('set:', setCount(123));
      console.log('get:', count()); // logs 0 instead of 123
    });
    

    Your Example Website or App

    https://playground.solidjs.com/?hash=-937748346&version=1.3.9

    Steps to Reproduce the Bug or Issue

    See link

    Expected behavior

    Reading and writing signals should be atomic operations.

    Screenshots or Videos

    No response

    Platform

    n/a

    Additional context

    I spent a weekend debugging an issue I thought was in LUME, because I never expected reading a signal after setting it would return an old value. Essentially the write is not atomic.

    The issue in my case also wasn't obvious because the read was far removed (several methods deep) from where the write happens.

    The reason I wanted to use batch was to group the write of a signal with some method calls after it, so that reactivity would be triggered after the write and subsequent method calls.

    question 
    opened by trusktr 30
  • css-in-js options?

    css-in-js options?

    This is a great library and builds on the shortcomings of some big libraries out there. Absolutely prefer DOM library over virtual DOM implementation. Thanks for this great effort. 💯

    I have a couple of questions:

    • Does anyone know of a css-in-js (styled) library that could work with solid out of the box?
    • What is the React.createElement equivalent for solid.js if someone were to write a port of @emotion/styled or something similar?

    Thanks

    question 
    opened by praneybehl 27
  • Boolean attributes

    Boolean attributes

    Questions: How should JSX attributes foo and foo={true} behave for an HTML element, as in <div foo> or <div foo={true}>? Should the behavior differ for components and/or spreads?

    Current behavior in Solid: Assume const Div = (props) => <div {...props}/>

    | JSX | Equivalent HTML | |-----|--------------------| | <div foo> | <div foo=""> | | <div foo=""> | <div foo=""> | | <div foo={true}> | <div foo="true"> | | <div foo="true"> | <div foo="true"> | | <Div foo> | <div foo="true"> ☆ | | <Div foo=""> | <div foo=""> | | <Div foo={true}> | <div foo="true"> | | <Div foo="true"> | <div foo="true"> |

    The ☆ behavior (another consequence of setAttribute's string casting) is particularly counterintuitive, as it differs between HTML elements and component-wrapped elements. (It may also be an SSR discrepancy?) @fabiospampinato's observation of this weirdness is what spawned this exploration (in #templating channel, also with @LXSMNSYC).

    Relevant facts:

    • HTML 5 defines attribute foo to be equivalent to foo=""
    • React defines that foo is equivalent to foo={true}.
    • TypeScript treats foo like foo={true} (attribute should accept Boolean value)
    • React (specifically react-dom) converts JSX attribute foo to HTML equivalent of foo="true" unless foo is on a whitelist of "Boolean" attributes, in which case it converts to foo="". Here's a relevant list; note that there are some Boolean-like attributes which aren't actually Boolean. For example, React "correctly" converts draggable={true} to draggable="true" and "correctly" converts async={true} to async="".

    Desired properties:

    1. foo is always equivalent to foo="" for HTML/SVG elements (as in HTML 5). This is useful for copy/pastability of HTML, which the JSX spec considers important.
    2. foo is equivalent to foo={true} (as in React and TypeScript)
    3. draggable={true} is equivalent to draggable="true"
    4. <div foo> is equivalent to const Div = (props) => <div {...props}/>; <Div foo>

    We can't have all four properties.

    • Properties 1, 2, and 3 contradict each other: 1+2 imply draggable={true} is equivalent to draggable="".
    • React satisfies Properties 2, 3, and 4. It follows Property 1 only for a whitelist of Boolean attributes.
    • Solid satisfies Properties 1 and 2. It satisfies Property 3 for Div but not div. It doesn't satisfy Property 4 because of ☆.

    Proposal: I really like Property 1, and would propose gaining Property 4 by compiling foo={value} to essentially _el$.setAttribute('foo', value === true ? '' : value), where value is stored in a temporary variable if it's a general expression, with a similar modification to spreads. This would basically flip ☆, and also change those marked "!":

    | JSX | Equivalent HTML | |-----|--------------------| | <div foo> | <div foo=""> | | <div foo=""> | <div foo=""> | | <div foo={true}> | <div foo=""> ! | | <div foo="true"> | <div foo="true"> | | <Div foo> | <div foo=""> ☆ | | <Div foo=""> | <div foo=""> | | <Div foo={true}> | <div foo=""> ! | | <Div foo="true"> | <div foo="true"> |

    (By contrast, React's approach of an attribute whitelist feels gross...)

    opened by edemaine 24
  • feat(signal): abortable resource

    feat(signal): abortable resource

    As mentioned earlier on Discord, it would be helpful to be able to abort resource fetching (for fetch or anything else you intend to make abortable). A simple way to do so is to introduce an optional third argument to the fetcher that if used creates an AbortController automatically and calls it whenever the resource is fetched anew. This saves developers the hassle of managing the creation of new AbortControllers themselves, as those are not reusable once called.

    opened by atk 24
  • setting getters on already defined keys in a store loses reactivty

    setting getters on already defined keys in a store loses reactivty

    Describe the bug

    Setting a getter-function on a key that has already been initialized in a store with setStore will not create a reactive getter, unlike when you set the getter-function while initializing a key.

    This can be solved (as @thetarnav suggested) by doing a shallow merge.

    setStore({
      alreadyDefinedKey: {
        get get() {
          return store.value;
        },
      },
    });
    

    but this feels like it should be supported without having to shallow merge objects (i personally try to avoid shallow merging as much as possible, because of the fact you can accidentally create multiple paths leading to the same reactive value and that can have unexpected results)

    Your Example Website or App

    https://playground.solidjs.com/anonymous/972b3aec-d7df-4ec0-9a68-c8b8b60785e3

    Steps to Reproduce the Bug or Issue

    const [store, setStore] = createStore({ value: 0, alreadyDefinedKey: {}});
    
    setStore('alreadyDefinedKey', {
     get get() {
       return store.value
     }
    })
    
    setStore('newKey', {
     get get() {
       return store.value
     }
    })
    
    setStore('value', 1)
    

    Expected behavior

    I expect in the above example store.alreadyDefinedKey.get to be 1, just as store.newKey.get, but instead it is the old value 0.

    Screenshots or Videos

    No response

    Platform

    • OS: iOS
    • Browser: firefox
    • Version: 12.1.1

    Additional context

    No response

    enhancement 
    opened by bigmistqke 2
  • SSR: Cannot read properties of undefined (reading 'cloneNode') because getNextElement() is called without arguments

    SSR: Cannot read properties of undefined (reading 'cloneNode') because getNextElement() is called without arguments

    Describe the bug

    Hi,

    I'm working on getting SSR and hydration to work... I'm not using solid-start because I have my own simple project for doing SSG only and for learning purposes. I started over and over again with like 4 to 5 attempts but always end up with the same error and now I think it's less likely that it's only me doing something wrong here...?

    What I actually do is building and bundling code using esbuild, and of course with a Babel approach using esbuild-plugin-solid to make sure the JSX and SSR/DOM code generation works correctly... (the dependency uses standard solid babel transform under the hood, see: https://github.com/amoutonbrady/esbuild-plugin-solid/blob/master/src/plugin.ts#L5)

    For the server code bundle I have something like this:

    import { build } from 'esbuild'
    import { solidPlugin } from 'esbuild-plugin-solid'
    
    // shortened and simplified
    await build({
        bundle: true,
        format: 'esm',
        platform: 'node',
        // basePlugins has some more plugins for handling importing .css files etc., nothing that would deal with js/ts stuff
        plugins: [solidPlugin({ generate: 'ssr', hydratable: true }), ...basePlugins],
      })
    

    For the client I build another code bundle:

    // shortened and simplified
    await build({
        bundle: true,
        platform: 'browser',
        plugins: [solidPlugin({ generate: 'dom', hydratable: true }), ...basePlugins],
        format: 'iife',
      })
    

    On the server I use the server code bundle and after executing the code below in a vm context:

    Bildschirmfoto 2022-12-26 um 18 30 17

    Please note: The render() function here is not Solid's render function. It is my own runtime render() function that, when executed on server side, calls renderToString() and on the client a different runtime impl. is executed and calls Solid's hydrate().

    PageScript is executed on server-side and injects the client-side code bundle (this is executed on server side) by returning a