Open-Source Wikis

/

Solid

/

Packages

/

solid-js/universal

solidjs/solid

solid-js/universal

Active contributors: Ryan Carniato

solid-js/universal is the entry point that lets Solid drive any tree-shaped target — terminal UIs, canvas frameworks, native mobile renderers, Figma plugin trees — instead of the DOM. It exposes a createRenderer factory; you supply the host operations (createElement, insertNode, getParentNode, etc.) and you get back a fully wired Solid renderer that the JSX compiler can target.

Purpose

solid-js/web is the renderer for one specific platform (the browser). solid-js/universal lets community projects build renderers for everything else without forking the framework. Combined with babel-preset-solid configured as generate: "universal" and moduleName: "<your-renderer>", the same JSX you write for a web app can be compiled to drive an arbitrary tree.

Directory layout

packages/solid/universal/
├── package.json                # ./universal export selector
├── tsconfig.json
├── README.md                   # full example: building a custom DOM renderer
└── src/
    ├── index.ts                # createRenderer wiring
    └── universal.ts            # type re-exports from dom-expressions/universal

Key abstractions

Symbol Source Description
createRenderer<NodeType>(options) packages/solid/universal/src/index.ts Wraps dom-expressions's createRenderer, then attaches mergeProps from solid-js. Returns a Renderer<NodeType> object.
RendererOptions<NodeType> re-exported from dom-expressions/universal The host-operation contract you must implement.
Renderer<NodeType> re-exported from dom-expressions/universal What you re-export from your renderer module: render, effect, memo, createComponent, createElement, createTextNode, insertNode, insert, spread, setProp, mergeProps, use.

How it works

import { mergeProps } from 'solid-js';
import {
  createRenderer as createRendererDX,
  type Renderer,
  type RendererOptions,
} from './universal.js';

export function createRenderer<NodeType>(
  options: RendererOptions<NodeType>
): Renderer<NodeType> {
  const renderer = createRendererDX(options);
  renderer.mergeProps = mergeProps;
  return renderer;
}

That is the entire implementation. dom-expressions/universal does the structural work — wiring up createComponent, effect, memo, createElement, insertNode, spread, etc. against your host operations. Solid's only contribution is to add its own mergeProps so consumers can rely on the standard prop-merging shape.

RendererOptions<NodeType> is the contract you need to satisfy:

{
  createElement: (tag: string) => NodeType,
  createTextNode: (value: string) => NodeType,
  replaceText: (textNode: NodeType, value: string) => void,
  setProperty: (node: NodeType, name: string, value: any, prev?: any) => void,
  insertNode: (parent: NodeType, node: NodeType, anchor?: NodeType) => void,
  isTextNode: (node: NodeType) => boolean,
  removeNode: (parent: NodeType, node: NodeType) => void,
  getParentNode: (node: NodeType) => NodeType | undefined,
  getFirstChild: (node: NodeType) => NodeType | undefined,
  getNextSibling: (node: NodeType) => NodeType | undefined,
}

The README at packages/solid/universal/README.md walks through implementing a custom DOM renderer (with a PROPERTIES allowlist for className and textContent).

Connecting to the compiler

babel-preset-solid accepts moduleName and generate options. To target a custom renderer:

// .babelrc
{
  "presets": [
    [
      "babel-preset-solid",
      {
        "moduleName": "solid-custom-dom",
        "generate": "universal",
      },
    ],
  ],
}

The compiler then emits import { ... } from "solid-custom-dom" instead of solid-js/web. The names it imports (createComponent, createElement, effect, insert, spread, setProp, template, …) are exactly the ones returned by createRenderer. Re-export them from your renderer's index file.

graph LR
    JSX[App JSX] -->|babel-preset-solid<br>generate: universal<br>moduleName: solid-custom-dom| Compiled[Compiled JS]
    Compiled -->|imports from| Module[solid-custom-dom<br>your renderer module]
    Module -->|createRenderer(...)| Universal[solid-js/universal]
    Universal -->|wraps| DXUniv[dom-expressions/universal]
    Universal -->|mergeProps| Solid[solid-js]
    Module -->|"render(() => <App/>, root)"| Host[Your host system]

Key source files

File Purpose
packages/solid/universal/src/index.ts The 11-line wrapper that adds mergeProps to dom-expressions/universal's renderer.
packages/solid/universal/src/universal.ts Type re-exports.
packages/solid/universal/README.md Full example renderer with babel/vite configuration.

Integration points

  • Solid's mergeProps is the only direct dependency on solid-js; solid-js is external in the Rollup config so consumers dedupe with their app's solid-js.
  • dom-expressions/universal is a workspace devDependency (via dom-expressions) and provides the structural renderer.
  • The babel-preset-solid generate: "universal" mode is what makes the JSX targetable. See babel-preset-solid.

Entry points for modification

  • Changing the universal renderer surface: the function in packages/solid/universal/src/index.ts is the entire surface. Most evolution happens in dom-expressions/universal.
  • Adding new host operations as first-class: if a host operation should be supported by the compiler (e.g. a special setStyle), it must land in dom-expressions first. Solid's wrapper here will pick up the new option automatically.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

solid-js/universal – Solid wiki | Factory