Skip to content

Add HMR test suite for universal (node + web) targets#21193

Merged
alexander-akait merged 5 commits into
mainfrom
test/universal-target-hmr
Jun 16, 2026
Merged

Add HMR test suite for universal (node + web) targets#21193
alexander-akait merged 5 commits into
mainfrom
test/universal-target-hmr

Conversation

@alexander-akait

Copy link
Copy Markdown
Member

Summary

We test HMR per single target (HotTestCasesNode, HotTestCasesWeb, …) but never assert that a universal build (one ESM bundle for both node and web) hot-updates correctly in every environment. This adds HotTestCasesUniversal, which builds each opted-in hot case as a universal target (["web", "node"], ESM output) and runs the same bundle once per environment — reusing the existing TestRunner.isUniversalTarget expansion (web+node, electron, webworker, …).

New test/hotCases/universal/ cases cover the module types: ESM, CommonJS interop, JSON, dynamic import, import attributes (with { type }), import defer, async WebAssembly, CSS (locals / text / style export types), and a ["web", "node", "webworker"] multi-target case.

Supporting changes: a small runtime guard so CSS style injection no-ops when there is no document (universal/node), and the test runner's fetch now returns a real Response (with arrayBuffer/wasm mime type) so async wasm loads in the web environment.

What kind of change does this PR introduce?

test (plus a small CSS-runtime guard).

Did you add tests for your changes?

Yes — this PR is the tests: test/HotTestCasesUniversal.test.js, the shared test/HotTestCases.template.js, and the test/hotCases/universal/* cases. The CSS style guard is exercised by test/hotCases/universal/css-export-type-style.

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?

n/a — no public API change. The universal target itself remains emulated (see follow-ups) and is not yet a documented option.

Use of AI

Yes. Authored with Claude Code: it explored the hot-test/runner harness, wrote the suite and cases, made the CSS/harness changes, and ran the suites locally; all changes were reviewed before committing.


Follow-ups needed for full universal-target support

This PR only adds coverage and the minimal runtime guards needed to run it. To actually ship a universal target, these still need work:

  • First-class target: "universal" — today it is emulated with ["web", "node"] + output.module; a dedicated target should set the neutral platform (web=null, node=null) directly so users don't hand-assemble it.
  • CSS css-style-sheet export type — depends on CSSStyleSheet, which is absent in node, so it stays web-only for now; needs an SSR-friendly universal behavior before it can run universally (this PR intentionally has no node fallback for it).
  • CSS style injection in node — currently a no-op (no document); for SSR it should collect/expose styles server-side instead of silently dropping them.
  • CSS chunk loading in node — the loading runtime is document-guarded and no-ops; universal SSR needs node-side CSS retrieval (e.g. reading the emitted file) so server output includes styles.
  • Universal Worker instantiationnew Worker(new URL(...)) across Web Workers vs node worker_threads needs runtime + test coverage.
  • HMR transport in node — only module re-evaluation is exercised here; verify hot-update manifest/chunk fetching works under node universal (fetch vs fs).
  • Consolidate platform probes — many runtime modules branch on ad-hoc typeof document/self !== "undefined"; a single shared universal platform-detection helper would be more robust.
  • Wider module-type coverage — source-phase imports (import source) and source-phase wasm, asset/resource public-path resolution, and externals (node builtins vs browser) under a universal target.

Generated by Claude Code

Add HotTestCasesUniversal that builds hot cases as a universal target
(["web", "node"], ESM output) and runs each bundle in both a web and a
node environment, mirroring the universal handling in the config-cases
runner. Cover the module types (ESM, CommonJS interop, JSON, dynamic
import, import attributes, defer import) plus a multi-target case that
also exercises the webworker environment.
Guard the CSS style-injection runtime and the `css-style-sheet` export so
universal/neutral targets no longer reference `document`/`CSSStyleSheet`
in node; `css-style-sheet` falls back to the raw CSS text there.

Add universal hot cases for wasm and for CSS export types (locals, text,
style, css-style-sheet), and teach the test runner's `fetch` to serve a
real Response (arrayBuffer/text + wasm mime) so async wasm loads in the
web environment.
Replace the universal-config detection with a check that the target lists
both a node-like and a web-like environment, as a stand-in until a real
universal target lands. Drop the `universal` suite flag in favour of it.

Revert the `css-style-sheet` neutral-platform fallback and its hot case;
that export type stays web-only for now. The `style` injection guard and
its node+web hot case remain.
Restore the general isUniversalTarget check (covers web+node, electron,
webworker and other neutral-platform combos) instead of a node+web-only
probe, in preparation for a dedicated universal target. The hot suite
derives its behavior from it rather than a config flag.
@changeset-bot

changeset-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7e91224

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
webpack Patch

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

@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

This PR is packaged and the instant preview is available (b6d570d).

Install it locally:

  • npm
npm i -D webpack@https://pkg.pr.new/webpack@b6d570d
  • yarn
yarn add -D webpack@https://pkg.pr.new/webpack@b6d570d
  • pnpm
pnpm add -D webpack@https://pkg.pr.new/webpack@b6d570d

@codecov

codecov Bot commented Jun 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.70%. Comparing base (877f3dc) to head (7e91224).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #21193   +/-   ##
=======================================
  Coverage   92.70%   92.70%           
=======================================
  Files         588      588           
  Lines       64091    64122   +31     
  Branches    17785    17799   +14     
=======================================
+ Hits        59416    59447   +31     
  Misses       4675     4675           
Flag Coverage Δ
css-parsing 28.68% <ø> (-0.01%) ⬇️
html5lib 31.19% <ø> (-0.01%) ⬇️
integration 88.68% <100.00%> (+<0.01%) ⬆️
test262 45.59% <ø> (+0.04%) ⬆️
unit 41.06% <ø> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@codspeed-hq

codspeed-hq Bot commented Jun 16, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 49.16%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 4 improved benchmarks
❌ 1 regressed benchmark
✅ 139 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory benchmark "many-chunks-esm", scenario '{"name":"mode-production","mode":"production"}' 7.1 MB 10.1 MB -29.6%
Memory benchmark "asset-modules-inline", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 1,293.6 KB 324.6 KB ×4
Memory benchmark "side-effects-reexport", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 1,208.6 KB 760.9 KB +58.85%
Memory benchmark "asset-modules-bytes", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 326.8 KB 247.8 KB +31.85%
Memory benchmark "devtool-source-map", scenario '{"name":"mode-production","mode":"production"}' 7.7 MB 6.2 MB +25.66%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing test/universal-target-hmr (7e91224) with main (8596a28)

Open in CodSpeed

The async-wasm bundle is an async module (top-level await) and its web
path needs `Response`, so the case fails to parse/run on Node 10/12.
Filter it to WebAssembly- and Response-capable Node, matching the
configCases/wasm/universal guard.
@github-actions

Copy link
Copy Markdown
Contributor

Types Coverage

Coverage after merging test/universal-target-hmr into main will be
99.33%
Coverage Report<
FileStmtsBranchesFuncsLinesUncovered Lines
bin
   webpack.js98.77%100%100%98.77%91
examples
   build-common.js100%100%100%100%
   buildAll.js100%100%100%100%
   examples.js100%100%100%100%
   template-common.js98.21%100%100%98.21%72
examples/custom-javascript-parser
   test.filter.js100%100%100%100%
examples/custom-javascript-parser/internals
   acorn-parse.js100%100%100%100%
   meriyah-parse.js100%100%100%100%
   oxc-parse.js91.30%100%100%91.30%140, 142–143, 145, 147, 153–154, 161, 168, 90
examples/markdown
   webpack.config.mjs100%100%100%100%
examples/typescript
   test.filter.js100%100%100%100%
examples/typescript-non-erasable
   test.filter.js50%100%100%50%5
examples/virtual-modules
   test.filter.js100%100%100%100%
examples/wasm-bindgen-esm
   test.filter.js100%100%100%100%
examples/wasm-complex
   test.filter.js100%100%100%100%
examples/wasm-simple
   test.filter.js100%100%100%100%
examples/wasm-simple-source-phase
   test.filter.js100%100%100%100%
lib
   APIPlugin.js100%100%100%100%
   AsyncDependenciesBlock.js100%100%100%100%
   AutomaticPrefetchPlugin.js100%100%100%100%
   BannerPlugin.js100%100%100%100%
   Cache.js98.21%100%100%98.21%101
   CacheFacade.js100%100%100%100%
   Chunk.js99.72%100%100%99.72%39
   ChunkGraph.js100%100%100%100%
   ChunkGroup.js100%100%100%100%
   ChunkTemplate.js100%100%100%100%
   CleanPlugin.js99.15%100%100%99.15%206, 226
   CodeGenerationResults.js100%100%100%100%
   CompatibilityPlugin.js100%100%100%100%
   Compilation.js98.44%100%100%98.44%1609, 1905, 1912, 1920, 1942, 2838, 3317–3318, 3350, 4014, 4044, 4097–4098, 4102, 4107, 4123–4124, 4138–4139, 4144–4145, 4622, 4648, 513, 518, 5456, 5488, 5505, 5521, 5537, 5552, 5577–5578, 5580, 5908, 5913, 5919, 5922, 5934, 5936, 5940, 5956, 5971, 6003, 6057, 6081, 6195, 763–764
   Compiler.js99.56%100%100%99.56%1135–1136, 1144
   ConcatenationScope.js98.59%100%100%98.59%189
   ConditionalInitFragment.js100%100%100%100%
   ConstPlugin.js100%100%100%100%
   ContextExclusionPlugin.js100%100%100%100%
   ContextModule.js100%100%100%100%
   ContextModuleFactory.js97.40%100%100%97.40%258, 395, 418, 420, 424, 433–434
   ContextReplacementPlugin.js100%100%100%100%
   DefinePlugin.js99%100%100%99%171–172, 188, 207, 281
   DependenciesBlock.js100%100%100%100%
   Dependency.js98.15%100%100%98.15%382, 428
   DependencyTemplate.js100%100%100%100%
   DependencyTemplates.js100%100%100%100%
   DotenvPlugin.js98.41%100%100%98.41%378, 391–392
   DynamicEntryPlugin.js100%100%100%100%
   EntryOptionPlugin.js100%100%100%100%
   EntryPlugin.js100%100%100%100%
   Entrypoint.js100%100%100%100%
   EnvironmentPlugin.js97.14%100%100%97.14%49
   ErrorHelpers.js100%100%100%100%
   EvalDevToolModulePlugin.js100%100%100%100%
   EvalSourceMapDevToolPlugin.js100%100%100%100%
   ExportsInfo.js100%100%100%100%
   ExportsInfoApiPlugin.js100%100%100%100%
   ExternalModule.js98.67%100%100%98.67%1062, 1065, 448–452, 600
   ExternalModuleFactoryPlugin.js100%100%100%100%
   ExternalsPlugin.js100%100%100%100%
   FileSystemInfo.js99.50%100%100%99.50%182, 2252–2253, 2256, 2267, 2278, 2289, 278, 3693, 3708, 3732
   FlagAllModulesAsUsedPlugin.js100%100%100%100%
   FlagDependencyExportsPlugin.js98.46%100%100%98.46%425, 434, 436, 440
   FlagDependencyUsagePlugin.js100%100%100%100%
   FlagEntryExportAsUsedPlugin.js100%100%100%100%
   Generator.js100%100%100%100%
   HotModuleReplacementPlugin.js100%100%100%100%
   HotUpdateChunk.js100%100%100%100%
   IgnorePlugin.js100%100%100%100%
   IgnoreWarningsPlugin.js100%100%100%100%
   InitFragment.js100%100%100%100%
   JavascriptMetaInfoPlugin.js100%100%100%100%
   LibraryTemplatePlugin.js100%100%100%100%
   LoaderOptionsPlugin.js100%100%100%100%
   LoaderTargetPlugin.js100%100%100%100%
   MainTemplate.js100%100%100%100%
   ManifestPlugin.js100%100%100%100%
   Module.js98.50%100%100%98.50%1285, 1290, 1350, 1364, 1426, 1435
   ModuleFactory.js100%100%100%100%
   ModuleFilenameHelpers.js98.85%100%100%98.85%106, 108
   ModuleGraph.js99.73%100%100%99.73%1005
   ModuleGraphConnection.js100%100%100%100%
   ModuleInfoHeaderPlugin.js100%100%100%100%
   ModuleNotFoundError.js100%100%100%100%
   ModuleProfile.js100%100%100%100%
   ModuleSourceTypeConstants.js100%100%100%100%
   ModuleTemplate.js100%100%100%100%
   ModuleTypeConstants.js100%100%100%100%
   MultiCompiler.js99.69%100%100%99.69%659
   MultiStats.js100%100%100%100%
   MultiWatching.js100%100%100%100%
   NoEmitOnErrorsPlugin.js100%100%100%100%
   NodeStuffPlugin.js100%100%100%100%
   NormalModule.js97.90%100%100%97.90%1240, 1243, 1260, 1277, 1524, 1558, 1574, 1661, 2017, 2316, 2321–2331, 419, 423, 577
   NormalModuleFactory.js99.47%100%100%99.47%1083, 1392, 486, 498
   NormalModuleReplacementPlugin.js100%100%100%100%
   NullFactory.js100%100%100%100%
   OptimizationStages.js100%100%100%100%
   OptionsApply.js100%100%100%100%
   Parser.js100%100%100%100%
   PlatformPlugin.js100%100%100%100%
   PrefetchPlugin.js100%100%100%100%
   ProgressPlugin.js98.85%100%100%98.85%519–520, 525, 527, 591
   ProvidePlugin.js100%100%100%100%
   RawModule.js100%100%100%100%
   RecordIdsPlugin.js100%100%100%100%
   RequestShortener.js100%100%100%100%
   ResolverFactory.js100%100%100%100%
   RuntimeGlobals.js100%100%100%100%
   RuntimeModule.js100%100%100%100%
   RuntimePlugin.js100%100%100%100%
   RuntimeTemplate.js100%100%100%100%
   SelfModuleFactory.js100%100%100%100%
   SingleEntryPlugin.js100%100%100%100%
   SourceMapDevToolModuleOptionsPlugin.js100%100%100%100%
   SourceMapDevToolPlugin.js98.62%100%100%98.62%220, 224, 226, 419, 430, 891
   Stats.js100%100%100%100%
   Template.js100%100%100%100%
   TemplatedPathPlugin.js99.17%100%100%99.17%176–177
   UseStrictPlugin.js100%100%100%100%
   WarnCaseSensitiveModulesPlugin.js100%100%100%100%
   WarnDeprecatedOptionPlugin.js100%100%100%100%
   WarnNoModeSetPlugin.js100%100%100%100%
   WatchIgnorePlugin.js100%100%100%100%
   Watching.js100%100%100%100%
   WebpackError.js100%100%100%100%
   WebpackIsIncludedPlugin.js100%100%100%100%
   WebpackOptionsApply.js100%100%100%100%
   WebpackOptionsDefaulter.js100%100%100%100%
   buildChunkGraph.js99.87%100%100%99.87%371
   cli.js98.62%100%100%98.62%10, 119, 545, 577, 627, 897
   index.js99.72%100%100%99.72%179
   validateSchema.js94.67%100%100%94.67%100, 87, 89, 98
   webpack.js96.33%100%100%96.33%10, 198, 220, 222
lib/asset
   AssetBytesGenerator.js100%100%100%100%
   AssetBytesParser.js100%100%100%100%
   AssetGenerator.js100%100%100%100%
   AssetModule.js100%100%100%100%
   AssetModulesPlugin.js97.32%100%100%97.32%281, 305, 308, 36, 360, 41
   AssetParser.js100%100%100%100%
   AssetSourceGenerator.js100%100%100%100%
   AssetSourceParser.js100%100%100%100%
   RawDataUrlModule.js100%100%100%100%
lib/async-modules
   AsyncModuleHelpers.js100%100%100%100%
   AwaitDependenciesInitFragment.js100%100%100%100%
   InferAsyncModulesPlugin.js100%100%100%100%
lib/cache
   AddBuildDependenciesPlugin.js100%100%100%100%
   AddManagedPathsPlugin.js100%100%100%100%
   IdleFileCachePlugin.js97.92%100%100%97.92%71, 83, 91
   MemoryCachePlugin.js95.83%100%100%95.83%33
   MemoryWithGcCachePlugin.js93.15%100%100%93.15%106, 113–114, 122, 89
   PackFileCacheStrategy.js96.40%100%100%96.40%1250, 1350, 1354, 1416, 628, 647, 657–659, 661, 677–678, 683, 686, 688, 693, 698, 722, 728, 762, 768, 774, 779, 790, 799, 804–805, 807, 824, 830–831, 833
   ResolverCachePlugin.js100%100%100%100%
   getLazyHashedEtag.js100%100%100%100%
   mergeEtags.js100%100%100%100%
lib/config
   browserslistTargetHandler.js100%100%100%100%
   defaults.js99.31%100%100%99.31%1439–1441, 1449, 274, 277, 282, 286
   normalization.js99.01%100%100%99.01%191–192, 258, 273
   target.js100%100%100%100%
lib/container
   ContainerEntryDependency.js100%100%100%100%
   ContainerEntryModule.js100%100%100%100%
   ContainerEntryModuleFactory.js100%100%100%100%

@alexander-akait alexander-akait merged commit b6d570d into main Jun 16, 2026
66 checks passed
@alexander-akait alexander-akait deleted the test/universal-target-hmr branch June 16, 2026 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant