solidjs/solid
solid-js/web (DOM renderer)
Active contributors: Ryan Carniato, Damian Tarnawski
solid-js/web is the renderer that turns Solid components into real DOM. It is the module that the JSX compiler emits calls into, so almost every Solid app imports it (directly or indirectly) for render, hydrate, Portal, and Dynamic. Most of the low-level surface (insert, spread, template, delegateEvents, …) is re-exported from the sibling dom-expressions package and not implemented here.
Purpose
The browser entry of solid-js/web (packages/solid/web/src/index.ts) provides:
- Bundled re-exports of
dom-expressions's client runtime. This is what the compiler actually calls. hydrate(...)— a wrapper around the dom-expressions hydrate that flips theenableHydration()switch in the reactive runtime first.<Portal>— render outside the current parent (modals, tooltips, document.head injection).<Dynamic>/createDynamic— render a component or HTML element decided at runtime.- Re-exports of control-flow tags (
For,Show,Suspense,SuspenseList,Switch,Match,Index,ErrorBoundary,mergeProps) so the compiler'smoduleName: "solid-js/web"setting can resolve everything from one place. isServerandisDevbooleans that flip based on which build the export conditions resolve to.server-mock.tsshims forrenderToString,renderToStringAsync,renderToStream,ssr,ssrElement, etc., that error helpfully if called from a browser-condition build.
The server entry (packages/solid/web/server/index.ts) is documented separately in solid-js/web server entry.
Directory layout
packages/solid/web/
├── package.json # ./web export selector
├── README.md
├── tsconfig.build.json
├── tsconfig.json
├── src/ # browser entry
│ ├── index.ts # public surface: render, hydrate, Portal, Dynamic
│ ├── client.ts # `export * from "dom-expressions/src/client.js"`
│ ├── core.ts # rxcore for browser; re-exports from solid-js
│ ├── jsx.ts # type re-export
│ └── server-mock.ts # SSR-API stubs that error in the browser
├── server/
│ └── index.ts # SSR entry (Dynamic, Portal stub, mergeProps override)
├── storage/ # solid-js/web/storage entry (own page)
└── test/ # vitest suites for hydration, suspense, transitions, portalsThe client.ts file is one line: export * from "dom-expressions/src/client.js". Everything else in src/index.ts either layers Solid-specific behaviour on top, or re-exports from solid-js.
Key abstractions
| Symbol | File | Description |
|---|---|---|
render(code, element, init?) |
re-export from dom-expressions |
Mounts a component into a DOM element. Returns a dispose function. |
hydrate(code, element, options?) |
packages/solid/web/src/index.ts |
Like render, but reuses the existing DOM and attaches reactive subscriptions. Calls enableHydration() before delegating to dom-expressions. |
Portal<T, S> |
packages/solid/web/src/index.ts |
Renders children into a mount Node (default document.body), wrapped in a <div> (or <g> if isSVG), optionally inside a Shadow DOM root. Cleans up on disposal. |
Dynamic<T> |
packages/solid/web/src/index.ts |
Renders a component or string element decided at render time. |
createDynamic<T>(component, props) |
packages/solid/web/src/index.ts |
Lower-level form of Dynamic. Used by libraries that already have an accessor for the component identity. |
isServer |
packages/solid/web/src/index.ts |
false in browser builds, true in server builds. |
isDev |
packages/solid/web/src/index.ts |
"_SOLID_DEV_" placeholder, replaced at build time. |
insert, spread, template, delegateEvents, effect, memo, style, classList, setAttribute, dynamicProperty, mergeProps, getNextElement, Aliases, Properties, ChildProperties, DelegatedEvents, SVGElements, SVGNamespace |
re-exports from dom-expressions via client.ts |
The compiler-targeted runtime primitives. |
For, Show, Suspense, SuspenseList, Switch, Match, Index, ErrorBoundary |
re-exports from solid-js |
Control flow available from the same module name as the runtime primitives. |
How it works
graph TD
Compiler["JSX → babel-preset-solid<br>moduleName: 'solid-js/web'"]
SrcIndex["packages/solid/web/src/index.ts"]
Client["packages/solid/web/src/client.ts<br>(re-exports dom-expressions/client)"]
DomExp["dom-expressions<br>(workspace devDep)"]
Runtime["packages/solid/src<br>reactive runtime"]
ServerMock["server-mock.ts<br>SSR stubs that error"]
Compiler -->|insert / spread / template / etc.| SrcIndex
SrcIndex --> Client
Client --> DomExp
SrcIndex -->|hydrate, Portal, Dynamic| Runtime
SrcIndex -->|browser condition| ServerMockrender and hydrate
render is dom-expressions's; Solid does not re-implement it. Internally it allocates a fresh Owner via createRoot, runs the user component, calls insert to mount the produced fragment, and returns a disposer.
hydrate exists in two places:
- The dom-expressions implementation (
hydrateCorehere) takes the existing DOM tree and attaches reactive subscriptions in lockstep with the compiler-emittedtemplatemarkers. - Solid's wrapper at the top of
packages/solid/web/src/index.tscallsenableHydration()first. That flag (inpackages/solid/src/render/component.ts) makescreateComponentadvance thesharedConfig.context.idcounter so subsequent component invocations match the SSR-emitted ids.
export const hydrate: typeof hydrateCore = (...args) => {
enableHydration();
return hydrateCore(...args);
};Portal
Portal allocates a marker text node, then in a createEffect mounts an internal container (<div> or <g>) into the chosen parent and inserts the children into it. Special cases:
mountdefaults todocument.body.- If
mountis a<head>, the portal renders directly into the head (no extra container). useShadow: trueattaches a shadow root and renders inside it for style isolation.isSVG: trueswitches to an SVG<g>container.- The container's
_$hostgetter returnsmarker.parentNodeso descendant code that walks back up the tree sees the original logical parent. - On cleanup, the container is removed from
mount.
Dynamic and createDynamic
Dynamic is a thin wrapper that splits off the component prop and forwards the rest to createDynamic. The implementation creates a createMemo of component(), then in a nested memo dispatches:
typeof component === "function"→ call it as a component (with dev-component branding in the dev build).typeof component === "string"→ either claim the next hydration node orcreateElementit, thenspreadthe remaining props.
Both reactively re-render when component changes; switching a Dynamic from 'input' to 'textarea' swaps the underlying element while preserving sibling order.
The server-mock.ts shim
server-mock.ts is loaded only by the browser bundle (it is export * from "./server-mock.js" in src/index.ts). It defines stub bodies for every SSR API:
export function renderToString<T>(fn: () => T, options?): string {
throwInBrowser(renderToString);
}throwInBrowser calls console.error with a clear message. The @deprecated pipeToWritable and pipeToNodeWritable are also stubbed here.
This means: if your build accidentally pulls the browser entry of solid-js/web into Node, renderToString will not silently return undefined — you will see a <funcName> is not supported in the browser error in the console.
Key source files
| File | Purpose |
|---|---|
packages/solid/web/src/index.ts |
Browser entry — hydrate, Portal, Dynamic, createDynamic, control-flow re-exports, isServer, isDev. |
packages/solid/web/src/client.ts |
One-line re-export of dom-expressions's client runtime. |
packages/solid/web/src/core.ts |
rxcore shim for the browser; exports getOwner, createComponent, createRoot as root, createRenderEffect as effect, untrack, mergeProps, sharedConfig, and memo. The Rollup config rewrites import "rxcore" to this file at build time. |
packages/solid/web/src/server-mock.ts |
Browser-time stubs for SSR APIs, all marked @ts-nocheck. |
packages/solid/web/server/index.ts |
Server entry. See solid-js/web server entry. |
packages/solid/web/package.json |
The ./web exports selector with browser/dev/server/worker conditions. |
Integration points
- The compiler.
packages/babel-preset-solid/index.jsdefaultsmoduleName: "solid-js/web", meaning every JSX expression turns into named imports from this module. Seebabel-preset-solid. - The reactive runtime. Everything
solid-js/webdoes is built on top ofsolid-js's primitives —createSignal,createMemo,createEffect,createRoot,getOwner,runWithOwner,sharedConfig,enableHydration. See Reactivity primitives. dom-expressions. The bulk of the renderer is in this sibling library, declared as a workspace devDependency in the rootpackage.json. Theclient.tsre-export and therxcorerewrite (packages/solid/rollup.config.js) are the connection points.solid-element. Wraps Solid components as Web Components and usesinsert(fromsolid-js/web) pluscreateRootto mount them. Seesolid-element.solid-js/h,solid-js/html. Both consumespread,assign,insert,createComponent, etc. fromsolid-js/web. Seesolid-js/handsolid-js/html.
Entry points for modification
- Adding a new DOM-only component (e.g.
<HeadPortal>): export it frompackages/solid/web/src/index.ts, mirror it (likely as a stub) inpackages/solid/web/server/index.ts, and add tests underpackages/solid/web/test/. - Adjusting hydration: the
hydratewrapper inpackages/solid/web/src/index.tsandenableHydrationinpackages/solid/src/render/component.ts. The hydration protocol (id counters, marker comments) is governed bydom-expressionsandsharedConfig(packages/solid/src/render/hydration.ts), so coordinated changes are usually required. - Updating the dom-expressions integration: the workspace
dom-expressionsversion is bumped together withbabel-plugin-jsx-dom-expressions. Look for commits likeUpdate dom-expressions packages to 0.40.6for the cadence.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.