Cut Bun test skips down to the few cases Bun genuinely can't run#21239
Conversation
Under Bun, jest's vm `importModuleDynamically` callback resolving to a
`vm.Module` yields an empty namespace, so less-loader's `import('less')`
came back undefined. Coerce the callback result to `module.namespace` in
the Bun preload, then re-enable the less cases (Deno skips kept).
JSC (Bun) and some other engines emit a frames-only `Error.stack` without the leading "Name: message" line V8 adds, so ModuleBuildError rendered loader errors as bare stack frames with no message. Lead with the message when the stack does not already contain it, then un-skip configCases/loaders/options under Bun.
Bun (JSC) phrases the magic-comment syntax error and the JSON manifest parse error differently than V8. Normalize both to the V8 form in the stats harness (no-op on V8, which never emits those strings) and un-skip the two cases.
…p webpack-ignore Bun (JSC) quotes the bad token and appends "Expected …" in magic-comment parse errors where V8 does neither. Normalize to the V8 form in checkArrayExpectation (no-op on V8) and un-skip configCases/css/webpack-ignore, which was mislabeled as a less case but actually hit this phrasing difference.
Replace the vague "worker chunk does not run to completion" notes with the diagnosed cause: a message queued while webpack's import() worker chunk loading (Promise.race) is in flight stalls Bun's event loop. Also record why the logging stats trace is engine-inherent (V8 vs JSC Error.stack shape and line:col).
…kip Bun moduleScope matched the css bundle with /bundle.+.css/, which also matches the .css.map source map; with readdirSync order unspecified, Bun picked the map file so the linked stylesheet had no rules and getComputedStyle returned undefined. Anchor the regex to .css. Deno kept skipped (separate undefined-CSS-export issue).
afterExecute snapshotted raw readdirSync output, whose order is filesystem-dependent; Node happened to return alphabetical order (matching the recorded snapshot) but Bun does not. Sort before snapshotting. Deno kept skipped (separate undefined-CSS-export issue).
…orker HMR Two Bun-specific issues in test/helpers/createFakeWorker.js: - Bun implements the web Worker API, so assigning self.onmessage registers a native handler; forwarding parentPort messages to it too delivered every message twice (breaking HMR check() with reentrancy). Skip the forward on Bun. - Bun's eval-worker module context is a blob: URL, from which require() of the emitted chunks (entry and hot-update) fails to resolve. Root require at a real file via module.createRequire. Un-skip the three worker HMR cases; Node/Deno behavior is unchanged.
The case passes reliably on Bun now (verified 3x); remove the Bun-only filter.
findBundle matched bundle names with an unanchored regex, so `^bundle1` also matched `bundle10`..`bundle17` and the first readdir match (order-dependent) could be the wrong bundle with a different hash length. Anchor with a trailing dot. Deno kept skipped (separate output difference).
Fixed by the createFakeWorker eval-worker require fix; verified passing on Bun across normal, minimized, and source-map variants, and on Node.
The out-of-bounds call_indirect trap message differs by engine; the test already tolerated several V8 wordings, so add JSC's "Out of bounds call_indirect". Deno kept skipped (separate hard-panic).
The `this` external throws because top-level `this` is undefined in ESM; JSC phrases it "undefined is not an object" vs V8's "Cannot read property...". Accept both in the assertion.
The case passes on Bun now across Node/Web/WebWorker hot suites and on Node; remove the Bun-only filter.
The worker attached parentPort.on after `await import()`; Bun drops a message posted before the listener is attached during async worker startup (Node buffers it). This case tests global Worker resolution, not top-level await, so move the import into the handler and attach the listener synchronously. Deno kept skipped.
Earlier comments claimed the chunk-load promise never resolves; investigation shows it does resolve and the listener does attach. The real cause: Bun drops a worker message posted before the listener is attached when attachment is deferred past the worker's async-module startup (Node/browsers buffer it). Reproduces running the emitted bundle outside jest, so it is not a jest issue.
The logging stats snapshot embedded raw engine stack traces: V8 emits one "at fn (file:line:col)" frame, while JSC (Bun) emits extra internal frames (its LOADER_EXECUTION frame is unnamed so cutOffLoaderExecution can't trim it), omits the function name, and reports different line:col. Normalize trace frames to the in-test-dir file path so the snapshot is engine-independent.
…sal/webworker cases Browsers queue messages posted to a worker until onmessage is assigned; the worker module often sets it only after async (module) startup. The fake Worker delivered via parentPort.on directly, so under Bun a message posted before self.onmessage was set was lost (module workers), and Bun's web-Worker API also double-dispatched. Define self.onmessage as an accessor with a message buffer: this emulates the browser queue and prevents Bun's native double-dispatch. Un-skip worker/universal, css/universal, target/universal-all-module-types, module/issue-17014-webworker, worker/self-import.
Two engine differences broke the blob-URL worker on Bun: (1) the publicPath is derived from self.location by stripping "blob:" and the last path segment, but Bun's URL.createObjectURL returns "blob:<id>" with no path separator (Node: "blob:nodedata:<id>") so the bare id became the publicPath — build the blob location from options.originalURL instead; (2) the worker uses parentPort as a global, which Bun does not surface implicitly — expose self.parentPort. With the earlier onmessage-buffer change this also removes the prior JSC SIGABRT.
webpack correctly externalizes every node builtin for module output here (the
verification loop — all builtins load and every import("…") is emitted — passes
on Bun). The sole failure was `expect(typeof repl.start).toBe("function")`: Bun's
repl module is a stub with no start. Guard that one interactive-builtin check so
it runs where implemented. Deno kept skipped (separate hard-panic).
These workers used a real worker_threads Worker and attached parentPort.on only after `await import(chunk)`. Bun's real MessagePort does not buffer a message that arrives during the worker's async-module startup (Node/browsers do), so the posted message was dropped. Attach the listener synchronously before the await while keeping a top-level await, so the async-module worker output is still exercised. Deno kept skipped.
The ProfilingPlugin inspector concurrency error no longer reproduces on current Bun: the case passes under both --runInBand and --workerThreads (isolated and in the full watch suite). Remove the Bun-only filter.
Investigating the layer segfaults pinpointed the root cause: a vm-loaded ESM module (jest SourceTextModule) that statically imports any node builtin crashes Bun (SIGSEGV/SIGILL), and the less package is ESM importing module/fs/etc. So every less-loader case crashes the whole Bun run, not just one test. These were optimistically un-skipped earlier; restore the Bun skip with the accurate cause, and correct the layer/context comments.
less-loader's default `import("less")` runs through jest's vm and crashes
Bun (SIGSEGV) because a vm-loaded ESM module that statically imports a node
builtin aborts the process. Passing `implementation: require("less")` makes
the loader use the CJS less and skip the dynamic import. The inline
cases/loaders/less-loader case uses a thin wrapper loader to inject it.
The crash is a RELEASE_ASSERT in Bun's NodeVMSourceTextModule::link()
(node:vm), hit only when less-loader runs `import("less")` through jest's vm.
Pass `implementation: require("less")` only under `process.versions.bun` so
Node keeps exercising the default ESM path exactly as before.
…acle webpack's `require(esm)` "module.exports" unwrapping is runtime-agnostic; only the native cross-check (Module._load) needs Node's require(esm), which Bun does not implement. Assert webpack's output against literal values on every runtime and gate the native comparison behind `crossCheckNative` instead of skipping the whole case under Bun.
🦋 Changeset detectedLatest commit: 15e33b4 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 |
|
This PR is packaged and the instant preview is available (10f5fcc). Install it locally:
npm i -D webpack@https://pkg.pr.new/webpack@10f5fcc
yarn add -D webpack@https://pkg.pr.new/webpack@10f5fcc
pnpm add -D webpack@https://pkg.pr.new/webpack@10f5fcc |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #21239 +/- ##
==========================================
+ Coverage 92.77% 92.80% +0.02%
==========================================
Files 591 591
Lines 64488 64565 +77
Branches 17920 17957 +37
==========================================
+ Hits 59829 59918 +89
+ Misses 4659 4647 -12
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:
|
Declare the untyped `less`/`less-loader` modules and type the less-loader wrapper so `tsc` on the test sources passes. Add a ModuleBuildError unit test covering both the V8 (stack includes message) and JSC (frames-only) branches, which also covers the previously-untested lines flagged by codecov.
Removing the Bun skip accidentally dropped the `process.getBuiltinModule` capability check too, so the case ran on Node 12/14/16 (which lack it) and failed with `__webpack_require__.wc is not a constructor`. Keep the gate; Bun has `getBuiltinModule`, so it still runs there.
Merging this PR will improve performance by ×2.9
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ⚡ | Memory | benchmark "lodash", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' |
859 KB | 127.6 KB | ×6.7 |
| ⚡ | Memory | benchmark "wasm-modules-async", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' |
419.4 KB | 330 KB | +27.08% |
Tip
Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.
Comparing claude/bun-ignored-tests-54arh9 (15e33b4) with main (b9323eb)
These un-skips were too optimistic: they pass in the basic ConfigTestCases/cases runs but fail under Bun in secondary suites. Restore the Bun skips for watchCases profiling-plugin (inspector Session), hotCases lazy-compilation/https (no HMR update), cases side-effects/issue-11673 (require undefined in eval'd minified worker), and skip only the filesystem-cache variant of require/esm-module-exports (nondeterministic persistent cache).
…variant Bun only binds CJS `require` for `eval()` in strict mode; only the minified eval-devtool output is sloppy at the top level and reaches the worker_threads external via `require` inside `eval`. Narrow the Bun skip to that one variant so the case runs under Bun everywhere else instead of being skipped entirely.
Bun ignores a runtime `NODE_TLS_REJECT_UNAUTHORIZED`, so the case's self-signed
HTTPS backend was never accepted under Bun's worker threads ("No update
available"). Point the backend at a test-local client that passes
`rejectUnauthorized: false` per request, scoping the bypass to this case, and
drop the Bun skip. Verified on Bun (worker threads, node + async-node) and Node.
…-runInBand and unstable flags) (#21240) * test: un-skip Deno tests that pass; keep only genuinely-unrunnable skips Cut the Deno test skips down to the cases Deno 2.8.3 genuinely cannot run, mirroring the Bun cleanup (#21239). - All 27 test/cases skips pass under Deno; drop their Deno guards. The one real blocker was less-loader's import("less"), which hard-panics Deno's vm, so inject the CJS less (as already done for Bun) and run the case. - Un-skip 13 configCases verified green in the full in-band Deno suite. - Keep skips where Deno hard-panics executing ESM .mjs output via vm.SourceTextModule, and where cases fail/panic only cumulatively late in the in-band run (wasm ESM, worker/worklet, trusted-types/web-worker). * test: run less-loader CSS cases under Deno; re-skip flaky worker cases CSS: six css cases panicked only because less-loader's `import("less")` hard-panics Deno's vm (same root cause as test/cases/loaders/less-loader). Inject the CJS less on Deno too (as already done for Bun) and un-skip them: css-auto, css-loader, import, local-ident-name, prefer-relative-css-import, source-map-export-types. The remaining css skips emit ESM `.mjs` run through node:vm SourceTextModule, which Deno aborts on — document that precisely. Worker: the worker_threads cases un-skipped in the previous commit emit async "Module not found" errors that leak across cases in the in-band Deno run (e.g. worker-public-path's path bleeding into later worker tests), so they are flaky in aggregate though each passes alone. Re-skip blob, custom-worker, issue-17489, node-worker-async-node, web-worker and output/worker-public-path. * test: run nearly all Deno-skipped cases by fixing the import.meta vm panic Root cause of almost every Deno skip: Deno 2.8.3 hard-panics ("Module not found", bindings.rs) the instant `import.meta` is accessed inside a vm.SourceTextModule — no initializeImportMeta shape avoids it. webpack's ESM output reads `import.meta.url` (auto publicPath, createRequire, __dirname), so every ESM/universal/css/html/node case aborted the whole in-band run. - Rewrite `import.meta` to a prepended object in the ESM runner under Deno (test/harness/runner/rewriteImportMeta.js, parsed via acorn so only real meta-properties are touched, never string/comment text). Node/Bun keep the initializeImportMeta callback. - Extend the existing Bun CJS-`less` workaround to Deno for the less-loader cases (css-*, layer/*), whose `import("less")` also panics Deno's vm. - Give worker/worker-self-reference-global a findBundle (its [name].js output never matched the default bundle0.js; it only runs where a global Worker exists, i.e. never on Node). - Swallow a fake worker's late post-terminate error so it can't fail a later case. This un-skips ~100 cases. The remaining Deno skips are the few web worker_threads cases whose in-worker chunk load rejects asynchronously with a blob:/data:/custom-publicPath URL Deno can't map, leaking into a later case. * test: run the Deno test suite with jest's parallel workers The import.meta vm fix removed the module-load panic that forced Deno to --runInBand, so run Deno with parallel workers (matching Bun's --workerIdleMemoryLimit=512MB recycling). Verified green: basictest 15735 passed, test+unittest 52953 passed, no panic. HotTestCases stay excluded for Deno (their async cache/compilation tails outlive a suite's teardown under Deno's event-loop timing); update the CI note accordingly. * test: drop the unstable Deno flags, no longer needed --unstable-node-globals, --unstable-bare-node-builtins and --unstable-detect-cjs were required when the Deno CI was first added; Deno 2.8.3 makes them default/no-ops for this workload (CJS require resolves bare node builtins, Node globals are present, CJS is auto-detected). The full test:deno scope passes without them: basictest 15735, longtest 15554, test+unittest 52953 — zero failures from removing each flag or all three. * test: un-skip all Deno worker cases by overriding the worker self.location A Deno worker's globalThis.location is a read-only "data:text/javascript," (the eval-worker URL), so createFakeWorker's `self.location = new URL(...)` was silently ignored and webpack derived a bogus "data:text/" publicPath, making in-worker chunk loads miss and leak an uncaught error into a later case. Use Object.defineProperty to actually override it (Node/Bun accept either). Also handle the expected worker load error in output/worker-public-path (its worker URL is the intentionally-fake workerPublicPath). This un-skips the last 8 Deno-skipped cases: worker/{blob,custom-worker, issue-17489,self-import,web-worker,worklet}, trusted-types/web-worker and output/worker-public-path. The eval-worker bootstrap is CJS and Deno's node-ESM output uses bare builtins, so restore --unstable-detect-cjs and --unstable-bare-node-builtins (--unstable-node-globals stays dropped). * test: replace the Deno --unstable-bare-node-builtins flag with an import map webpack's node-target ESM output imports builtins bare (e.g. `import "fs"`), which Deno resolves only with --unstable-bare-node-builtins. Map every bare node builtin to its `node:` specifier via an import map instead, dropping that flag. --unstable-detect-cjs stays: Deno needs content-based CJS detection for webpack's mixed `.js` output (a forced package.json "type":"commonjs" breaks ESM-syntax `.js` library outputs). Verified: basictest 15867 passed, 0 failures. * docs: ask for a short Use-of-AI sentence in templates and commit descriptions Require the Use-of-AI disclosure to be one or two short sentences wherever it appears — issue templates, the PR template, and the commit description. * docs: make the compact-by-default rule general, not AI-only Keep every issue/PR template section and the commit description body compact by default, expanding only when the task's complexity needs it. * test: drop the last Deno unstable flag via a worker_threads shim Route Deno's eval-worker bootstraps and CJS `.js` worker entries through temporary `.cjs` files in a worker_threads monkeypatch, so the test suite runs with zero unstable flags (was --unstable-detect-cjs). * docs: keep the compact-by-default rule general, drop AI-specific wording * test: de-flake nested async-modules runtime-performance budget The assertion used a 100ms wall-clock budget; its only real failure mode is an exponential async-DAG regression that hangs, so a loaded CI runner could starve microtasks past 100ms and flake. Raise the budget to 2000ms, which still catches the hang.
Summary
webpack runs its test suite under Bun (and Deno) in CI, but a growing number of cases were turned off per-case via
test.filter.jsto work around Bun runtime quirks. This PR removes ~33 of those Bun skips by fixing the underlying cause on webpack's / the test harness's side wherever possible, so the Bun job runs almost the whole suite.A handful of cases remain skipped under Bun because they exercise things Bun genuinely doesn't support yet (kept skipped, with precise TODO comments):
ProfilingPlugin(node:inspectorSession), lazy-compilation over HTTPS (HMR never delivers the update), an eval'd minified worker chunk (requireundefined),require(esm)"module.exports"interop, and the persistent-cache variant of that case.Highlights of the fixes:
Workeruntilonmessageis set and fix blob-URL handling (test/helpers/createFakeWorker.js); anode:vmdynamic-import preload shim for Bun (test/bun-preload.js); deterministicreaddirordering in a few CSS/hash snapshot configs; normalize JSC error/trap phrasing in stats and array-expectation checks.awaitso messages are not missed during async startup.implementation: require("less")so less-loader skipsimport("less"), which crashes Bun'snode:vmSourceTextModule.link(); Node keeps the default ESM path.require/esm-module-exports: assert webpack'srequire(esm)unwrapping against literal values on every runtime and gate only the native cross-check.Error.stackomits the message line, as JSC does (lib/errors/ModuleBuildError.js, with changeset + unit test).No open issue; this is CI/runtime-coverage hardening.
What kind of change does this PR introduce?
Mostly
test(enabling the Bun suite); includes one smallfixinlib/errors/ModuleBuildError.js.Did you add tests for your changes?
Yes — the change is largely test enablement (~33 previously-skipped cases now run under Bun), plus a new
test/ModuleBuildError.unittest.jscovering the V8 and JSC stack-message branches. Verified under Bun (worker-threads, full suite), Node, and Deno.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
Use of AI
Yes. I used Claude Code to investigate the Bun/JSC runtime failures (including isolating a
node:vmSourceTextModule.link()crash to a minimal reproduction), implement the fixes, and verify the suite across Bun, Node, and Deno.