Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/hello-world-under-10kb.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@solidjs/signals": patch
"@solidjs/web": patch
---

Smaller floor bundles: the optional engines (lanes, verdicts, boundaries, stores, affects, external sources) install their scheduler hooks as module variables, so an app that never loads them no longer retains their paths in `recompute`, `read` and the settle step; client-only `render` no longer retains hydration text adoption, and the delegated dispatcher drops dead guards. The JSX hello world goes from 10.35 KB to 9.75 KB brotli.
2 changes: 1 addition & 1 deletion examples/chat-client/test/chat.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const shown = (text: string) =>

// Wall-clock findings (hot scopes, a fallback shown "too briefly") measure
// the test's fake clock, not the app: off, as in the runtime's own suites.
const attribution = { hotTime: false, fallbackFlashes: false } as const;
const attribution = { hotTime: false, fallbackFlashes: false, wastedRecompute: false } as const;

let root: HTMLDivElement;
let dispose: () => void;
Expand Down
13 changes: 9 additions & 4 deletions packages/signals/src/affects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@

import { STATUS_PENDING, CONFIG_SLOT_NODE, $REFRESH } from "./core/constants.js";
import { emitDiagnostic } from "./core/dev.js";
import { flushTransaction, GlobalQueue, resolveTx, schedule } from "./core/scheduler.js";
import {
flushTransaction,
GlobalQueue,
resolveTx,
schedule,
installMarkHooks
} from "./core/scheduler.js";
import type { Computed } from "./core/types.js";
import { mark, repoll, unmark, type Marked } from "./affects-marks.js";
import type { Accessor } from "./signals.js";
Expand Down Expand Up @@ -122,8 +128,7 @@ function invalid(detail: string): never {
throw new Error(message);
}

GlobalQueue._releaseMarks = release;
GlobalQueue._releaseAmbientMarks = parked => {
installMarkHooks(release, parked => {
if (ambient.length === 0) return;
// The flush parked: the declaration's window is the frame's — the marks
// are its transaction's ("nothing async below ⇒ no window").
Expand All @@ -146,7 +151,7 @@ GlobalQueue._releaseAmbientMarks = parked => {
}
ambient.length = kept;
if (done.length !== 0) release(done);
};
});
function inFlight(n: Marked): boolean {
return (
((n as Computed<any>)._statusFlags & STATUS_PENDING) !== 0 ||
Expand Down
33 changes: 19 additions & 14 deletions packages/signals/src/boundaries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ import {
setPassLane,
txOf,
unhold,
type Transaction
type Transaction,
installBoundaryHooks,
laneStageHook
} from "./core/scheduler.js";
import { attrHooks } from "./core/attribution-hooks.js";
import type { Computed, Owner, Signal } from "./core/types.js";
Expand Down Expand Up @@ -203,8 +205,7 @@ function flip(b: Boundary): void {
b._lane = b._armLane;
const o = b._output!;
if (!b._lane && o._config & CONFIG_HELD && txOf(o) !== flushTransaction) unhold(o);
if (b._lane !== null && b._output !== null)
GlobalQueue._laneStage!(b._output, b._lane, true, false);
if (b._lane !== null && b._output !== null) laneStageHook!(b._output, b._lane, true, false);
}

export function redraw(b: Boundary): void {
Expand All @@ -214,7 +215,7 @@ export function redraw(b: Boundary): void {
}
}

/** GlobalQueue._catch: status from a frame reader, nearest boundary first.
/** `catchHook` (scheduler.ts): status from a frame reader, nearest boundary first.
* A loading boundary on the way records a pending reader whether or not it
* catches it (its `on` may collect it later). */
function catchStatus(
Expand Down Expand Up @@ -261,14 +262,14 @@ function armedOver(node: Computed<any>): boolean | undefined {
}

/** The nearest boundary on `r`'s chain showing its fallback, if any
* (GlobalQueue._hidden: `r` is behind a fallback). */
* (`hiddenHook`: `r` is behind a fallback). */
function hiddenBy(r: Computed<any>): Boundary | undefined {
for (let b: Boundary | null | undefined = boundaryOf(r); b; b = b._parent)
if (b._fallback) return b;
return undefined;
}

/** GlobalQueue._heldRun: a queued run behind a fallback waits for the reveal
/** `heldRunHook` (scheduler.ts): a queued run behind a fallback waits for the reveal
* (maintainer, 2026-10-02: the queue is held, the synchronous first render
* goes through). A user effect's, which would read a DOM that is not
* attached ("many depend on reading the DOM, something that can't happen
Expand Down Expand Up @@ -352,7 +353,7 @@ export function ready(b: Boundary): boolean {
);
}

/** GlobalQueue._boundarySeam, end of the seam: readers that settled, died,
/** `boundarySeamHook` (scheduler.ts), end of the seam: readers that settled, died,
* or landed and committed are dropped; a fallback with none left reveals
* next round. An arm resolves: nothing collected, nothing happened. */
function boundarySeam(): void {
Expand Down Expand Up @@ -848,10 +849,14 @@ export function createErrorBoundary<T, U>(

// Installed at module evaluation — present exactly when something imports a
// boundary. An app without one pays the three null checks and nothing else.
GlobalQueue._catch = catchStatus;
GlobalQueue._fresh = (node, held) =>
!held &&
(node._statusFlags & STATUS_UNINITIALIZED ? catchStatus(node, STATUS_PENDING) : armedOver(node));
GlobalQueue._hidden = hiddenBy;
GlobalQueue._boundarySeam = boundarySeam;
GlobalQueue._heldRun = heldRun;
installBoundaryHooks(
catchStatus,
(node, held) =>
!held &&
(node._statusFlags & STATUS_UNINITIALIZED
? catchStatus(node, STATUS_PENDING)
: armedOver(node)),
hiddenBy,
boundarySeam,
heldRun
);
9 changes: 5 additions & 4 deletions packages/signals/src/core/async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ import {
flush,
flushTransaction,
globalQueue,
GlobalQueue,
joinFuture,
MAINLINE_QUESTION,
passLane,
question,
queuePendingNode,
schedule,
txOf
txOf,
freshHook,
laneStageHook
} from "./scheduler.js";
import type { Computed, Link } from "./types.js";

Expand Down Expand Up @@ -955,7 +956,7 @@ export function propagateStatus(
passLane === null &&
(sub._config & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD &&
globalQueue._running &&
!GlobalQueue._fresh?.(sub, pendingSource._time === clock)
!freshHook?.(sub, pendingSource._time === clock)
)
joinFuture(txOf(sub));
return;
Expand All @@ -970,7 +971,7 @@ export function propagateStatus(
// the dependent's pending is the lane's own flight (`laneStage`).
if (!downstreamBlockStatus) {
// (No answer: a pending propagation leaves a written guess a guess.)
if (passLane !== null) GlobalQueue._laneStage!(sub, passLane, true, el);
if (laneStageHook && passLane !== null) laneStageHook(sub, passLane, true, el);
else {
queuePendingNode(sub);
// A15 (#3443): pending propagates onto a held memo without
Expand Down
2 changes: 1 addition & 1 deletion packages/signals/src/core/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ export const STATUS_PENDING = 1 << 0;
export const STATUS_ERROR = 1 << 1;
export const STATUS_UNINITIALIZED = 1 << 2;

/** `GlobalQueue._catch`'s answer when the catcher is a boundary re-armed by
/** `catchHook`'s answer when the catcher is a boundary re-armed by
* `on` (truthy, as any catch): what it collects is its own, not the hold's. */
export const CAUGHT_REARMED = 2;

Expand Down
Loading
Loading