1.0.0-next · SolidJS 2.0

Fewer templates.
Same app.

A compile-time optimizer that inlines components, removes context providers, and folds control flow before Solid's JSX transform runs. More markup lands in fewer templates.

Get startedOpen the REPL
$npm i -D solid-optimizer@next

What changes in the build

Six small apps, compiled by Solid’s JSX transform alone and with solid-optimizer first. Pick one to compare the JSX, the client code, and the HTML the server sends.

186templates−67%
160component calls−100%
196hydration keys−68%
3931 B2211 Bclient code, gzip−44%

Three components and a debug branch fold into the page that uses them.

Solid’s JSX transform alone5 templates4 component calls4 hydration keys14 hydration markers988 B client code, gzip
import { template as _$template } from '@solidjs/web';
import { getNextElement as _$getNextElement } from '@solidjs/web';
import { getNextMarker as _$getNextMarker } from '@solidjs/web';
import { insert as _$insert } from '@solidjs/web';
import { scope as _$scope } from '@solidjs/web';
import { createComponent as _$createComponent } from '@solidjs/web';
import { className as _$className } from '@solidjs/web';
import { effect as _$effect } from '@solidjs/web';
import { addEvent as _$addEvent } from '@solidjs/web';
import { delegateEvents as _$delegateEvents } from '@solidjs/web';
import { runHydrationEvents as _$runHydrationEvents } from '@solidjs/web';
var _tmpl$ = /* @__PURE__ */ _$template(`<span>`);
var _tmpl$2 = /* @__PURE__ */ _$template(
  `<button type=button><!$><!/><!$><!/>`,
);
var _tmpl$3 = /* @__PURE__ */ _$template(
  `<p class=counter><!$><!/>: <!$><!/> <!$><!/>`,
);
var _tmpl$4 = /* @__PURE__ */ _$template(`<pre>debug panel`);
var _tmpl$5 = /* @__PURE__ */ _$template(
  `<main><h1>Hello</h1><!$><!/><!$><!/>`,
);
import { Show, createSignal } from 'solid-js';
import type { JSX } from '@solidjs/web';
const DEBUG = false;
function Icon(props: { name: string }) {
  var _el$ = _$getNextElement(_tmpl$);
  _$effect(
    () => `icon icon-${props.name}`,
    (_v$, _$p) => {
      _$className(_el$, _v$, _$p);
    },
  );
  return _el$;
}
function Button(props: {
  icon: string;
  onClick: () => void;
  children: JSX.Element;
}) {
  var _el$2 = _$getNextElement(_tmpl$2);
  var _el$3 = _el$2.firstChild;
  var [_el$4, _el$5] = _$getNextMarker(_el$3.nextSibling);
  var _el$6 = _el$4.nextSibling;
  var [_el$7, _el$8] = _$getNextMarker(_el$6.nextSibling);
  _$addEvent(_el$2, 'click', props.onClick, true);
  _$insert(
    _el$2,
    _$createComponent(Icon, {
      get name() {
        return props.icon;
      },
    }),
    _el$4,
    _el$5,
  );
  _$insert(
    _el$2,
    _$scope(() => {
      return props.children;
    }),
    _el$7,
    _el$8,
  );
  _$runHydrationEvents();
  return _el$2;
}
function Counter(props: { label: string; start: number }) {
  const [count, setCount] = createSignal(props.start);
  var _el$9 = _$getNextElement(_tmpl$3);
  var _el$10 = _el$9.firstChild;
  var [_el$11, _el$12] = _$getNextMarker(_el$10.nextSibling);
  var _el$13 = _el$11.nextSibling;
  var _el$14 = _el$13.nextSibling;
  var [_el$15, _el$16] = _$getNextMarker(_el$14.nextSibling);
  var _el$17 = _el$15.nextSibling;
  var _el$18 = _el$17.nextSibling;
  var [_el$19, _el$20] = _$getNextMarker(_el$18.nextSibling);
  _$insert(
    _el$9,
    _$scope(() => {
      return props.label;
    }),
    _el$11,
    _el$12,
  );
  _$insert(
    _el$9,
    _$scope(() => {
      return count();
    }),
    _el$15,
    _el$16,
  );
  _$insert(
    _el$9,
    _$createComponent(Button, {
      icon: 'plus',
      onClick: () => setCount(count() + 1),
      children: 'Add',
    }),
    _el$19,
    _el$20,
  );
  return _el$9;
}
export function App() {
  var _el$21 = _$getNextElement(_tmpl$5);
  var _el$22 = _el$21.firstChild;
  var _el$24 = _el$22.nextSibling;
  var [_el$25, _el$26] = _$getNextMarker(_el$24.nextSibling);
  var _el$27 = _el$25.nextSibling;
  var [_el$28, _el$29] = _$getNextMarker(_el$27.nextSibling);
  _$insert(
    _el$21,
    _$createComponent(Show, {
      when: DEBUG,
      get children() {
        return _$getNextElement(_tmpl$4);
      },
    }),
    _el$25,
    _el$26,
  );
  _$insert(
    _el$21,
    _$createComponent(Counter, {
      label: 'Clicks',
      start: 0,
    }),
    _el$28,
    _el$29,
  );
  return _el$21;
}
_$delegateEvents(['click']);
solid-optimizer, then the JSX transform1 template1 hydration key2 hydration markers527 B client code, gzip
import { template as _$template } from '@solidjs/web';
import { getNextElement as _$getNextElement } from '@solidjs/web';
import { getNextMarker as _$getNextMarker } from '@solidjs/web';
import { insert as _$insert } from '@solidjs/web';
import { scope as _$scope } from '@solidjs/web';
import { delegateEvents as _$delegateEvents } from '@solidjs/web';
import { runHydrationEvents as _$runHydrationEvents } from '@solidjs/web';
var _tmpl$ = /* @__PURE__ */ _$template(
  `<main><h1>Hello</h1><p class=counter>Clicks: <!$><!/> <button type=button><span class="icon icon-plus"></span>Add`,
);
import { Show, createSignal } from 'solid-js';
import type { JSX } from '@solidjs/web';
const DEBUG = false;
export function App() {
  const [count$1, setCount$1] = createSignal(0);
  var _el$ = _$getNextElement(_tmpl$);
  var _el$2 = _el$.firstChild;
  var _el$3 = _el$2.nextSibling;
  var _el$4 = _el$3.firstChild;
  var _el$5 = _el$4.nextSibling;
  var [_el$6, _el$7] = _$getNextMarker(_el$5.nextSibling);
  var _el$8 = _el$6.nextSibling;
  var _el$9 = _el$8.nextSibling;
  _$insert(
    _el$3,
    _$scope(() => {
      return count$1();
    }),
    _el$6,
    _el$7,
  );
  _el$9._$$click = () => setCount$1(count$1() + 1);
  _$runHydrationEvents();
  return _el$;
}
_$delegateEvents(['click']);
Open this example in the REPL →

Each example is one module, compiled by @solidjs/compiler with and without solid-optimizer first, and rendered on the server with renderToString. Sizes are the client code before minification.

Four passes, repeated until nothing changes

01

Component inlining

A call becomes the component’s JSX, so it joins the parent’s template.

function Title(props) {
  return <h1 class="title">{props.label}</h1>;
}

<main><Title label="Hello" /></main>
<main>
  <h1 class="title">{'Hello'}</h1>
</main>
02

Context inlining

A provider whose readers are all visible is removed. Each read gets its value.

<Theme value="dark">
  <Label />
</Theme>

// Label: useContext(Theme)
<span class={'dark'}>label</span>
03

Memo inlining

A memo that always makes a new value and is read once becomes its computation.

const style = createMemo(() => ({
  color: color(),
}));
<p style={style()} />
<p style={{ color: color() }} />
04

Control flow folding

<Show>, <Switch>, <For> and <Dynamic> resolve when their props are constants.

const LINKS = [{ href: '/' }, { href: '/docs' }];
<For each={LINKS}>
  {(link) => <a href={link.href} />}
</For>
<a href={'/'} /><a href={'/docs'} />

Less work at runtime

@solidjs/vite-pluginsolid-optimizer

renderToString

−74%
27.2 µs27.2 µs ±1.0 µs@solidjs/vite-plugin
7.0 µs7.0 µs ±1.0 µssolid-optimizer

Hydration

−47%
4.25 ms4.25 ms ±90.0 µs@solidjs/vite-plugin
2.25 ms2.25 ms ±90.0 µssolid-optimizer

First render

−48%
3.84 ms3.84 ms ±55.0 µs@solidjs/vite-plugin
2.00 ms2.00 ms ±55.0 µssolid-optimizer

Render, warm

−65%
52.3 µs52.3 µs ±1.6 µs@solidjs/vite-plugin
18.3 µs18.3 µs ±1.6 µssolid-optimizer

Heap per app

−81%
34.8 kB34.8 kB ±5 B@solidjs/vite-plugin
6.6 kB6.6 kB ±5 Bsolid-optimizer

Templates cloned

−88%
4242@solidjs/vite-plugin
55solid-optimizer

Owners

−86%
138138@solidjs/vite-plugin
1919solid-optimizer

Computations

−85%
7878@solidjs/vite-plugin
1212solid-optimizer

Dependency edges

−73%
3030@solidjs/vite-plugin
88solid-optimizer
Every measure
Measure@solidjs/vite-pluginsolid-optimizerChange
Timings
First client render, fresh page3.84 ms2.00 ms−48%
Client render, warm52.3 µs18.3 µs−65%
Dispose, warm4.2 µs2.2 µs−49%
Heap held by one mounted app34.8 kB6.6 kB−81%
Hydration, fresh page4.25 ms2.25 ms−47%
renderToString, warm27.2 µs7.0 µs−74%
Client render
Script, gzip22.2 kB13.9 kB−37%
Templates created212−90%
Templates cloned425−88%
Elements66660%
Text nodes3534−3%
Comment markers140−100%
Owners13819−86%
Computations7812−85%
Signals read770%
Dependency edges308−73%
Server render
HTML2.6 kB2.0 kB−25%
HTML, gzip878 B725 B−17%
Hydration keys (_hk)435−88%
Comment markers440−100%
Hydration
Script, gzip28.2 kB20.8 kB−26%
Nodes created000%
Server nodes removed000%
Templates cloned000%

The demo app with every page rendered at once. It is written to show each pass. 2026-11-07. Median of 15 runs on Apple M2, Chromium 156 and Node v26.12.1.

Drop-in for Vite

One import changes. The plugin wraps @solidjs/vite-plugin, so SSR, server functions, and HMR keep working.

vite.config.ts
// vite.config.ts
import { defineConfig } from 'vite';
import solid from 'solid-optimizer/vite';

export default defineConfig({
  // Replaces @solidjs/vite-plugin, with the same options.
  plugins: [solid()],
});
compile.ts
import { compile } from 'solid-optimizer';

const result = compile(code, { filename: 'app.tsx' });

result.code; // JSX, ready for Solid's JSX transform
result.map;  // a source map, or null when nothing changed

Try it on your components

The REPL runs solid-optimizer and Solid’s JSX transform in your browser, as WebAssembly. Paste a few modules and compare the output, the templates, and the size.