Compile-time functions

Compile-time functions (CTFs) look like regular calls but are erased at build time and replaced with Solid 2.0 primitives. solid-labels declares them globally, so they need no imports, and every Solid API the output uses is imported for you.

Every example on this page is compiled by the real compiler when the site is built. Where the dev build differs from the prod build, a switch appears above the output. In dev, signals and memos also get a name option.

#Reactivity

#$signal

function $signal<T>(): T | undefined;
function $signal<T>(
  value: T,
  options?: {
    equals?: false | ((prev: T, next: T) => boolean);
    name?: string;
    ownedWrite?: boolean;
    unobserved?: () => void;
  },
): T;

Declares a signal variable. Reads compile to accessor calls, writes to setter calls. Only allowed as a variable initializer.

Memoized signal convention: a static initializer (literal, identifier, static object/array) is stored as-is. A non-static initializer, or one that reads another reactive variable, is wrapped in a compute function, producing Solid 2.0's writable derived form:

let value = $signal(foo());
let count = $signal(0);
let doubled = $signal(count * 2);
import { createSignal as _createSignal } from "solid-js";
let [value, setvalue_1] = _createSignal(() => foo());
let [count, setcount_1] = _createSignal(0);
let [doubled, setdoubled_1] = _createSignal(() => count() * 2);
import { createSignal as _createSignal } from "solid-js";
let [value, setvalue_1] = _createSignal(() => foo(), { name: "value" });
let [count, setcount_1] = _createSignal(0, { name: "count" });
let [doubled, setdoubled_1] = _createSignal(() => count() * 2, { name: "doubled" });

doubled recomputes from count until it is written, then holds the written value, as createSignal(fn) does. Pass a function explicitly ($signal(() => expr)) to opt in regardless of the initializer's shape.

let count = $signal(0);
console.log(count);
count = 10;
count += 1;
count++;
import { createSignal as _createSignal } from "solid-js";
let [count, setcount_1] = _createSignal(0);
console.log(count());
setcount_1(() => 10);
setcount_1(current_1 => current_1 += 1);
setcount_1(current_2 => ++current_2);
import { createSignal as _createSignal } from "solid-js";
let [count, setcount_1] = _createSignal(0, { name: "count" });
console.log(count());
setcount_1(() => 10);
setcount_1(current_1 => current_1 += 1);
setcount_1(current_2 => ++current_2);

Assignments whose right side awaits or yields are split so the setter never captures the promise:

let count = $signal(0);

async function example() {
  count = await asyncValue();
}
import { createSignal as _createSignal } from "solid-js";
let [count, setcount_1] = _createSignal(0);

async function example() {
  const tmp_1 = await asyncValue();
setcount_1(() => tmp_1);
}
import { createSignal as _createSignal } from "solid-js";
let [count, setcount_1] = _createSignal(0, { name: "count" });

async function example() {
  const tmp_1 = await asyncValue();
setcount_1(() => tmp_1);
}

#$memo

function $memo<T>(
  value: T,
  options?: {
    equals?: false | ((prev: T, next: T) => boolean);
    name?: string;
    lazy?: boolean;
    unobserved?: () => void;
  },
): T;

Declares a derived variable. Reads compile to accessor calls. Writes are a compile error.

let count = $signal(0);
const message = $memo(`Count: ${count}`);
console.log(message);
import { createSignal as _createSignal } from "solid-js";
import { createMemo as _createMemo } from "solid-js";
let [count, setcount_1] = _createSignal(0);
const message = _createMemo(() => `Count: ${count()}`);
console.log(message());
import { createSignal as _createSignal } from "solid-js";
import { createMemo as _createMemo } from "solid-js";
let [count, setcount_1] = _createSignal(0, { name: "count" });
const message = _createMemo(() => `Count: ${count()}`, { name: "message" });
console.log(message());

#$

Shorthand with two forms: in a variable declaration it is $memo; as a statement the expression is compiled exactly like an $effect body: it captures identifiers, generates a compute phase and hoists the callback.

let count = $signal(0);
const message = $(`Count: ${count}`);
$(console.log(message));
import { createSignal as _createSignal } from "solid-js";
import { createMemo as _createMemo } from "solid-js";
import { createEffect as _createEffect } from "solid-js";
let [count, setcount_1] = _createSignal(0);
const message = _createMemo(() => `Count: ${count()}`);
_createEffect(() => [message()], effectCallback_1);
function effectCallback_1([message]) {
  console.log(message);
}
import { createSignal as _createSignal } from "solid-js";
import { createMemo as _createMemo } from "solid-js";
import { createEffect as _createEffect } from "solid-js";
let [count, setcount_1] = _createSignal(0, { name: "count" });
const message = _createMemo(() => `Count: ${count()}`, { name: "message" });
_createEffect(() => [message()], effectCallback_1);
function effectCallback_1([message]) {
  console.log(message);
}

The expression may not await or yield, and its value is discarded (it is emitted as a statement, so a function value is never mistaken for an effect cleanup).

#$effect and $renderEffect

function $effect(callback: () => (() => void) | void, options?: EffectOptions): void;
function $renderEffect(callback: () => (() => void) | void, options?: EffectOptions): void;

Solid 2.0 effects have two phases: a tracked compute function and an untracked effect function. $effect compiles the familiar single-callback form into both phases mechanically:

  1. The compiler collects every outer identifier the callback uses.
  2. The generated compute phase returns their current values. Signals and memos are read there, so that phase does the tracking.
  3. The callback receives the values as a destructured array parameter. Its body reads plain values and stays untracked.
function App() {
  let bar = $signal(someValue());
  $effect(() => {
    foo(bar);
  });
}
import { createSignal as _createSignal } from "solid-js";
import { createEffect as _createEffect } from "solid-js";
function App() {
  let [bar, setbar_1] = _createSignal(() => someValue());
  _createEffect(() => [bar()], AppEffect_1);
}
function AppEffect_1([bar]) {
    foo(bar);
  }
import { createSignal as _createSignal } from "solid-js";
import { createEffect as _createEffect } from "solid-js";
function App() {
  let [bar, setbar_1] = _createSignal(() => someValue(), { name: "bar" });
  _createEffect(() => [bar()], AppEffect_1);
}
function AppEffect_1([bar]) {
    foo(bar);
  }

The callback is hoisted to module level as a named function whenever that is safe. Its name comes from the nearest enclosing named scope, such as AppEffect_1 inside function App() (also picked up through const App = ..., $component(...), object methods, and assignments), with effectCallback_1 as the module-level fallback. It stays inline, with the same array parameter, when the callback:

  • writes to a plain (non-signal) outer variable, which must keep closure semantics, or
  • uses this, super, or arguments.
function App() {
  let el;
  let count = $signal(0);
  $effect(() => {
    el = render(count);
  });
}
import { createSignal as _createSignal } from "solid-js";
import { createEffect as _createEffect } from "solid-js";
function App() {
  let el;
  let [count, setcount_1] = _createSignal(0);
  _createEffect(() => [count()], ([count]) => {
    el = render(count);
  });
}
import { createSignal as _createSignal } from "solid-js";
import { createEffect as _createEffect } from "solid-js";
function App() {
  let el;
  let [count, setcount_1] = _createSignal(0, { name: "count" });
  _createEffect(() => [count()], ([count]) => {
    el = render(count);
  });
}

Signal writes inside the callback work. The compiler captures the setter:

let count = $signal(0);
$effect(() => {
  count += 1;
});
import { createSignal as _createSignal } from "solid-js";
import { createEffect as _createEffect } from "solid-js";
let [count, setcount_1] = _createSignal(0);
_createEffect(() => [setcount_1], effectCallback_1);
function effectCallback_1([setcount_1]) {
  setcount_1(current_1 => current_1 += 1);
}
import { createSignal as _createSignal } from "solid-js";
import { createEffect as _createEffect } from "solid-js";
let [count, setcount_1] = _createSignal(0, { name: "count" });
_createEffect(() => [setcount_1], effectCallback_1);
function effectCallback_1([setcount_1]) {
  setcount_1(current_1 => current_1 += 1);
}

Rules:

  • The callback takes no parameters. Captured values are injected by the compiler.
  • The callback must be synchronous (no async, no generators).
  • Module-scope plain values are not captured. They resolve directly.
  • Reads wrapped in $untrack(...) are excluded from the tracked source list: the signal's accessor is passed through the capture array (a stable identity that never triggers the effect) and the body performs a real untracked read, untrack(() => count()), at effect time. A signal read both inside and outside $untrack in the same callback is tracked.
  • A function returned from the callback is the cleanup: it runs before the next application and on dispose, exactly as in createEffect. (The $(expr); statement form discards the expression value. Use the callback form when you need cleanup.)
  • An optional second argument is passed through as EffectOptions ({ defer: true }, …).

$renderEffect compiles identically to createRenderEffect. Reach for it only when authoring renderer internals.

let width = $signal(0);

$renderEffect(() => {
  element.style.width = `${width}px`;
});
import { createSignal as _createSignal } from "solid-js";
import { createRenderEffect as _createRenderEffect } from "solid-js";
let [width, setwidth_1] = _createSignal(0);

_createRenderEffect(() => [width()], effectCallback_1);
function effectCallback_1([width]) {
  element.style.width = `${width}px`;
}
import { createSignal as _createSignal } from "solid-js";
import { createRenderEffect as _createRenderEffect } from "solid-js";
let [width, setwidth_1] = _createSignal(0, { name: "width" });

_createRenderEffect(() => [width()], effectCallback_1);
function effectCallback_1([width]) {
  element.style.width = `${width}px`;
}

#$untrack

function $untrack<T>(value: T): T;

Compiles to untrack, wrapping the value in an arrow function.

let count = $signal(0);
const value = $untrack(count);
import { createSignal as _createSignal } from "solid-js";
import { untrack as _untrack } from "solid-js";
let [count, setcount_1] = _createSignal(0);
const value = _untrack(() => count());
import { createSignal as _createSignal } from "solid-js";
import { untrack as _untrack } from "solid-js";
let [count, setcount_1] = _createSignal(0, { name: "count" });
const value = _untrack(() => count());

Inside an $effect callback, $untrack also changes what the effect tracks: reads wrapped in it are excluded from the generated source list.

Solid 2.0 batches writes by default, so there is no $batch. Use $flush to apply queued writes synchronously.

#$root, $reaction, $flush, $onFlush

Aliases for createRoot, createReaction, flush and onFlush. $onFlush(callback) runs the callback once after the pending flush.

let count = $signal(0);

const dispose = $root((dispose) => {
  $onFlush(() => console.log('flushed'));
  return dispose;
});

const track = $reaction(() => console.log('changed'));

count += 1;
$flush();
import { createSignal as _createSignal } from "solid-js";
import { createRoot as _createRoot } from "solid-js";
import { onFlush as _onFlush } from "solid-js";
import { createReaction as _createReaction } from "solid-js";
import { flush as _flush } from "solid-js";
let [count, setcount_1] = _createSignal(0);

const dispose = _createRoot((dispose) => {
  _onFlush(() => console.log('flushed'));
  return dispose;
});

const track = _createReaction(() => console.log('changed'));

setcount_1(current_1 => current_1 += 1);
_flush();
import { createSignal as _createSignal } from "solid-js";
import { createRoot as _createRoot } from "solid-js";
import { onFlush as _onFlush } from "solid-js";
import { createReaction as _createReaction } from "solid-js";
import { flush as _flush } from "solid-js";
let [count, setcount_1] = _createSignal(0, { name: "count" });

const dispose = _createRoot((dispose) => {
  _onFlush(() => console.log('flushed'));
  return dispose;
});

const track = _createReaction(() => console.log('changed'));

setcount_1(current_1 => current_1 += 1);
_flush();

#$owner, $observer, $isDisposed

Aliases for getOwner, getObserver and isDisposed. $observer() returns the current tracking scope, or null outside one. $isDisposed(owner) tells a late callback whether its owner is gone.

const owner = $owner();
const scope = $observer();
queueMicrotask(() => {
  if (!$isDisposed(owner)) {
    $runWithOwner(owner, work);
  }
});
import { getOwner as _getOwner } from "solid-js";
import { getObserver as _getObserver } from "solid-js";
import { isDisposed as _isDisposed } from "solid-js";
import { runWithOwner as _runWithOwner } from "solid-js";
const owner = _getOwner();
const scope = _getObserver();
queueMicrotask(() => {
  if (!_isDisposed(owner)) {
    _runWithOwner(owner, work);
  }
});

#Lifecycles

  • $settled → onSettled. Runs once after the current activity settles. Return a cleanup function for teardown. This replaces the 1.x onMount/onCleanup pairing for component-level lifecycle.
  • $cleanup → onCleanup. Reactive cleanup inside computations (library/primitive internals).
$settled(() => {
  const id = setInterval(tick, 1000);
  return () => clearInterval(id);
});

$cleanup(() => console.log('the owner is disposed'));
import { onSettled as _onSettled } from "solid-js";
import { onCleanup as _onCleanup } from "solid-js";
_onSettled(() => {
  const id = setInterval(tick, 1000);
  return () => clearInterval(id);
});

_onCleanup(() => console.log('the owner is disposed'));

#Async

Compile-time aliases for the Solid 2.0 async surface:

  • $action → action
  • $isPending → isPending
  • $latest → latest
  • $refresh → refresh
  • $affects → affects
  • $flush → flush
  • $resolve → resolve (the value is wrapped in an arrow function)
  • $until → until (the predicate is wrapped in an arrow function)
  • $from → from. Turns a subscription, an Observable or an async iterable into an async iterable for a computation
const save = $action(function* (todo) {
  yield api.add(todo);
  $refresh(todos);
});
const settled = await $resolve(user);
await $until(user, { timeout: 5000 });
const fresh = $latest(() => user);
$affects(save);
const position = $memo(() =>
  $from((next, error) => {
    const id = navigator.geolocation.watchPosition(next, error);
    return () => navigator.geolocation.clearWatch(id);
  }),
);
import { action as _action } from "solid-js";
import { refresh as _refresh } from "solid-js";
import { resolve as _resolve } from "solid-js";
import { until as _until } from "solid-js";
import { latest as _latest } from "solid-js";
import { affects as _affects } from "solid-js";
import { createMemo as _createMemo } from "solid-js";
import { from as _from } from "solid-js";
const save = _action(function* (todo) {
  yield api.add(todo);
  _refresh(todos);
});
const settled = await _resolve(() => user);
await _until(() => user, { timeout: 5000 });
const fresh = _latest(() => user);
_affects(save);
const position = _createMemo(() =>
  _from((next, error) => {
    const id = navigator.geolocation.watchPosition(next, error);
    return () => navigator.geolocation.clearWatch(id);
  }),
);
import { action as _action } from "solid-js";
import { refresh as _refresh } from "solid-js";
import { resolve as _resolve } from "solid-js";
import { until as _until } from "solid-js";
import { latest as _latest } from "solid-js";
import { affects as _affects } from "solid-js";
import { createMemo as _createMemo } from "solid-js";
import { from as _from } from "solid-js";
const save = _action(function* (todo) {
  yield api.add(todo);
  _refresh(todos);
});
const settled = await _resolve(() => user);
await _until(() => user, { timeout: 5000 });
const fresh = _latest(() => user);
_affects(save);
const position = _createMemo(() =>
  _from((next, error) => {
    const id = navigator.geolocation.watchPosition(next, error);
    return () => navigator.geolocation.clearWatch(id);
  }), { name: "position" },
);

#$deferred

Declaration CTF for createDeferred. The variable becomes an accessor under its original name, like $memo. After its first answer, a refetch keeps the previous value until the new one lands, and $isPending reports the refetch. The value is wrapped in an arrow function.

In 1.x, $deferred lagged a sync signal and took timeoutMs. In 2.0 it wraps an async computation, and timeoutMs is gone.

let period = $signal('week');
const rows = $deferred(fetchRows(period));
const stale = $isPending(() => rows);
import { createSignal as _createSignal } from "solid-js";
import { createDeferred as _createDeferred } from "solid-js";
import { isPending as _isPending } from "solid-js";
let [period, setperiod_1] = _createSignal('week');
const rows = _createDeferred(() => fetchRows(period()));
const stale = _isPending(() => rows());
import { createSignal as _createSignal } from "solid-js";
import { createDeferred as _createDeferred } from "solid-js";
import { isPending as _isPending } from "solid-js";
let [period, setperiod_1] = _createSignal('week', { name: "period" });
const rows = _createDeferred(() => fetchRows(period()), { name: "rows" });
const stale = _isPending(() => rows());

#Objects

#$destructure

function $destructure<T>(value: T): T;

Destructures an object or array while retaining reactivity: every leaf becomes an accessor under its original name. Rest elements compile to omit. Supports nesting, defaults, and computed keys.

let {
  a: { b, c },
  b: { d = defaultD, e = compute() },
  ...f
} = $destructure(x);
console.log(b, c, d, e, f);
import { createMemo as _createMemo } from "solid-js";
import { omit as _omit } from "solid-js";
let prop_1 = () => x.a,
  b = () => prop_1().b,
  c = () => prop_1().c,
  prop_2 = () => x.b,
  def_1 = defaultD,
  d = () => { const value_1 = prop_2().d; return value_1 === void 0 ? def_1 : value_1; },
  def_2 = _createMemo(() => compute()),
  e = () => { const value_2 = prop_2().e; return value_2 === void 0 ? def_2() : value_2; },
  f = _omit(x, "a", "b");
console.log(b(), c(), d(), e(), f);

If the source is itself reactive, every property access re-reads it:

let state = $signal({ a: 1 });
const { a } = $destructure(state);
import { createSignal as _createSignal } from "solid-js";
let [state, setstate_1] = _createSignal({ a: 1 });
const a = () => state().a;
import { createSignal as _createSignal } from "solid-js";
let [state, setstate_1] = _createSignal({ a: 1 }, { name: "state" });
const a = () => state().a;

#$component

Wraps a component function and turns an object-pattern parameter into a reactive destructure. Props stay live even though they look destructured.

const App = $component(({ name, count = 0 }) => {
  return (
    <div>
      {name}: {count}
    </div>
  );
});
const App = (props_1) => {
  const name = () => props_1.name,
    def_1 = 0,
    count = () => { const value_1 = props_1.count; return value_1 === void 0 ? def_1 : value_1; };
  return (
    <div>
      {name()}: {count()}
    </div>
  );
};

#$merge and $omit

Aliases for merge (formerly mergeProps) and omit (formerly splitProps).

const merged = $merge(defaults, props);
const rest = $omit(props, 'class', 'style');
import { merge as _merge } from "solid-js";
import { omit as _omit } from "solid-js";
const merged = _merge(defaults, props);
const rest = _omit(props, 'class', 'style');

#$getter, $setter and $property

Expose a reactive variable as a live object property. Only valid as object property values.

let count = $signal(0);
const a = { value: $getter(count) };
const b = { value: $setter(count) };
const c = { value: $property(count) };
import { createSignal as _createSignal } from "solid-js";
let [count, setcount_1] = _createSignal(0);
const a = { get value() { return count(); } };
const b = { set value(param_1) { setcount_1(() => param_1); } };
const c = { get value() { return count(); }, set value(param_2) { setcount_1(() => param_2); } };
import { createSignal as _createSignal } from "solid-js";
let [count, setcount_1] = _createSignal(0, { name: "count" });
const a = { get value() { return count(); } };
const b = { set value(param_1) { setcount_1(() => param_1); } };
const c = { get value() { return count(); }, set value(param_2) { setcount_1(() => param_2); } };

#Arrays

#$mapArray

function $mapArray<T, U>(
  arr: readonly T[] | undefined | null | false,
  mapFn: (v: T, i: Accessor<number>) => U,
  options?: { keyed?: true; fallback?: Accessor<any>; name?: string },
): U[];
function $mapArray<T, U>(
  arr: readonly T[] | undefined | null | false,
  mapFn: (v: Accessor<T>, i: number) => U,
  options: { keyed: false; fallback?: Accessor<any>; name?: string },
): U[];

Compiles to mapArray; the result is an accessor variable. Non-keyed mapping uses { keyed: false }. indexArray is gone in 2.0.

let list = $signal([]);
const squares = $mapArray(list, (value) => value ** 2);
import { createSignal as _createSignal } from "solid-js";
import { mapArray as _mapArray } from "solid-js";
let [list, setlist_1] = _createSignal([]);
const squares = _mapArray(() => list(), (value) => value ** 2);
import { createSignal as _createSignal } from "solid-js";
import { mapArray as _mapArray } from "solid-js";
let [list, setlist_1] = _createSignal([], { name: "list" });
const squares = _mapArray(() => list(), (value) => value ** 2);

#$repeat

function $repeat<U>(
  count: number,
  mapFn: (i: number) => U,
  options?: { from?: number; fallback?: Accessor<any>; name?: string },
): U[];

Compiles to repeat. Count-driven rendering, the underlying helper of <Repeat>.

let total = $signal(10);
const rows = $repeat(total, (index) => index * 2);
import { createSignal as _createSignal } from "solid-js";
import { repeat as _repeat } from "solid-js";
let [total, settotal_1] = _createSignal(10);
const rows = _repeat(() => total(), (index) => index * 2);
import { createSignal as _createSignal } from "solid-js";
import { repeat as _repeat } from "solid-js";
let [total, settotal_1] = _createSignal(10, { name: "total" });
const rows = _repeat(() => total(), (index) => index * 2);

#Component APIs

#$uid

Alias for createUniqueId.

const id = $uid();
import { createUniqueId as _createUniqueId } from "solid-js";
const id = _createUniqueId();

#$lazy

function $lazy<T extends Component<any>>(
  fn: Promise<{ default: T }>,
): T & {
  preload: () => void;
};
const Page = $lazy(import('./Page'));
import { lazy as _lazy } from "solid-js";
const Page = _lazy(() => import('./Page'));

#$lazyModule

Wraps a dynamic import for a module that is not a component, like $lazy does for a component. The promise is wrapped in an arrow function. The result is callable and has preload() and peek().

const admin = $lazyModule(import('./admin/routes'));
import { lazyModule as _lazyModule } from "solid-js";
const admin = _lazyModule(() => import('./admin/routes'));

#$children

Resolves children into an accessor variable.

const nodes = $children(props.children);
console.log(nodes);
import { children as _children } from "solid-js";
const nodes = _children(() => props.children);
console.log(nodes());

#Context

const CounterContext = $createContext(0);
const value = $useContext(CounterContext);
import { createContext as _createContext } from "solid-js";
import { useContext as _useContext } from "solid-js";
const CounterContext = _createContext(0);
const value = _useContext(CounterContext);

Compiles to createContext / useContext. In 2.0 the context value is the provider: <CounterContext value={10}>...</CounterContext>.

#Owner

$owner → getOwner, $runWithOwner → runWithOwner.

#Refs and Derefs

Opt in and out of the variable syntax. Use these to return real signal tuples or to adopt them from third-party code.

function $refSignal<T>(value: T): [Accessor<T>, Setter<T>];
function $refMemo<T>(value: T): Accessor<T>;
function $get<T>(value: T): Accessor<T>;
function $set<T>(value: T): Setter<T>;
function $derefSignal<T>(value: [Accessor<T>, Setter<T>]): T;
function $derefMemo<T>(value: Accessor<T>): T;
let count = $signal(0);
const tuple = $refSignal(count);
const read = $get(count);
const write = $set(count);

let adopted = $derefSignal(useCounter());
adopted += 1;

const double = $memo(count * 2);
const accessor = $refMemo(double);
const external = $derefMemo(useDouble());
console.log(external);
import { createSignal as _createSignal } from "solid-js";
import { createMemo as _createMemo } from "solid-js";
let [count, setcount_1] = _createSignal(0);
const tuple = [count, setcount_1];
const read = count;
const write = setcount_1;

let [adopted, setadopted_1] = useCounter();
setadopted_1(current_1 => current_1 += 1);

const double = _createMemo(() => count() * 2);
const accessor = double;
const external = useDouble();
console.log(external());
import { createSignal as _createSignal } from "solid-js";
import { createMemo as _createMemo } from "solid-js";
let [count, setcount_1] = _createSignal(0, { name: "count" });
const tuple = [count, setcount_1];
const read = count;
const write = setcount_1;

let [adopted, setadopted_1] = useCounter();
setadopted_1(current_1 => current_1 += 1);

const double = _createMemo(() => count() * 2, { name: "double" });
const accessor = double;
const external = useDouble();
console.log(external());

#Stores

Store APIs live in solid-js on Solid 2.0:

  • $store → createStore
  • $projection → createProjection
  • $optimistic → createOptimistic
  • $optimisticStore → createOptimisticStore
  • $reconcile → reconcile
  • $snapshot → snapshot
  • $deep → deep
  • $isWrappable → isWrappable
const [state, setState] = $store({ user: { name: 'Ada' }, items: [] });
const [view] = $projection((draft) => {
  draft.count = state.items.length;
}, { count: 0 });
const [likes, setLikes] = $optimistic(0);
const [todos, setTodos] = $optimisticStore({ list: [] });

setState((draft) => {
  draft.user = $reconcile(next.user);
});

console.log($snapshot(state), $deep(state), $isWrappable(state));
import { createStore as _createStore } from "solid-js";
import { createProjection as _createProjection } from "solid-js";
import { createOptimistic as _createOptimistic } from "solid-js";
import { createOptimisticStore as _createOptimisticStore } from "solid-js";
import { reconcile as _reconcile } from "solid-js";
import { snapshot as _snapshot } from "solid-js";
import { deep as _deep } from "solid-js";
import { isWrappable as _isWrappable } from "solid-js";
const [state, setState] = _createStore({ user: { name: 'Ada' }, items: [] });
const [view] = _createProjection((draft) => {
  draft.count = state.items.length;
}, { count: 0 });
const [likes, setLikes] = _createOptimistic(0);
const [todos, setTodos] = _createOptimisticStore({ list: [] });

setState((draft) => {
  draft.user = _reconcile(next.user);
});

console.log(_snapshot(state), _deep(state), _isWrappable(state));

createMutable and produce are gone. Store setters are draft-first.

#Children

  • $flatten → flatten
const flat = $flatten(props.children);
import { flatten as _flatten } from "solid-js";
const flat = _flatten(props.children);

#Web

From @solidjs/web:

  • $useHead → useHead
  • $clientOnly → clientOnly. Like $lazy, the promise is wrapped in an arrow function: $clientOnly(import('./Chart')).
$useHead({ tag: 'title', props: { children: 'Docs' } });

const Chart = $clientOnly(import('./Chart'));
import { useHead as _useHead } from "@solidjs/web";
import { clientOnly as _clientOnly } from "@solidjs/web";
_useHead({ tag: 'title', props: { children: 'Docs' } });

const Chart = _clientOnly(() => import('./Chart'));

dynamic, render, hydrate, isServer, isDev, isHydrating, isHydratable and onHydrationEnd have no CTF. Import them from @solidjs/web.

#Components

Unshadowed component identifiers are auto-imported, in JSX and in expression positions: For, Show, Switch, Match, Repeat, Errored, Loading, Reveal from solid-js; Portal, HydrationScript, Hydration, NoHydration from @solidjs/web. Dynamic is not auto-imported, because Solid deprecates it. Use dynamic() from @solidjs/web.

const view = (
  <>
    <For each={list}>{(item) => <li>{item}</li>}</For>
    <Show when={user} fallback={<p>Signed out</p>}>
      <p>Signed in</p>
    </Show>
    <Switch fallback={<p>Not found</p>}>
      <Match when={route === 'home'}>
        <p>Home</p>
      </Match>
    </Switch>
    <Repeat count={3}>{(index) => <i>{index}</i>}</Repeat>
    <Errored fallback={<p>Failed</p>}>
      <Panel />
    </Errored>
    <Loading fallback={<p>Loading</p>}>
      <Panel />
    </Loading>
    <Reveal collapsed>
      <Loading>
        <Panel />
      </Loading>
    </Reveal>
    <Portal mount={document.body}>
      <p>Moved</p>
    </Portal>
    <NoHydration>
      <p>Static</p>
      <Hydration>
        <Panel />
      </Hydration>
    </NoHydration>
    <HydrationScript />
  </>
);
import { For as _For } from "solid-js";
import { Show as _Show } from "solid-js";
import { Switch as _Switch } from "solid-js";
import { Match as _Match } from "solid-js";
import { Repeat as _Repeat } from "solid-js";
import { Errored as _Errored } from "solid-js";
import { Loading as _Loading } from "solid-js";
import { Reveal as _Reveal } from "solid-js";
import { Portal as _Portal } from "@solidjs/web";
import { NoHydration as _NoHydration } from "@solidjs/web";
import { Hydration as _Hydration } from "@solidjs/web";
import { HydrationScript as _HydrationScript } from "@solidjs/web";
const view = (
  <>
    <_For each={list}>{(item) => <li>{item}</li>}</_For>
    <_Show when={user} fallback={<p>Signed out</p>}>
      <p>Signed in</p>
    </_Show>
    <_Switch fallback={<p>Not found</p>}>
      <_Match when={route === 'home'}>
        <p>Home</p>
      </_Match>
    </_Switch>
    <_Repeat count={3}>{(index) => <i>{index}</i>}</_Repeat>
    <_Errored fallback={<p>Failed</p>}>
      <Panel />
    </_Errored>
    <_Loading fallback={<p>Loading</p>}>
      <Panel />
    </_Loading>
    <_Reveal collapsed>
      <_Loading>
        <Panel />
      </_Loading>
    </_Reveal>
    <_Portal mount={document.body}>
      <p>Moved</p>
    </_Portal>
    <_NoHydration>
      <p>Static</p>
      <_Hydration>
        <Panel />
      </_Hydration>
    </_NoHydration>
    <_HydrationScript />
  </>
);

The solid: JSX namespace maps the same set. See solid: namespace. Any other solid: element name is a compile error.

#Tooling

#TypeScript

On any d.ts file, add:

/// <reference types="solid-labels" />

All CTFs are declared globally, so no imports are needed.

#Disabling CTFs

solidLabels({
  disabled: {
    ctf: {
      $signal: true,
    },
  },
});

#Programmatic use

import { compile } from 'solid-labels/compiler';

const { code, map } = compile('App.tsx', source, { dev: true });

The compiler parses with oxc-parser and emits span edits through magic-string, so the untouched parts of your code keep their formatting and the source map is high-resolution.

Nextsolid: namespace