feat: harden universal (node + web) target support — CSS SSR, coverage, runtime helper#21208
Conversation
…urce-phase imports Cross-platform (web + node) checks for a universal ESM target: - asset/resource public path resolves to publicPath + hashed name, incl. an asset in a dynamically imported chunk - a node builtin and a browser-only `global` external each resolve on the platform that provides them - `import source` of externals runs under both web and node https://claude.ai/code/session_01DU6PsnqWro8626o19PVM6h
…r SSR Make universal CSS apps run in Node end-to-end and expose their styles, with no extra runtime code on single-platform builds: - Add `RuntimeTemplate#cssServerStyleRegistry()` — one `globalThis` registry (namespaced by uniqueName) an SSR host reads; and `isWebLikePlatformExpression()`. - `css-style-sheet` export: web emits `new CSSStyleSheet()`, node a text-carrying fallback object, universal a `typeof CSSStyleSheet` branch between them. - `style` injection collects into the registry when there is no DOM. - `link` chunk loading reads the emitted `.css` via the shared `getBuiltinModule` helper and collects it, falling back to the previous no-op on read error. - Reuse the new web-like probe in the universal async wasm loader. Only neutral/universal targets emit these branches; web and node-only are unchanged. https://claude.ai/code/session_01DU6PsnqWro8626o19PVM6h
ESM/module-format HMR downloads hot-update chunks via a local import()-based loader, so the global `__webpack_require__.l` (LoadScriptRuntimeModule, which references `document`) was emitted but never called. Stop requiring it, removing dead browser-only code from every module-format HMR bundle (web, node, universal). https://claude.ai/code/session_01DU6PsnqWro8626o19PVM6h
Use fs.readFile instead of readFileSync in the universal CSS chunk-loading node branch so an SSR server's event loop is not blocked while reading the emitted .css; the chunk promise already resolves via loadingEnded in the callback. https://claude.ai/code/session_01DU6PsnqWro8626o19PVM6h
🦋 Changeset detectedLatest commit: 25fc336 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #21208 +/- ##
==========================================
+ Coverage 92.71% 92.73% +0.01%
==========================================
Files 589 591 +2
Lines 64190 64400 +210
Branches 17806 17863 +57
==========================================
+ Hits 59516 59720 +204
- Misses 4674 4680 +6
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…n older node
process.getBuiltinModule is node 22.3+, so it was falsy on older node, leaving
the SSR CSS registry unpopulated. Read the emitted .css via dynamic import('fs')
(the universal wasm loader's pattern) so it works on every node version.
The universal-node-vs-browser external test relies on node-commonjs (createRequire
via getBuiltinModule), so gate it with supportsProcessGetBuiltinModule like the
other node-commonjs universal tests.
Merging this PR will not alter performance
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ❌ | Memory | benchmark "side-effects-reexport", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' |
128.7 KB | 859.3 KB | -85.03% |
| ❌ | Memory | benchmark "asset-modules-bytes", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' |
246.9 KB | 325.8 KB | -24.19% |
| ⚡ | Memory | benchmark "lodash", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' |
859.1 KB | 126.8 KB | ×6.8 |
| ⚡ | Memory | benchmark "wasm-modules-async", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' |
330.1 KB | 190.5 KB | +73.3% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing feat/universal-target-support (25fc336) with main (66a67df)
|
This PR is packaged and the instant preview is available (6e43ab8). Install it locally:
npm i -D webpack@https://pkg.pr.new/webpack@6e43ab8
yarn add -D webpack@https://pkg.pr.new/webpack@6e43ab8
pnpm add -D webpack@https://pkg.pr.new/webpack@6e43ab8 |
The server-side style registry is exposed on globalThis, which only exists on node 12+, so skip these cases on older node like the existing universal-css test (universal targets already default output.globalObject to globalThis).
It uses a `global globalThis` external and asserts against globalThis, which only exists on node 12+, so skip it on older node like the other universal cases.
The universal ESM bundles use globalThis/modern output unsupported on node 10, so gate the last two universal cases on supportsGlobalThis (node 12+) like the existing universal-css test, keeping the node 10 CI shard green.
…port Its node SSR style collection writes to globalThis (node 12+); gate the hot case like the universal config cases so the node 10 CI shard stays green.
Summary
Hardens webpack's universal target (
target: ["web", "node"]+output.module, i.e. the neutral platform whereplatform.web === null && platform.node === null) so universal bundles run in Node for SSR without crashing and expose their styles server-side, and broadens cross-platform test coverage.css-style-sheetexport returns a text-carrying fallback object whenCSSStyleSheetis absent;styleinjection andlink-chunk loading collect CSS into aglobalThisregistry an SSR host can read, reading the emitted.cssasynchronously via the sharedgetBuiltinModulehelper.RuntimeTemplate#isWebLikePlatformExpression()as the single node-or-web runtime probe, reused by the universal async wasm loader.loadScriptruntime from ESM hot-update bundles.Single-platform (web / node) builds emit no extra runtime — every node/web branch is gated to neutral targets. No related tracking issue.
What kind of change does this PR introduce?
feat (also includes test and perf changes).
Did you add tests for your changes?
Yes —
test/configCases/css/{import-css-stylesheet-universal, export-type-style-universal, universal-async-css-chunk},test/configCases/target/universal-public-path, andtest/configCases/externals/{universal-node-vs-browser, universal-phase-imports-source}; each runs under both the web and node sub-platforms of a universal target.Does this PR introduce a breaking change?
No.
If relevant, what needs to be documented once your changes are merged or what have you already documented?
The server-side CSS registry exposed at
globalThis["__webpack_css__" + uniqueName](the SSR retrieval surface forstyle/linkCSS under a universal target) should be documented.Use of AI
Yes. Implemented with Claude Code (Anthropic): AI assisted in auditing the runtime/generators, writing the implementation and tests, and verifying behavior. All changes were reviewed and validated against the test suite, per the webpack AI policy.
🤖 Generated with Claude Code
https://claude.ai/code/session_01DU6PsnqWro8626o19PVM6h
Generated by Claude Code