Repository navigation
Conversation
…d of exiting the process A disallowed character inside square brackets of a route path called process.exit(1), which killed a running dev server. Throw an error instead, so the router generator plugin reports it: a dev server logs it and keeps running, and a build (Vite, Rspack or webpack) now fails on any route generation error instead of bundling a stale route tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ype arguments
The route factory code filter required `createFileRoute(` and
`createRootRoute(`, so a route file calling `createFileRoute<'/a'>('/a')`
was never code-split nor hot-updated. Accept type arguments for every route
factory, and let route files that rename a route factory import through, so
the plugins can report them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Report through the bundler (`this.warn`, once per message) instead of silently skipping: - a renamed (`createFileRoute as cfr`) or namespaced route factory, which is neither split nor hot-updated (code splitter and route HMR plugin); - a split option written as a method, getter or setter, or behind a computed key, and split options or codeSplitGroupings spread from another object (only options the groupings would split); - an exported split option: the warning moves from console.warn on every transform to the bundler, and now also fires in a production build where the exported option is the only split one. The dev-only console.warn injected into the route module stays, for the browser console. Fail the compilation with a clear error for: - a route file that calls createFileRoute more than once, naming every call (it failed with "Duplicated export" in the split chunks); - codeSplitGroupings behind a computed key, which were ignored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion leaves alone A Start factory call the compiler cannot rewrite keeps its implementation, and the server-only code it reads, in the client bundle without a word: a builder returned by a function, `(0, createServerFn)()`, `Start['createServerFn']()`, an aliased createServerOnlyFn in a function, a builder held in an object property or a ternary, the factory passed to user code. In the client compilation, every reference to a module-scope import of a Start factory (or a member of a namespace import of a Start package), resolved through Yuku references, must now be part of a call chain the compiler rewrites, initialize a module-level builder variable (unfinished by handler/server/client), be re-exported, or initialize a module-level alias whose references follow the same rule. References inside code the client output drops, such as a server fn handler, are not checked. Anything else is reported with its location: a warning in dev, once per message, and an error that fails a build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s in a function Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…o a chunk component Split <Hydrate> children move into the chunk's own component, so code that reads the state of the rendering component's call changed meaning or broke the chunk silently. Fail the compilation with a clear error for: - await, for await and yield in the children, outside nested functions (the chunk did not parse); - arguments of the component in the children, up to the first non-arrow function (the chunk read its own props); - hooks recognized by their import rather than by a use* name only: a renamed hook (useState as state), React 19 use(), and hooks read from a namespace or default import (React.useId()), of react, solid-js or vue. In dev, warn once when a boundary receives its children from a spread, which leaves them unsplit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… browser in development The compiler rewrites every Start factory call of the client bundle, so a call that reaches the runtime uncompiled is one it missed, cross-module ones included, and its server code shipped to the client. In development, in the browser, report it with console.error, naming the factory and how to write it so it compiles: - createServerFn().handler() called without the RPC the compiler passes; - createServerOnlyFn() and createClientOnlyFn() called at all; - createIsomorphicFn().server() called at all (a bare createIsomorphicFn() builder stays in compiled code, so it is not reported). The checks are gated by direct process.env.NODE_ENV checks, so production bundles do not change, and skip NODE_ENV=test, where code runs uncompiled by design. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ass the factory The bundle-size benchmark apps keep the Start API surface alive with `[createMiddleware, createServerFn]`, which failed their build. A value use that neither calls the factory nor hands it to a function carries no implementation, so it is left alone. A sequence, conditional or logical expression around the factory still counts as a use, but never as a module-level builder or alias. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…compiled server fn Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: a618e3b The changes in this PR will be included in the next version bump. This PR includes changesets to release 19 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
View your CI Pipeline Execution ↗ for commit a618e3b
☁️ Nx Cloud last updated this comment at |
🚀 Changeset Version Preview14 package(s) bumped directly, 12 bumped as dependents. 🟨 Minor bumps
🟩 Patch bumps
|
Bundle Size BenchmarksThis pull request does not affect bundle size in any measured scenario. |
Merging this PR will not alter performance
Comparing Footnotes
|
Prototype, stacked on #8504 (Babel → Yuku compiler migration). It captures diagnostics for compiler patterns that are already silent on
main, so they are not forgotten. Patterns that became silent in the migration itself are handled in #8504 (for example thethis.warnfor a route created inside a function, which this PR reuses).Policy agreed with the maintainer:
{ dev, build }behaviour, dedup with a seen set, bundler warning channel).Breadth over polish: each item has behaviour-level tests in its topic suite, but the messages, option surface and exact precision are open for discussion (see the open questions).
Items
main/ #8504createServerFn().handler()without the compiled RPC,createServerOnlyFn(),createClientOnlyFn(),createIsomorphicFn().server()reaching the runtime stub in the browserconsole.errornaming the factory, that it was not compiled, and how to write it (module-level variable, factory imported directly from the Start package)NODE_ENV !== 'production' && !== 'test',!(isServer ?? typeof window === 'undefined')) / nothing in productionexport *diamonds/cycles, builder finished in another module, namespace-read builder (F.authed.handler), re-exported Start namespace,authed()from another moduleconst authed = () => createServerFn(),(0, createServerFn)(),Start['createServerFn'](), aliasedcreateServerOnlyFninside a function, builder in an object property or a ternary, factory passed to user codeUnrecognized use of createX at file:line:col: it is not compiled, so its implementation ships to the client. …this.warnonce per message / build error.bind)"createFileRoutecalls in one route filemain: renders the wrong component; #8504: build fails with "Duplicated export 'component'""file" calls createFileRoute 2 times:+file:line:colof each callawait/for await/yieldin split<Hydrate>children (outside nested functions)argumentsof the component in split children (stops at non-arrow functions)useState as st, React 19use(),React.useId()(namespace or default import;react,solid-js,vue), plus the existinguse[A-Z0-9]name rule for custom hooks<Hydrate {...props} />: children from a spread, left unsplitconsole.warnon every transform; missing in a production build when the exported option is the only split onethis.warn, once per message, also in buildscomponent() {})['codeSplitGroupings']computed keycodeSplitGroupingsfrom a spread resolving to an object literal; split option behind a computed key['component']createFileRoute<'/a'>('/a')(type arguments)\s*(?:<|\()for every route factoryimport { createFileRoute as cfr },Router.createFileRoute(...)in a route fileconsole.error+process.exit(1): kills the dev servercommand === 'build', Rspack/webpackbeforeRun) instead of bundling a stale treePin status was checked by running the
known-bugs-start-compilerandknown-bugs-hydratefiles of #8630 against this branch and against #8504: exactly the four pins named "flips" above change from expected-fail to passing. The router-plugin and router-generator pins were not affected by design.Notes per item
createIsomorphicFn()alone is not reported, since compiled code keeps a bare builder (export const iso = createIsomorphicFn()); only.server()reaching the stub is.start-fn-stubsusestypeof window !== 'undefined'withoutisServer, to avoid adding a@tanstack/router-coredependency to it; theNODE_ENVgate already removes the block from every production bundle, andisServerisundefinedin dev, so it would not change any outcome.start-client-coreusesisServer ?? typeof window === 'undefined'as AGENTS.md says.NODE_ENV === 'test'is excluded explicitly because unit tests (Vitest with jsdom) run uncompiled by design.module.imports,binding.references,parentOf), not names. A reference is accepted when it is on a call chain the compiler rewrote, initializes a module-level variable with an unfinished builder (no.handler/.server/.client/.createMiddlewaresin the chain,createServerFn/createMiddleware/createIsomorphicFnonly), or initializes a module-level alias (const cs = createServerFn) whose references follow the same rule. Of the rest, only uses that call the factory (through any call/member chain, sequence, conditional or logical expression) or pass it to a function are reported; other value uses (an array element, an object value, a comparison, a re-export, a type) carry no implementation and are left alone. That last rule came from the bundle-size benchmark appreact-start.full, whose[createMiddleware, createServerFn, useServerFn]failed its build with the first version. References inside code the client output drops (server fn handler, middleware/isomorphic.server()argument,createServerOnlyFnargument) are skipped. Only the client compilation checks, and virtual modules (?tss-…) are skipped so a Hydrate chunk does not report twice.console.warnin the route module stays: it reaches the browser console, where the terminal warning is often not seen. Note that Vite 8 relays browser console output to the terminal, so in dev the terminal now shows the bundler warning once per environment (client and SSR) plus a relayed[console.warn]per page load; dropping the injected warning is an option if that is too noisy. Dedup is per message (which names the file and exports), so an edit that changes the exported set reports again.loader() {}with the default groupings is not reported).False positives / precision
makeBuilder(createServerFn)): reported. The compiler cannot follow it either way, so the implementation does ship, but a user may consider it intentional.const authed = () => createServerFn()). Reporting only what survives tree-shaking would need the import-protection deferral machinery.const factories = { cs: createServerFn }; factories.cs().handler(...)leaks without a compile-time report (item 1 catches it at runtime in dev). This is the price of not failing on the benchmark-style[createServerFn]surface arrays.const S = Start; S.createServerFn()), an alias declared with destructuring, and cross-module patterns (a builder in another module) are not reported at compile time; item 1 catches the cross-module ones at runtime in dev.console.errorper server fn (the statement is true, the server code is in that bundle, but it is harmless there). Vitest is excluded viaNODE_ENV=test; Vitest browser mode with anotherNODE_ENVis not.use[A-Z0-9]…) is kept for custom hooks, so a non-hook function nameduseThingis still rejected, as before.children.Open questions for the maintainer
errormode) rather than a terminal warning, given it is a server-code leak?compilerDiagnostics: { dev: 'warn' | 'error' | 'off', build: … }, at least to silence item 2 for libraries / intentional patterns?console.error) forcreateServerOnlyFn, so the leaked server code does not run in the browser in dev? And is excludingNODE_ENV=testacceptable given AGENTS.md's!== 'production'rule?Validation
test:unit(Nx): router-plugin 973, router-utils 84, router-generator 270 + 12, start-plugin-core 925, start-client-core 125, start-fn-stubs 18, react-start-rsc 67 (affected consumer): all pass.test:types,test:eslint,test:buildfor router-plugin, router-utils, router-generator, start-plugin-core, start-client-core, start-fn-stubs: pass (start-client-core keeps its 44 existing lint warnings).tanstack-router-e2e-react-basic-file-based-code-splitting:test:e2e: 6 passed. One new-channel warning in the build output, a true positive:posts.$postId.tsxexportsPostErrorComponent, used as itserrorComponent("will not be code-split", item 5). It was already printed withconsole.warnbefore.tanstack-react-start-e2e-basic:test:e2e: all modes pass (137–140 tests each). No new message in the build or test output.e2e/react-start/server-functions(vite dev, Playwright:/,/env-only,/isomorphic-fns,/server-fn-in-client-only-fn, running the env-only and isomorphic functions): no[TanStack Start]runtime report and no compile-time report, so the internalcreateServerOnlyFn/createIsomorphicFn().server()uses of start-client-core are compiled in dev and do not trigger item 1.benchmark:bundle-size:run, all 18 scenarios, feat: migrate Router and Start compilation to Yuku #8504 head37111c76a7vs this branch), production gzip bytes, all unchanged (raw, brotli and initial also +0):The benchmark builds print none of the new diagnostics. Only the measurement phase of the bundle-size workflow was run: there is nothing to optimize at +0.
🤖 Generated with Claude Code