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 453d329ee..432f3b418 100644 --- a/scripts/size/README.md +++ b/scripts/size/README.md @@ -7,8 +7,8 @@ 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, their two compiled counterparts — the same pages as JSX with -the templates a real page has — and two server-entry floors: -`getRequestEvent`/`isServer` and +the templates a real page has — the same two hand-written 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 @@ -123,6 +123,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..4b6178ba9 --- /dev/null +++ b/scripts/size/sc-live-router-app.js @@ -0,0 +1,49 @@ +// 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, + 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"; +import { createRouter, useNavigate } from "@solidjs/router"; + +installServerComponents(); +const getStory = live(GET(createServerReference("story", "getStory"))); +const Story = dynamicComponent(() => 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..c03176757 --- /dev/null +++ b/scripts/size/sc-router-app.js @@ -0,0 +1,56 @@ +// 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, + 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"; +import { createRouter, useNavigate } from "@solidjs/router"; + +installServerComponents(); +const getStory = createServerReference("story", "getStory"); +const Story = dynamicComponent(() => 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 a47a3600e..05da999b3 100644 --- a/scripts/size/scenarios.js +++ b/scripts/size/scenarios.js @@ -4305,6 +4305,67 @@ module.exports = [ capMinified: 122779, 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. + // 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: "45.95 KB", + capMinified: 144082, + 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. + // 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: "47.21 KB", + capMinified: 148526, + alias: pageAlias + }, { name: "server: floor (getRequestEvent + isServer)", // What a server module that only asks "am I on the server / which