next.js
a75ece16 - Turbopack: don't strip async-module runtime from shared runtime chunks (#96599)

Commit
5 days ago
Turbopack: don't strip async-module runtime from shared runtime chunks (#96599) ### What? Turbopack could emit a `[turbopack]_runtime.js` without the async-module (top-level await) machinery while chunks written next to it call `__turbopack_context__.a(...)`, failing production builds with `TypeError: __turbopack_context__.a is not a function`. Reported against 16.3 when loading a `postcss.config.js`. Intermittent, production only. ### Why? The runtime chunk is emitted to a fixed path, so every module graph using a chunking context writes the same file. Since #94376 the async-module machinery is dropped when a graph has no async modules — but that's decided per graph. The Next.js node execution context is shared by every build-time JS evaluation (postcss configs, webpack loaders, `next/font/google`), each with its own `ModuleGraph`. A graph with no async modules emits a runtime without `.a` and clobbers the variant the postcss loader needs. The winner is `assets.first()` in `emit_assets`, which depends on emission order — hence the intermittency. ### How? Add `shared_runtime_chunk` to the chunking contexts and set it where one runtime is shared by several graphs, so those always emit the complete runtime. This matches the carve-out that already exists for development: in both cases a single graph can't see everything sharing the runtime. Whole-app server contexts keep the optimization. The browser side tested `RuntimeType::Development` as a proxy for per-page graphs, which only works because `per_page_module_graph` currently *is* `mode == Development`. It now reads the real flag. Also renames `has_async_modules` to `include_async_module_runtime`, since it's now true whenever we can't tell. I audited the rest of `.next/build/` for the same hazard: the runtime chunk (and its `.map`) was the only fixed-path output. Everything else is content-hashed via `AssetIdent::output_name`. ### Testing `test/production/app-dir/turbopack-shared-runtime-async-module` uses a single synchronous loader — enough on its own to produce the stripped runtime. Verified to fail without the fix and pass with it. `cargo test -p turbopack-tests` passes with no snapshot churn. Follow-up, not in this PR: the conflict is silent. `EmitConflictIssue` is `IssueSeverity::Error` but never fired here, because the two runtimes weren't grouped into one `emit_assets` call.
Author
Parents
Loading