Repository navigation
fix(rsc): validate client-only/server-only imports after tree-shaking - #1511
james-elicx wants to merge 2 commits into
Conversation
Thanks for the PR and write-up. The dev still failing is a concern. Rather than partially fixing for build, I'm wondering if we can recommend configuring barrel transform plugin manually as a stopgap. The workaround looks like this #1519 |
|
Alrighty, so I've gone back to my agents and reworked it into our existing plugin for import optimisation. And it works decently without much of a perf impact. also funnily enough i found that webpack actually has this same issue while turbopack doesn't. See the below table my agent created:
from what i gather, turbopack will follow the re-exports without running the client-only import, or something like that. We already had a plugin vinext for Next.js' for what it's worth, i do think this pr highlights a genuine bug that should be fixed, but i can appreciate that it may not be ideal to fix the build side without fixing the dev side, so i don't mind if you want to close it. My agent's summary:
|
Oh, this looks more involved than I thought initially. For this behavior though, to be honest, this looks like turbopack is bending For posterity, some quick sketch of how import chain look likes https://gist.github.com/hi-ogawa/ff662a548d746ed213ea441467bbeddc#file-0-toast-md |
So, looking at again, I was probably mislead by |
Description
rsc:validate-importschecked theclient-only/server-onlyimport chain inbuildEnd, which runs before tree-shaking. A server component importing one export from asideEffects: falsebarrel (e.g.import { Toast } from "@heroui/react") failed the build when an unused leaf of that barrel importedclient-only, even though the bundler drops the leaf and the marker from the output:The same happened when a module imports
client-onlybut only re-exports a used binding (react-aria-componentsexports/Toast.mjs, in a package with"sideEffects": ["*.css"]), and in the mirror case ofserver-onlyreached from a"use client"module. Reported in cloudflare/vinext#4007.Fix
generateBundleand fails only when the invalid marker module, which has side effects, is in an emitted chunk'smoduleIds, i.e. when it actually ships. Direct imports, CJSrequire, dynamicimport(), used leaves, and packages withoutsideEffects: falsestill fail.This matches Next.js with Turbopack, which reports
client-only/server-onlyimport errors only for modules that remain in the tree-shaken graph (insert_client_only_error_alias/insert_server_only_error_aliasincrates/next-core/src/next_import_map.rs). Next.js with webpack still rejects these barrels.Tests
New cases in
e2e/validate-imports.test.ts, using fixture packages that mirror the heroui → react-aria-components graph:client-onlyleaf of asideEffects: falsebarrel; the Toast re-export shape in a package with a globsideEffectsarray (Rolldown only); an unusedserver-onlyleaf reached from a"use client"component (all fail onmain)sideEffects: false, a CJSrequire, a dynamicimport()whose chain names the retained importer (names the dropped leaf onmain), a usedserver-onlyleaf from a client component, and on Rollup the Toast shape, whose chain names the re-export that kept the marker rather than the droppedcalendar.js(namescalendar.jsonmain)vite: ^7,@vitejs/plugin-react: ^5overrides, as in CI)Trade-offs for reviewers
client-only, 4/5 forserver-only) instead of during the first scan build. On the reporter's app (~250 modules per environment) that is ~1.1 s instead of ~0.4 s to the error; on larger apps the gap is larger.createContext. Addressing dev would need barrel/re-export optimization in dev, which is out of scope here.