From a29547578df3d1693060d630084e1ee3941db24a Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Tue, 6 Oct 2026 08:57:38 -0700 Subject: [PATCH 1/2] chore(size): add page base + router and page live + router scenarios MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two server-component page floors carry no router, so the "base case" they gate is a page nobody deploys. Two scenarios add @solidjs/router 2.0 (2.0.0-next.35 — the Solid 2 line, peers solid-js / @solidjs/web ^2.0.0-rc.13 — pinned exactly as a devDependency of scripts/size, resolved from its node_modules; .npmrc's legacy-peer-deps keeps the peers out of the lockfile since the page alias routes them to the dists): createRouter with two routes (one preload, one lazy), the instance as the hydrated root and useNavigate in a route component, so the router's runtime — not just its imports — is retained. Measured locally on next @ 49a8dca84: base + router 57,001 B br (184,378 minified) against the base page's 44,864 (145,599), the router's contribution +12,137 br; live + router 58,243 (188,800) against 48,527 (157,562), +9,716 br. The router standalone is 32,430 / 11,216 B. Inline caps at local measured + 10 B rounded up to 0.01 KB (57.02 / 58.26 KB), recorded minified from the same measurement; to be confirmed against CI. Co-authored-by: Claude via Cursor --- scripts/size/.npmrc | 4 +++ scripts/size/README.md | 16 +++++++++- scripts/size/package-lock.json | 18 +++++++++++ scripts/size/package.json | 1 + scripts/size/sc-live-router-app.js | 41 +++++++++++++++++++++++++ scripts/size/sc-router-app.js | 48 ++++++++++++++++++++++++++++++ scripts/size/scenarios.js | 47 +++++++++++++++++++++++++++++ 7 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 scripts/size/.npmrc create mode 100644 scripts/size/sc-live-router-app.js create mode 100644 scripts/size/sc-router-app.js diff --git a/scripts/size/.npmrc b/scripts/size/.npmrc new file mode 100644 index 000000000..99c103d40 --- /dev/null +++ b/scripts/size/.npmrc @@ -0,0 +1,4 @@ +# @solidjs/router declares solid-js / @solidjs/web as peers; the scenarios +# alias both to the built dists, so the peers must not be installed here +# (they would pull a published solid-js into this lockfile for nothing). +legacy-peer-deps=true diff --git a/scripts/size/README.md b/scripts/size/README.md index b39e216a3..bb88cb908 100644 --- a/scripts/size/README.md +++ b/scripts/size/README.md @@ -6,7 +6,8 @@ app, a representative CSR app, a hydrating pair — with and without store primitives — that keeps the store engine pay-for-use under `hydrate()`, three compiled-template scenarios — a compiled floor and a JSX todo app in CSR and hydrating form — the frames client as a package, two server-component PAGES: -base and live, and two server-entry floors: `getRequestEvent`/`isServer` and +base and live, the same two pages under `@solidjs/router`, and two +server-entry floors: `getRequestEvent`/`isServer` and `renderToString`) with hard brotli limits on the eager entry chunk. CI fails when a scenario exceeds its limit and grew more than a small minified allowance over its base (see [The gate](#the-gate)) — that means tree-shaking regressed, or a deliberate @@ -115,6 +116,19 @@ switch re-based every cap (Rolldown lands 4–9% lower on the same artifacts; the table is in the switch PR and each scenario's ledger note in `scenarios.js`). Ledger deltas must not be read across that line. +## The router scenarios + +`page: base + router` and `page: live + router` are the two page scenarios +plus `@solidjs/router` 2.0 — the Solid 2 line (`2.0.0-next.*`), a +devDependency of this directory pinned exactly like Rolldown (a router +upgrade is a re-base, recorded like any other cap change). It resolves from +this directory's `node_modules`; its `solid-js` / `@solidjs/web` imports go +through the page alias to the built dists like everything else, and +`.npmrc`'s `legacy-peer-deps` keeps npm from installing those peers from the +registry for nothing. The router's own modules report as the `other` +package. These are not floors: their caps are inline, and the two page +floors stay the gate on the runtime alone. + ## Compiled scenarios The hand-written `app:` fixtures call the runtime directly and never compile diff --git a/scripts/size/package-lock.json b/scripts/size/package-lock.json index 0d462dc9b..b4d36befb 100644 --- a/scripts/size/package-lock.json +++ b/scripts/size/package-lock.json @@ -6,6 +6,7 @@ "": { "name": "@solidjs/size-scenarios", "devDependencies": { + "@solidjs/router": "2.0.0-next.35", "rolldown": "1.2.11" } }, @@ -299,6 +300,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@solidjs/router": { + "version": "2.0.0-next.35", + "resolved": "https://registry.npmjs.org/@solidjs/router/-/router-2.0.0-next.35.tgz", + "integrity": "sha512-6+frAz9M2vrLSz+2hr51++nzGyxt3+nCo08dY3PkdieSOwDutAXdfSXj681FqTOvffv8kPDF5tdRqPUoLbKhlQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@solidjs/web": "^2.0.0-rc.13", + "filesystem-routing": ">=0.4.0", + "solid-js": "^2.0.0-rc.13" + }, + "peerDependenciesMeta": { + "filesystem-routing": { + "optional": true + } + } + }, "node_modules/rolldown": { "version": "1.2.11", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.11.tgz", diff --git a/scripts/size/package.json b/scripts/size/package.json index 6997aba6a..c268bf45d 100644 --- a/scripts/size/package.json +++ b/scripts/size/package.json @@ -12,6 +12,7 @@ "build": "cd ../.. && pnpm install --frozen-lockfile && pnpm build" }, "devDependencies": { + "@solidjs/router": "2.0.0-next.35", "rolldown": "1.2.11" } } diff --git a/scripts/size/sc-live-router-app.js b/scripts/size/sc-live-router-app.js new file mode 100644 index 000000000..edc750106 --- /dev/null +++ b/scripts/size/sc-live-router-app.js @@ -0,0 +1,41 @@ +// The live server-component page under a router: sc-live-app plus the same +// `@solidjs/router` 2.0 composition as sc-router-app (createRouter with two +// routes — one `preload`, one lazy — the instance as the hydrated root, +// `useNavigate` in a route component). See sc-router-app.js for why the +// router's runtime, not just its imports, is retained. +import { hydrate, Show, For, Loading, Errored, dynamic, createComponent } from "@solidjs/web"; +import { createSignal, createMemo, action, isPending, latest, lazy } from "solid-js"; +import { installServerComponents } from "@solidjs/web/frames"; +import { createServerReference, live, GET } from "@solidjs/web/server-functions/client"; +import { createRouter, useNavigate } from "@solidjs/router"; + +installServerComponents(); +const getStory = live(GET(createServerReference("story", "getStory"))); +const Story = dynamic(() => getStory()); +const send = action(async () => {}); +const [n, setN] = createSignal(0); +const Page = lazy(() => import("./lazy-page.js")); +function Home(props) { + const navigate = useNavigate(); + return [props.children, () => navigate("/story/1")]; +} +const Router = createRouter({ + routes: [ + { path: "/", component: Home, preload: ({ params, intent }) => void (params, intent) }, + { path: "/story/:id", component: Page } + ] +}); +hydrate(() => { + const d = createMemo(() => n() + (isPending(n) ? 1 : 0) + latest(n)); + setN(1); + return [ + d(), + Show, + For, + Loading, + Errored, + Story, + send, + createComponent(Router, { children: p => p.children }) + ]; +}, document.body); diff --git a/scripts/size/sc-router-app.js b/scripts/size/sc-router-app.js new file mode 100644 index 000000000..f1dad0302 --- /dev/null +++ b/scripts/size/sc-router-app.js @@ -0,0 +1,48 @@ +// The base server-component page under a router: sc-base-app plus what an +// application page ships to route — `@solidjs/router` 2.0 (the Solid 2 line, +// `2.0.0-next.*`, installed by this directory's own package.json; its +// `solid-js` / `@solidjs/web` imports route to the dists through pageAlias +// like every other import here). `createRouter` with two routes — one with a +// `preload`, one whose component is the page's `lazy()` chunk — rendered as +// the hydrated root through the instance with a render-prop layout, and +// `useNavigate` read in a route component. 2.0 has no ``: anchors are +// plain ``, claimed by the router through `registerElementClaim` and +// intercepted by delegation, so the link runtime is the router's own +// (`setupLinkClaims`, `setupNativeEvents`) and is retained by the instance. +// The instance, its routes and the route components are all reachable from +// the root, so the router's runtime — matching, the integration, the +// context, preloading, link claims — is retained, not just its imports. +import { hydrate, Show, For, Loading, Errored, dynamic, createComponent } from "@solidjs/web"; +import { createSignal, createMemo, lazy } from "solid-js"; +import { installServerComponents } from "@solidjs/web/frames"; +import { createServerReference } from "@solidjs/web/server-functions/client"; +import { createRouter, useNavigate } from "@solidjs/router"; + +installServerComponents(); +const getStory = createServerReference("story", "getStory"); +const Story = dynamic(() => getStory()); +const [n, setN] = createSignal(0); +const Page = lazy(() => import("./lazy-page.js")); +function Home(props) { + const navigate = useNavigate(); + return [props.children, () => navigate("/story/1")]; +} +const Router = createRouter({ + routes: [ + { path: "/", component: Home, preload: ({ params, intent }) => void (params, intent) }, + { path: "/story/:id", component: Page } + ] +}); +hydrate(() => { + const d = createMemo(() => n() + 1); + setN(1); + return [ + d(), + Show, + For, + Loading, + Errored, + Story, + createComponent(Router, { children: p => p.children }) + ]; +}, document.body); diff --git a/scripts/size/scenarios.js b/scripts/size/scenarios.js index f5e503529..149243036 100644 --- a/scripts/size/scenarios.js +++ b/scripts/size/scenarios.js @@ -3625,6 +3625,53 @@ module.exports = [ floorMinified["page: live server components (base + live/GET + action + isPending/latest)"], alias: pageAlias }, + { + name: "page: base + router (base page + @solidjs/router: createRouter, two routes, preload, useNavigate)", + // The base page as an application ships it: with a router. The two page + // floors above carry no router, so the "base case" they gate is a page + // nobody deploys; this scenario is the same composition plus + // `@solidjs/router` 2.0 (`2.0.0-next.35`, pinned exactly in this + // directory's package.json — the Solid 2 line; a router upgrade is a + // re-base recorded like a Rolldown upgrade) with `createRouter`, two + // routes (one `preload`, one lazy), the instance as the hydrated root + // and `useNavigate` in a route component. Not a floor: an inline cap, + // and the router's growth is the router's — this scenario attributes it + // (the `other` package in the report is the router). + // Landing (2026-10-06, next @ 49a8dca84): measured locally at 57,001 B + // (184,378 B minified) against the base page's 44,864 (145,599) — the + // router's contribution +12,137 B br / +38,779 B minified: the router's + // own 28,008 minified (`createRouterContext`, `setupNativeEvents` incl. + // the form-action path, `query`'s preload cache, `setupLinkClaims`, + // scroll restoration, `browserHistory`) plus what it retains of ours — + // signals +8,583 (`action` and the optimistic lanes: `verdictValue`, + // `dissolveLane`, `laneRead`, `isPending`/`latest`, `createEffect`, + // `onSettled`), sf +1,188 (`subscribeFlightData`, `decodeResponsePayload`, + // the redirect header, `parseServerFunctionAddress`), web +1,137 + // (`takeHydrationValue`, `registerElementClaim`), solid +364 + // (`createContext`, `children`). The router standalone, with solid-js / + // @solidjs/web external, is 32,430 / 11,216 B. Cap at local measured + + // 10 B rounded up to 0.01 KB; to be confirmed against CI's measurement + // (Node 24) + 10 B. + path: "sc-router-app.js", + limit: "57.02 KB", + capMinified: 184378, + alias: pageAlias + }, + { + name: "page: live + router (live page + @solidjs/router: createRouter, two routes, preload, useNavigate)", + // The live page with the same router (see the base + router note). + // Landing (2026-10-06, next @ 49a8dca84): measured locally at 58,243 B + // (188,800 B minified) against the live page's 48,527 (157,562) — the + // router's contribution +9,716 B br / +31,238 B minified, less than on + // the base page because the live page already carries `action` and + // `isPending`/`latest`, which the router retains on every page. Cap at + // local measured + 10 B rounded up to 0.01 KB; to be confirmed against + // CI's measurement (Node 24) + 10 B. + path: "sc-live-router-app.js", + limit: "58.26 KB", + capMinified: 188800, + alias: pageAlias + }, { name: "server: floor (getRequestEvent + isServer)", // What a server module that only asks "am I on the server / which From d1e3d203939b6ef6dd6947afaf6acd0106488365 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Wed, 7 Oct 2026 12:36:15 -0700 Subject: [PATCH 2/2] size: router pages mount through dynamicComponent; caps re-based on next (#3838) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two router scenarios are the base / live server-component pages plus @solidjs/router; those pages mount through dynamicComponent since #3870 (the documented mount), so the router fixtures do too — with dynamic they measured +2,332 / +2,278 B brotli more (the element runtime dynamic's tag arm keeps). Re-based onto next @ 7233451ee (the frames tiers): page base + router 57.02 -> 45.95 KB (45,940 B; 144,082 B min), page live + router 58.26 -> 47.21 KB (47,197 B; 148,526 B min), measured + 10 B rounded up to 0.01 KB. Co-authored-by: Cursor --- scripts/size/sc-live-router-app.js | 12 +++++++++-- scripts/size/sc-router-app.js | 12 +++++++++-- scripts/size/scenarios.js | 34 +++++++++++++++++++++--------- 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/scripts/size/sc-live-router-app.js b/scripts/size/sc-live-router-app.js index edc750106..4b6178ba9 100644 --- a/scripts/size/sc-live-router-app.js +++ b/scripts/size/sc-live-router-app.js @@ -3,7 +3,15 @@ // routes — one `preload`, one lazy — the instance as the hydrated root, // `useNavigate` in a route component). See sc-router-app.js for why the // router's runtime, not just its imports, is retained. -import { hydrate, Show, For, Loading, Errored, dynamic, createComponent } from "@solidjs/web"; +import { + hydrate, + Show, + For, + Loading, + Errored, + dynamicComponent, + createComponent +} from "@solidjs/web"; import { createSignal, createMemo, action, isPending, latest, lazy } from "solid-js"; import { installServerComponents } from "@solidjs/web/frames"; import { createServerReference, live, GET } from "@solidjs/web/server-functions/client"; @@ -11,7 +19,7 @@ import { createRouter, useNavigate } from "@solidjs/router"; installServerComponents(); const getStory = live(GET(createServerReference("story", "getStory"))); -const Story = dynamic(() => getStory()); +const Story = dynamicComponent(() => getStory()); const send = action(async () => {}); const [n, setN] = createSignal(0); const Page = lazy(() => import("./lazy-page.js")); diff --git a/scripts/size/sc-router-app.js b/scripts/size/sc-router-app.js index f1dad0302..c03176757 100644 --- a/scripts/size/sc-router-app.js +++ b/scripts/size/sc-router-app.js @@ -12,7 +12,15 @@ // The instance, its routes and the route components are all reachable from // the root, so the router's runtime — matching, the integration, the // context, preloading, link claims — is retained, not just its imports. -import { hydrate, Show, For, Loading, Errored, dynamic, createComponent } from "@solidjs/web"; +import { + hydrate, + Show, + For, + Loading, + Errored, + dynamicComponent, + createComponent +} from "@solidjs/web"; import { createSignal, createMemo, lazy } from "solid-js"; import { installServerComponents } from "@solidjs/web/frames"; import { createServerReference } from "@solidjs/web/server-functions/client"; @@ -20,7 +28,7 @@ import { createRouter, useNavigate } from "@solidjs/router"; installServerComponents(); const getStory = createServerReference("story", "getStory"); -const Story = dynamic(() => getStory()); +const Story = dynamicComponent(() => getStory()); const [n, setN] = createSignal(0); const Page = lazy(() => import("./lazy-page.js")); function Home(props) { diff --git a/scripts/size/scenarios.js b/scripts/size/scenarios.js index 547f7cba7..139fc4cb7 100644 --- a/scripts/size/scenarios.js +++ b/scripts/size/scenarios.js @@ -4320,12 +4320,21 @@ module.exports = [ // the redirect header, `parseServerFunctionAddress`), web +1,137 // (`takeHydrationValue`, `registerElementClaim`), solid +364 // (`createContext`, `children`). The router standalone, with solid-js / - // @solidjs/web external, is 32,430 / 11,216 B. Cap at local measured + - // 10 B rounded up to 0.01 KB; to be confirmed against CI's measurement - // (Node 24) + 10 B. + // @solidjs/web external, is 32,430 / 11,216 B. + // Re-based onto `next` @ 7233451ee (#3838, 2026-10-07), after the frames + // tiers (#3860): measured at 45,940 B (144,082 B minified) against the + // base page's 33,908 (105,252) — the router's contribution +12,032 B br + // / +38,830 B minified, the same as at landing; the page under it is + // what #3860 cut. The fixture now mounts through `dynamicComponent`, the + // documented server-component mount the base page uses (#3870); with + // `dynamic` it measures 48,272 / 151,551 (+2,332 / +7,469: `dynamic`'s + // tag arm keeps the element runtime). The router does not retain that + // runtime itself — its `serverRouteComponent` helper mounts with + // `dynamic`, but this page does not use it. Cap at measured + 10 B + // rounded up to 0.01 KB; recorded minified 144,082 B (CI-confirmed). path: "sc-router-app.js", - limit: "57.02 KB", - capMinified: 184378, + limit: "45.95 KB", + capMinified: 144082, alias: pageAlias }, { @@ -4335,12 +4344,17 @@ module.exports = [ // (188,800 B minified) against the live page's 48,527 (157,562) — the // router's contribution +9,716 B br / +31,238 B minified, less than on // the base page because the live page already carries `action` and - // `isPending`/`latest`, which the router retains on every page. Cap at - // local measured + 10 B rounded up to 0.01 KB; to be confirmed against - // CI's measurement (Node 24) + 10 B. + // `isPending`/`latest`, which the router retains on every page. + // Re-based onto `next` @ 7233451ee (#3838, 2026-10-07), after the frames + // tiers (#3860): measured at 47,197 B (148,526 B minified) against the + // live page's 37,512 (117,301) — the router's contribution +9,685 B br / + // +31,225 B minified. Mounted through `dynamicComponent` as the live page + // is (the base + router note); with `dynamic`: 49,475 / 155,995. Cap at + // measured + 10 B rounded up to 0.01 KB; recorded minified 148,526 B + // (CI-confirmed). path: "sc-live-router-app.js", - limit: "58.26 KB", - capMinified: 188800, + limit: "47.21 KB", + capMinified: 148526, alias: pageAlias }, {