Repository navigation
docs(rsc): document barrel package workaround for validate-imports - #1519
Merged
Merged
Conversation
Add e2e coverage with a synthetic `sideEffects: false` barrel package whose unused leaf imports `client-only`, which fails dev and build, and passes both with `@rolldown/plugin-transform-imports` rewriting to subpaths. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hi-ogawa
marked this pull request as ready for review
October 19, 2026 05:54
6 tasks done
ivogt
added a commit
to rangojs/rango
that referenced
this pull request
Oct 19, 2026
…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.
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.
A server component that imports one component from a UI library's barrel entry, such as
import { Toast } from "@heroui/react", fails with'client-only' cannot be imported in server buildwhen an unused module the barrel re-exports importsclient-only. #1511 moves the build check after tree-shaking, but dev still fails because Vite evaluates every module the barrel re-exports. In the heroui case, react-aria's top-levelcreateContextcrashes dev even when the check is disabled.Rewriting barrel imports to subpath imports fixes both dev and build without changes to the plugin. This PR adds a README section on barrel packages that shows how to do it with
@rolldown/plugin-transform-imports, lists the gotchas I hit, and points to a"use client"component when a subpath still reaches client-only code.The e2e test uses a synthetic
sideEffects: falsebarrel package where a server component usesButtonand an unusedCalendarimportsclient-only:For
@heroui/react, I checked this setup on the reporter's repro with vinext 1.2.0 and plugin-rsc 0.5.38. Dev renders, and the build passes and renders in preview:Skeletonworks through the barrel with the transform, because its subpath entry only reaches a"use client"module.Toastis different, because the import that fails is inside heroui, so rewriting app imports can't reach it. In heroui 3.2.1 and react-aria-components 1.18.0, the chain is:components/toast/index.jshas no directive, so it is server code. It importstoast-queue.jsto build the compoundToastobject.components/toast/toast-queue.jshas no directive either. It importsUNSTABLE_ToastQueuefromreact-aria-components/Toastand creates atoastQueueinstance at the module's top level.react-aria-components/dist/exports/Toast.mjshasimport "client-only", and it also imports the toast components, whose module callscreateContextat the top level. That call throws under thereact-servercondition, which is the dev error.The queue class itself comes from
react-stately/useToastStateand is safe on the server, but heroui imports it through react-aria-components' client-only entry. Tools disagree on this graph. Vite dev evaluates the entry and crashes, and Next's webpack build reportsclient-only. Next's Turbopack follows the re-export to react-stately and never evaluates the entry, and Rolldown drops the entry from the output, which is why the build passes with #1511. vinext#4099 gets Turbopack's result in Vite by rewriting heroui's import to react-stately in the rsc environment.Which behavior is right is debatable:
import "client-only"on theToastentry on purpose, with a comment saying it should break RSC imports. heroui imports that entry from modules with no directive, which run on the server. Rejecting matches ESM evaluation and webpack, and it keeps dev and build consistent. The lenient result relies on the package's"sideEffects": ["*.css"], which declares the entry side-effect free even though itsclient-onlyimport is a side effect by design. Under that reading, the marker can't fire for any named import. The entry defines nothing of its own, since all eight of its exports are re-exports plus theclient-onlyimport, so following re-exports skips it even for the components the marker is meant to keep out of rsc. Only a namespace, bare or dynamic import of the entry still hits it.sideEffectsexplicitly allows skipping modules whose own exports are unused, and Turbopack, Next.js's default bundler, renders this page. But following re-exports is no safety net: a server component importingCollectionfromreact-aria-components/Collectionfails withcreateContext is not a functionin Turbopack, a less clear error thanclient-only.This PR keeps plugin-rsc's current behavior, rejecting it in both dev and build, and documents the
"use client"wrapper as the user-side fix. The cleaner fix is upstream, either heroui importingToastQueuefromreact-stately/useToastStateor react-aria-components exporting the queue from an entry without the marker.