Solid JS

repository·main·Indexed 12 days ago

https://github.com/solidjs/solid

A declarative JavaScript library for building user interfaces that avoids the Virtual DOM in favor of fine-grained reactivity. It compiles templates to real DOM nodes and updates them directly. The ecosystem includes babel-preset-solid for JSX transformation, solid-element for Web Components, and solid-ssr for various server-side rendering strategies including streaming, async, and static site generation (SSG). It also provides non-compiled alternatives via solid-js/h (HyperScript) and solid-js/html (tagged template literals).

Tokens
26.3K
Snippets
105
Records
123
Agent score
96%

What's inside Solid

  1. Manage deeply nested reactivity with Solid Store

    main

    Solid Store provides primitives for handling deeply nested reactive structures using Proxies. It allows you to create complex state objects where updates to nested properties trigger fine-grained reactivity.

    Core Primitives

    • createStore: The primary way to create a reactive, nested state object.
    • createMutable: An alternative primitive for creating dynamic nested reactive structures.

    Helper Methods

    To augment the behavior of the store setter, you can use:

    • produce: Allows for localized mutation (writing code that looks like direct mutation) within a setter.
    • reconcile: Used for data diffing to update a store to match a new object while preserving reactivity.

    For detailed API documentation, visit the official SolidJS website.

    import { createStore } from "solid-js/store";
    
    const [store, setStore] = createStore({
      user: {
        firstName: "John",
        lastName: "Smith"
      }
    });
    
    // update store.user.firstName
    setStore("user", "firstName", "Will");
  2. Maintain reactivity in HyperScript

    main

    When using HyperScript instead of JSX, you must manually handle reactivity for expressions, props, and spreads. If you do not wrap these in functions, they will be evaluated once at creation time and will not update when the underlying signals change.

    1. Reactive Expressions

    Wrap expressions in a function to ensure they are tracked as reactive getters.

    // Instead of: h("div", { id: props.id }, props.name)
    // Use:
    h("div", { id: () => props.id }, () => props.name)

    2. Merging Props

    To maintain reactivity when spreading props, use the mergeProps helper from solid-js.

    import { mergeProps } from "solid-js";
    import h from "solid-js/h";
    
    // Instead of: h("div", { class: selectedClass(), ...props })
    // Use:
    h("div", mergeProps({ class: selectedClass }, props));

    3. Component Events and Render Props

    Solid's HyperScript automatically wraps functions passed to component props (that have no arguments) in getters. To prevent unexpected behavior with events or render props (like in <For>), provide an explicit argument to the function.

    // Good: Explicit event argument
    h(Button, { onClick: (e) => console.log("Hi") });
    
    // Bad: No arguments might be wrapped incorrectly
    h(Button, { onClick: () => console.log("Hi") });
    import { mergeProps } from "solid-js";
    import h from "solid-js/h";
    
    // Reactive expression and merged props example
    h("div", mergeProps({ class: selectedClass }, props), () => firstName() + lastName());
  3. Compare Solid SSR rendering strategies

    main

    Solid SSR supports four primary rendering strategies depending on when data is queried and how the HTML is delivered to the client:

    1. Standard SSR (ssr): Data is queried on the client. Elements outside suspense boundaries are rendered on the server; everything depending on data is rendered client-side. Uses renderToString.
    2. Streaming SSR (stream): Data is queried on the server at request time. The page is sent with placeholders that are replaced as the stream loads. Uses renderToStream.
    3. Async SSR (async): Data is queried on the server at request time. All nodes are rendered on the server and hydrated on the client. Data is serialized and sent with the page to avoid loading states. Uses renderToStringAsync.
    4. Static Site Generation (ssg): Data is queried on the server at build-time. This follows the same hydration pattern as async but removes real-time rendering overhead. Uses renderToStringAsync.
  4. Reactive Expressions in `html` Tagged Templates

    main

    Unlike JSX, where expressions are automatically tracked, reactive expressions in html tagged templates must be manually wrapped in functions to maintain reactivity. If you pass a raw value that changes, the template will not update; you must pass a getter function.

    Comparison:

    • JSX: <div id={props.id}>{firstName() + lastName()}</div>
    • html: html<div id=${() => props.id}>${() => firstName() + lastName()}</div>``
    html`<div id=${() => props.id}>${() => firstName() + lastName()}</div>`
  5. How SolidJS works: The Render-Once mental model

    main

    Unlike Virtual DOM frameworks, SolidJS compiles templates to real DOM nodes and updates them using fine-grained reactions.

    Key Concepts:

    • Components run once: A component is just a regular JavaScript function that runs once to set up your view. It does not re-run on every state change.
    • Fine-grained updates: When state changes, only the specific code (the reactive dependency) that depends on that state re-runs. The rest of the component remains untouched.
    • Reactive Primitives: You use primitives like createSignal to declare state. Accessing these signals inside a template or an effect automatically creates a subscription.

    Example: Counter Component

    import { createSignal } from "solid-js";
    import { render } from "solid-js/web";
    
    function Counter() {
      // createSignal returns an accessor (count) and a setter (setCount)
      const [count, setCount] = createSignal(0);
      
      // Derived state is created by wrapping an expression in a function
      const doubleCount = () => count() * 2;
      
      console.log("The body of the function runs once...");
    
      return (
        <button onClick={() => setCount(c => c + 1)}>
          Increment: {doubleCount()}
        </button>
      );
    }
    
    render(Counter, document.getElementById("app")!);

    In this example, clicking the button updates the signal, which triggers only the text node inside the button to update. The Counter function itself does not execute again.

    import { createSignal } from "solid-js";
    import { render } from "solid-js/web";
    
    function Counter() {
      const [count, setCount] = createSignal(0);
      const doubleCount = () => count() * 2;
      
      return (
        <button onClick={() => setCount(c => c + 1)}>
          Increment: {doubleCount()}
        </button>
      );
    }
    
    render(Counter, document.getElementById("app")!);
  6. How Solid Universal works for custom renderers

    main

    Solid Universal provides the tools to create a runtime for custom renderers. This allows Solid to target platforms other than the standard DOM, such as native mobile, desktop, canvas, WebGL, or the terminal.

    Creating a custom renderer involves two main steps:

    1. Custom Compilation: Using babel-preset-solid with the generate: 'universal' option and specifying a custom moduleName to point to your renderer package.
    2. Renderer Implementation: Implementing the required interface via createRenderer from solid-js/universal and exporting the resulting methods as named exports from your package.
  7. Advanced Composition with withSolid and register

    main

    For advanced use cases where you want to mix in custom behaviors or use other Higher-Order Components (HOCs), you can use the withSolid mixin directly with component-register's register and compose utilities.

    register upgrades a function to a Web Component, and compose allows you to chain multiple mixins together. withSolid is the core mixin that enables Solid logic within the component lifecycle.

    import { register, compose } from 'component-register';
    import { withSolid } from 'solid-element';
    
    // Compose register and withSolid to create a custom element
    compose(
      register('my-component'),
      withSolid
    )((props, options) => {
      // ... Solid code
    })
  8. Handling Events and Render Props in `html`

    main

    When passing events or render props (like those used in the <For> component) to components via html, you must provide an explicit function that accepts arguments.

    Solid's html implementation automatically wraps functions passed to component props that have no arguments in getters. To prevent this behavior and ensure your event handler receives the event object (or other arguments), pass the function directly or ensure it is not being treated as a zero-argument getter.

    Correct usage:

    html`<${Button} onClick=${(e) => console.log("Hi")} />`;

    Incorrect usage (may be wrapped in a getter):

    html`<${Button} onClick=${() => console.log("Hi")} />`;
    html`<${Button} onClick=${(e) => console.log("Hi")} />`
  9. Use Solid Tagged Template Literals via `html`

    main

    The solid-js/html submodule provides an html tagged template literal method. This allows you to use Solid in environments without a compilation step (non-JSX environments) as a replacement for JSX.

    Key syntax rules:

    • Use ${} to escape into JavaScript expressions.
    • Close components using the <//> syntax.
    • Components are instantiated using <${ComponentName} ... />.
    • Attributes can be spread using ...${props}.
    • Multiple top-level elements are supported without needing a Fragment component.
    import { render } from "solid-js/web";
    import html from "solid-js/html";
    import { createSignal } from "solid-js";
    
    function Button(props) {
      return html`<button class="btn-primary" ...${props} />`;
    }
    
    function Counter() {
      const [count, setCount] = createSignal(0);
      const increment = (e) => setCount((c) => c + 1);
    
      return html`<${Button} type="button" onClick=${increment}>${count}<//>`;
    }
    
    render(Counter, document.getElementById("app"));
  10. Use Solid Web entry point methods for rendering

    main

    The solid-js/web submodule provides the primary entry point methods for rendering Solid applications in both browser and server environments. Depending on your target environment and rendering strategy, you should use one of the following methods:

    Browser Rendering

    • render: Used for standard client-side rendering (CSR) to mount a Solid application into a DOM element.
    • hydrate: Used for client-side hydration when the initial HTML was pre-rendered on the server (SSR).

    Server-Side Rendering (SSR)

    • renderToString: Synchronously renders the application to a string.
    • renderToStringAsync: Asynchronously renders the application to a string (useful for handling async components/resources).
    • pipeToNodeWritable: Pipes the rendered output to a Node.js Writable stream.
    • pipeToWritable: Pipes the rendered output to a generic Writable stream.
  11. Review SolidJS performance benchmarks

    main

    To understand the performance characteristics of SolidJS, you can review several benchmark implementations:

    • JS Framework Benchmark: The industry standard for comparing framework performance.
    • Sierpinski's Triangle Demo: A Solid implementation of the React Fiber demo to compare reconciliation/rendering approaches.
    • WebComponent Todos: Demonstrates performance and usage of solid-element for Web Components.
    • UIBench Benchmark: Tests a variety of common UI scenarios.
    • DBMon Benchmark: Tests the ability of libraries to render unoptimized data.