Filed as a follow-up to #9350 (being closed as fixed by #9398). Original report by @arvinxx at #9350 (comment) — opening a dedicated issue so the import(null) codegen variant is tracked separately and can be verified against the fix.
Reproduction link or steps
Hitting the same dedupe pass (#9305) in production with a different and more severe codegen symptom: rolldown 1.0.1 emits literal import(null) for some import() call sites whose target chunk was deduped away.
Bisect
Using Vite + rolldown through rolldownOptions. Two consecutive Vercel builds, same source commit family (canary), differ only in patch versions:
| Vite |
rolldown |
Result |
| 8.0.12 |
1.0.0 |
OK |
| 8.0.13 |
1.0.1 |
broken |
Vite 8.0.13's only chunk-affecting change is vitejs/vite#22444 — the rolldown 1.0.0 → 1.0.1 bump, which contains #9305.
Concrete diff between the two builds
Same source (src/libs/trpc/client/lambda.ts):
headers: async () => {
const { createHeaderWithAuth } = await import('@/services/_auth');
// ...
}
rolldown 1.0.0 (working) — client-DYN1hRzS.js:
let{createHeaderWithAuth:e}=await o(async()=>{
let{createHeaderWithAuth:e}=await import(`./_auth-enwjeLpB.js`).then(e=>(e.i(),e.t));
return{createHeaderWithAuth:e}
}, __vite__mapDeps([45,1,8,46,12,3,13,14,4,5,6,7,9,10,11,15,16,4,...]))
rolldown 1.0.1 (broken) — client-DPywsu0Y.js:
let{createHeaderWithAuth:e}=await o(async()=>{
let{createHeaderWithAuth:e}=await import(null); // ← literal null
return{createHeaderWithAuth:e}
}, []) // ← deps emptied too
A grep over the entire broken client-*.js for _auth-*.js returns zero matches — rolldown didn't just empty the deps list, it eliminated every reference to the _auth chunk filename, but left the import() call site behind with a null argument. Two distinct call sites in the bundle exhibit this (both are lambda.ts / tools.ts headers callbacks that share the import('@/services/_auth') pattern).
Likely shape that triggers it
The file containing the broken import has 6 sibling dynamic imports inside one tRPC client setup:
// inside errorHandlingLink + httpBatchLink headers, all in the same module:
await import('@/services/_auth');
await import('@/layout/AuthProvider/MarketAuth/events');
await import('@/store/user/store');
await import('@/components/Error/loginRequiredNotification');
await import('@/store/image');
await import('@/store/image/slices/generationConfig/selectors');
These targets share several transitive deps, so the "already-loaded by every importer" analysis from #9305 has many candidates to merge — consistent with the unsound-sibling-merge failure mode in #9350, just escalated all the way to "the chunk itself disappears, the call site stays."
If a separate minimal repro for the import(null) variant would help, happy to put one together.
What is expected?
import() call sites should emit a valid chunk specifier (or be removed entirely along with the call site). They should never resolve to literal null at runtime.
What is actually happening?
Production runtime throws on every tRPC call:
TypeError: Failed to resolve module specifier 'null'
…because the headers callback contains await import('@/services/_auth') and rolldown emitted import(null) for it. The headers callback is on the hot path for every request, so the whole app is down.
The chunk_optimization dedupe pass appears to reduce the _auth chunk's dependent-entry bits to empty for some sibling-dynamic-import shapes; the rewriting step then has nothing to point the import() at and writes import(null). Believed to be the same root cause as #9350 — under-approximating "guaranteed already loaded" when sibling dynamic entries can be reached independently — but with a much more severe symptom (the chunk filename is gone, not just an extra side effect). Worth confirming whether #9398 already covers this variant.
System Info
rolldown 1.0.1 (regression vs 1.0.0)
Vite 8.0.13 (regression vs 8.0.12)
Build environment: Vercel
Repro shape: 6 sibling `await import()` calls in the same module sharing transitive deps
Any additional comments?
Real-world impact: For LobeHub the only mitigation was pinning Vite back to 8.0.12 (lobehub/lobehub#14804). The repo installs with lockfile=false + resolution-mode=highest, so the broken 1.0.1 was picked up automatically on the next deploy after 8.0.13 published and took the whole app offline until the pin landed.
Reported by @arvinxx in #9350 (comment).
Reproduction link or steps
Hitting the same dedupe pass (#9305) in production with a different and more severe codegen symptom: rolldown 1.0.1 emits literal
import(null)for someimport()call sites whose target chunk was deduped away.Bisect
Using Vite + rolldown through
rolldownOptions. Two consecutive Vercel builds, same source commit family (canary), differ only in patch versions:Vite 8.0.13's only chunk-affecting change is vitejs/vite#22444 — the rolldown 1.0.0 → 1.0.1 bump, which contains #9305.
Concrete diff between the two builds
Same source (
src/libs/trpc/client/lambda.ts):rolldown 1.0.0 (working) —
client-DYN1hRzS.js:rolldown 1.0.1 (broken) —
client-DPywsu0Y.js:A
grepover the entire brokenclient-*.jsfor_auth-*.jsreturns zero matches — rolldown didn't just empty the deps list, it eliminated every reference to the_authchunk filename, but left theimport()call site behind with anullargument. Two distinct call sites in the bundle exhibit this (both arelambda.ts/tools.tsheaders callbacks that share theimport('@/services/_auth')pattern).Likely shape that triggers it
The file containing the broken import has 6 sibling dynamic imports inside one tRPC client setup:
These targets share several transitive deps, so the "already-loaded by every importer" analysis from #9305 has many candidates to merge — consistent with the unsound-sibling-merge failure mode in #9350, just escalated all the way to "the chunk itself disappears, the call site stays."
If a separate minimal repro for the
import(null)variant would help, happy to put one together.What is expected?
import()call sites should emit a valid chunk specifier (or be removed entirely along with the call site). They should never resolve to literalnullat runtime.What is actually happening?
Production runtime throws on every tRPC call:
…because the headers callback contains
await import('@/services/_auth')and rolldown emittedimport(null)for it. The headers callback is on the hot path for every request, so the whole app is down.The
chunk_optimizationdedupe pass appears to reduce the_authchunk's dependent-entry bits to empty for some sibling-dynamic-import shapes; the rewriting step then has nothing to point theimport()at and writesimport(null). Believed to be the same root cause as #9350 — under-approximating "guaranteed already loaded" when sibling dynamic entries can be reached independently — but with a much more severe symptom (the chunk filename is gone, not just an extra side effect). Worth confirming whether #9398 already covers this variant.System Info
Any additional comments?
Real-world impact: For LobeHub the only mitigation was pinning Vite back to 8.0.12 (lobehub/lobehub#14804). The repo installs with
lockfile=false+resolution-mode=highest, so the broken 1.0.1 was picked up automatically on the next deploy after 8.0.13 published and took the whole app offline until the pin landed.Reported by @arvinxx in #9350 (comment).