Repository navigation
fix(router): forward @rolldown/plugin-transform-imports to route discovery (vite-plugin-react#1519) - #1204
Merged
Conversation
…overy plugin-rsc's README recommends @rolldown/plugin-transform-imports when a server component imports from a barrel package whose other module imports client-only (vitejs/vite-plugin-react#1519). Rango's discovery temp server forwarded only resolveId/load user plugins, so the rewrite never ran there: `vite build` failed with "[rango] Build-time router discovery failed: 'client-only' cannot be imported in server build", and Cloudflare-preset dev answered 500 on every request. Forward that plugin by name (IMPORT_REWRITE_PLUGINS) with its transform hook. Other transform-only plugins are still dropped; the list's JSDoc states the rule for adding one. Tests: a forward-user-plugins unit test, and a barrel-imports e2e (dev and production) in the router test-app and cloudflare-basic, backed by a rango-e2e-barrel-lib test package.
ivogt
force-pushed
the
fix/discovery-transform-imports
branch
from
October 19, 2026 08:22
b9b7f8c to
45b9f8a
Compare
ivogt
marked this pull request as ready for review
October 19, 2026 08:22
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
devcorner: dc-t104 · session dc-s168
An app that follows plugin-rsc's new barrel-package recipe (vitejs/vite-plugin-react#1519) now builds under rango. Rango's route discovery now applies
@rolldown/plugin-transform-imports's import rewrite too. Before,vite buildfailed, and in Cloudflare-preset dev every request answered 500.Problem
Take a server component that imports one export from a
sideEffects: falsebarrel package, where another module in that barrel importsclient-only. plugin-rsc fails it with'client-only' cannot be imported in server build, because the unused module is still in the module graph. vitejs/vite-plugin-react#1519 (open, docs and e2e only) documents the fix: rewrite barrel imports to subpath imports with@rolldown/plugin-transform-imports.That works in plain plugin-rsc, but not under rango. Rango discovers routes in a temp Vite server that receives only user plugins with
resolveIdorload(selectForwardableResolvePlugins,src/vite/utils/forward-user-plugins.ts). This plugin has only atransformhook, so it was dropped. Discovery then loaded the whole barrel and failed:Before / after
The same app config and server component:
vite buildexits 1 with[rango] Build-time router discovery failed. In Cloudflare-preset dev, every request (/, not only the barrel route) answers 500 with[rango] Failed to create temp runner: 'client-only' cannot be imported….vite buildsucceeds, and the RSC bundle containssome-ui-library/buttononly. Cloudflare-preset dev renders the page.End-to-end consumer example
This is the shape the e2e apps now use: a UI kit whose barrel mixes server-safe and client-only components.
Semantics
vite buildvite buildclient-onlymodule, without transform-imports@tailwindcss/vite,@mdx-js/rollup)Code change
packages/rangojs-router/src/vite/utils/forward-user-plugins.ts: addsIMPORT_REWRITE_PLUGINS, a named allowlist, currently"rolldown-plugin-transform-imports". Plugins on it are forwarded with theirtransformhook, including itsfilter. Its JSDoc states the rule for adding a plugin: its transform changes the code or imports discovery evaluates, and reads only state built at construction or inconfig/configResolved. The predicatehasResolutionHooksbecomesisForwardable.Why a list by name, not something more general. The /simplify altitude review weighed the alternatives:
@tailwindcss/vite: it caches per-environment state keyed by environment name, which the temp server shares.filterstill catches Tailwind and misses MDX.rango()andcloudflare()lifecycle.createTempRscServer(router-discovery.ts) is the only Vite server rango creates, so this one change covers the build, Cloudflare dev and dev prerender.Comment-only edits in
router-discovery.tsanddiscovery/state.ts.Complexity: 12 non-comment source lines added and 3 removed, in 1 file. No new modules, state, wire fields or public API, and no client bytes. The new
Set.hascheck runs once per user plugin, atconfigResolved.Test plan
src/vite/__tests__/forward-user-plugins.test.ts, "forwards the barrel import rewriter with its transform". It pins that the plugin is forwarded with its exact transform hook object, filter included. The existing "skips plugins without resolution hooks" test still pins that other transform-only plugins are dropped.e2e/barrel-imports.test.ts, withbarrel imports(dev) andbarrel imports (production). Each loads/barrel-imports(a server component importingButtonfromrango-e2e-barrel-lib), then expects a 200, no page errors, and the button after hydration.e2e/barrel-imports.test.ts, dev and(production), the same contract.forward-user-plugins.tsreverted:1 failed | 17 passedvite build: exit 1,[rango] Build-time router discovery failed: 'client-only' cannot be imported in server build ('rsc' environment)vite build: exit 1, the same error. Its Playwright webServer fails to start for the same reason.GET /barrel-imports-> 500 andGET /-> 500, with[rango] Failed to create temp runner: 'client-only' cannot be imported…barrel-imports client-package-resolution native-tsconfig-paths prerender.test(dev + production): 94 passedbarrel-imports resolve-plugin-parity client-package-resolution prerender.test(dev + production): 25 passedtest:hmr-routes-local(route-types HMR, which CI skips): 21 passedpnpm run typecheck,pnpm run test:unit:all(router 9145 unit + 1037 RSC passed, every consumer app passed),pnpm run lint, oxfmt check on the changed files. Everycheck:*script passes (suspense-contract, no-external-store, doc-links, docs-api, skills, skills-catalog, route-gen, e2e-bucketing, e2e-parity, e2e-console-baseline). The full e2e suites run on CI.Notes
rsc:validate-importstogenerateBundle, and the maintainer proposes #1519 in its place. #1511 alone would not help rango, because discovery runs in dev mode. Research: devcorner doc dc-d154.@rolldown/plugin-transform-importsas a router devDependency, so I left it to the e2e builds.rsc()and rango's transforms, while in the app's config the rewriter comes first. The rewrite only has to happen before imports are followed, so the order is fine here.@mdx-js/rolluphits the same gap.apps/docs/vite.config.tswraps it in aloadhook for this reason (mdxLoad).unplugin-auto-importmay also be affected, but that is inferred, not verified.@rolldown/plugin-transform-imports@^0.1.2(devDependency) and a localrango-e2e-barrel-libpackage, in both e2e apps. The barrel package is duplicated in both apps, following therango-e2e-deep-context-libprecedent./barrel-importsis nameless, so no*.gen.tschanges.Practices
Lint: oxlint
--deny-warningsreports 0, and oxfmt check passes on the changed files. A repo-wideoxfmt --check .also walks stale.claude/worktrees/*checkouts and fails on those, not on this diff.Dead code:
hasResolutionHooksis renamed toisForwardable; its only caller was in the same file. Nothing is left unused. In the test package,calendar.jsis kept on purpose: it is theclient-onlymodule the barrel must not load.index.d.tsdeclares onlyButton, since TypeScript only sees the barrel import.Tests: as listed above. I added the unit test and both e2e suites; the full e2e suites run on CI.
Code review: I ran /simplify with 4 Opus agents and applied:
toEqualunit assertion.d.tsfiles, no./calendarexport)transformin the test configsI skipped the shared e2e scenario for the two near-identical specs, following the existing precedent. /code-review (medium) found 10 items and I fixed the 3 stale descriptions (
discovery/state.ts, therouter-discovery.tscapture comment,skills/react-compiler/SKILL.md). Left, with reasons in Notes:transformon a matching plugin (harmless as it is)Docs:
docs/internal/feature-file-map.md(the Vite plugin row)docs/react-compiler.mdandskills/react-compiler/SKILL.md: which plugins discovery forwardsUnreleasedentryNo consumer guide recommends the barrel recipe yet. That can come with dc-t105.