Repository navigation
fix: treat arrow bodies and parameter defaults as declarations - #1568
Conversation
The elision analyser read the depth-0 text of a module and counted any `ident(` there as a call that runs at module load. An expression-bodied arrow (`export const usd = (m) => Math.round(m) / MICROS`) and a call in a function parameter default (`function relativeTime(when, now = Date.now())`) both left such text behind without running anything, so every page that imported a formatting helper shipped to the browser with its whole import graph. The frame now drops a function's parameter list and the expression body of an arrow that initializes a declarator before the call scan. The arrow body ends early whenever continuation is not provable, so a real call after it is still seen. Closes #1567 Claude-Session: https://claude.ai/code/session_01SZ72LSPAo4NvYvBDSD6RLo
|
Preview for Next: fix the listed keys in the compose file |
The #780 under-fetch guard made its page ship through an arrow helper whose body the analyser used to read as a load-time call. That helper is now correctly a declaration, so the page needs its own top-level call to stay a boot module. Also dates the counterfactual claim in the new declaration-shapes test. Claude-Session: https://claude.ai/code/session_01SZ72LSPAo4NvYvBDSD6RLo
|
Design rationale: blank the frame, end arrow bodies early I kept this inside the existing lexical scan instead of reaching for a parser. The analyser already works on a redacted, brace-depth-0 frame, so the fix is just removing two more kinds of text from that frame before the call scan runs. The one hard part is knowing where an expression-bodied arrow ends without a parser, because of ASI. The rule leans one way on purpose. Ending too early leaves the rest of the body in the frame, where it still gets scanned and at worst ships the module, which is the old behaviour. Ending too late could swallow a real statement on the next line, which would elide a module that does client work. So a newline ends the body unless continuation is certain: the line ends in a binary operator, Since the frame drops |
Ships the elision analyser fix (#1568): an expression-bodied arrow and a function parameter default are declarations, not load-time calls, so a page importing helpers written that way no longer ships to the browser. Claude-Session: https://claude.ai/code/session_01SZ72LSPAo4NvYvBDSD6RLo
Closes #1567
The elision analyser no longer reads an arrow's expression body or a function parameter default as code that runs at module load.
export const usd = (m) => Math.round(m) / MICROSandexport function relativeTime(when, now = Date.now())are declarations, so a page importing a helper module built from them stays elidable instead of shipping with its whole import graph.What changed
hasModuleScopeSideEffect(packages/server/src/component-elision.js) now runs the depth-0 frame throughblankDeclarationOnlyCodebefore its scans. That drops the parameter list of everyfunctionand the expression body of an arrow that initializes aconst/let/vardeclarator.,/;at its own depth 0, at a)/]closing something outside it, or at a newline once it has content, unless a trailing binary operator or a leading?/:/./&&/||proves the expression carries on. Ending early is always safe, since the rest stays in the frame and is still scanned.{...}block now leaves a marker in the frame while the blanking runs, soconst f = (v) => {...}followed byfoo()on the next line cannot be swallowed as an expression body. The marker is removed before the existing scans, so they see the same frame as before.Deliberately left out: an arrow whose own parameter default calls something (
(a = init()) =>) and an arrow outside a declarator (cond ? (a) => f(a) : null) still ship. Both stay conservative.Test plan
packages/server/test/elision/declaration-shapes.test.js: 13 declaration shapes return false, 16 real top-level call shapes (bare call, declarator call,LIST.filter(...), arrow and function IIFEs,(() => 1)(),const g = (a) => a, h = init(), a call after a block-bodied arrow, afterv++, after a regex literal, ...) return true, plus ananalyzeElisioncase where a page importing the formatting helpers is inert.packages/ui/test/utils-purity.test.jsstill green.test/ssr/dropped-page-app-preload.test.js: the App-module modulepreload over-fetches a dropped page's SSR-only subtree #780 under-fetch guard made its page ship through an arrow helper the analyser used to misread, so the page now ships through its own top-levelDate.now()call. That was the only test depending on the old reading.npm test: 4953 pass. The 7 failures (differential elision on vs off, the scaffold server-export coverage entry forrobots, the Bun listener test, a type fixture) fail identically with the pre-fix analyser in this worktree.webjs checkpasses in website.analyzeElisionunit test.Docs
.agents/skills/webjs/references/components.md(Display-only elision blockers)website/app/docs/elision/page.ts(same list on the docs site)packages/server/AGENTS.md(analyser notes)https://claude.ai/code/session_01SZ72LSPAo4NvYvBDSD6RLo