Skip to content

Key the ES module registry on the host-defined import attribute type - #32999

Draft
robobun wants to merge 1 commit into
mainfrom
farm/5cc6cbfd/import-attribute-module-key
Draft

Key the ES module registry on the host-defined import attribute type#32999
robobun wants to merge 1 commit into
mainfrom
farm/5cc6cbfd/import-attribute-module-key

Conversation

@robobun

@robobun robobun commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

Importing the same path with two different with { type } attributes returned one shared module, and whichever import evaluated first decided what every other attribute form got. The import-attributes proposal requires the module registry to be keyed on (specifier, attributes): different attributes are different modules.

Fixes #19834

Important

Blocked on oven-sh/WebKit#258. The fix is in JavaScriptCore's module map key. WEBKIT_VERSION here points at #258's preview build artifact (autobuild-preview-pr-258-a024b4c8, built on current oven-sh/WebKit main) so CI runs the new tests against the fixed JSC. Once #258 lands I will update it to the merge commit; please do not merge while it still points at the preview tag.

Reproduction

// data.txt contains "hello\n"
const a = await import("./data.txt", { with: { type: "text" } });
const b = await import("./data.txt", { with: { type: "file" } });
console.log(a.default, b.default, a === b);
// bun:      "hello\n" "hello\n" true   (reverse the order: both become the path)
// expected: "hello\n" "/abs/data.txt" false

Deterministic on the released bun and on main. #19834 is the HTML form of the same bug (type: "html" vs type: "file" on one .html file share a module on main).

Cause

JSC's ModuleMapKey is (UniquedStringImpl* specifier, ScriptFetchParameters::Type). ScriptFetchParameters::parseType maps every non-json / non-webassembly attribute value onto the single Type::HostDefined enum value, so with { type: "text" } and with { type: "file" } hash to the same registry bucket. The string that distinguishes them (ScriptFetchParameters::m_hostDefinedImportType) never reached the key.

There is no Bun-side seam: moduleLoaderResolve (which produces the key's specifier component) does not receive the attributes, and the cache hit in JSModuleLoader::loadModule happens before moduleLoaderFetch is ever called for the second import.

On #19834 specifically: the exact snippet in its body (a bare import("./index.html") vs import("./index.html", { with: { type: "file" } })) already behaves correctly on main, because an attribute-less import keys on Type::JavaScript while an attributed one keys on Type::HostDefined, and that two-value key was added to the WebKit fork after the issue was filed on 1.2.13. What the issue's title reports, two different with { type } values sharing one cache entry, is exactly what was still broken and what this PR fixes. Both forms are covered by the new tests.

Fix

oven-sh/WebKit#258 widens the key to (specifier, Type, hostDefinedImportType) and routes every key construction through one helper, including the parse-time requested-modules dedup and the static-binding resolution that oven-sh/WebKit#403 moved onto the same key. On the Bun side this PR is:

  • scripts/build/deps/webkit.ts: the WEBKIT_VERSION bump.
  • src/jsc/bindings/NodeVMSourceTextModule.cpp: the one Bun-side ModuleMapKey { a, b } construction moves to the new ModuleRequest::moduleMapKey() helper. The key grew a third component, so the two-argument form no longer compiles; that is intentional, so no call site can silently drop the attribute.
  • src/jsc/bindings/InspectorLifecycleAgent.cpp, src/jsc/bindings/ZigGlobalObject.cpp: two diagnostic iterations over the module map read the key's specifier via .first; they now use std::get<0>. No behavior change.
  • test/js/bun/resolve/import-attributes.test.ts: the regression tests. Six cover dynamic import (both orders, three distinct types on one path, same-attribute dedup, and the Importing HTML bundle with different with { type } are cached incorrectly #19834 HTML case in both orders); the seventh covers static imports and a re-export with host-defined attributes, which exercise the binding-resolution path Implement a Error.captureStackTrace polyfill #258 had to extend after Support for completion in Bash #403 (it is a guard for that path, not a fail-before case).

Verification

Released Bun (1.3.14), bun test test/js/bun/resolve/import-attributes.test.ts: 1 pass, 6 fail. The six dynamic-import tests fail with exactly the bug (file and base64 receive the text contents; the .html imported with { type: "file" } receives the HTMLBundle); the static-binding guard passes because a single attributed import per path never collided.

bun bd test test/js/bun/resolve/import-attributes.test.ts against the #258 artifact: 7 pass, 0 fail.

Also against the #258 artifact: import-empty, import-defer, import-query, esModule, regression/issue/16476 (48 tests), plus text-loader, jsonc, json5, yaml, toml, the attribute regressions 22656 / 26360 / 28042 / 30717 / 31575, and the node:vm module tests (337 tests). The only failure is text-loader's "reloaded 10000 times" stress case, which fails identically on a main debug+ASAN build in the same container (it times out at 5s; the fixture takes ~15s under ASAN); interleaved 5-run averages of that fixture are 15.48s on main vs 15.46s on this branch, so it is not affected by this change.

A note on fail-before: the load-bearing change lives in vendor/WebKit (a separate repository) plus scripts/build/deps/webkit.ts; the only src/ edits are compile fixes for the new WebKit API. Stashing src/ therefore does not reproduce the bug, it just breaks the build against the new WebKit. The released-Bun run above is the fail-before proof.

What is still not covered

Two narrower siblings remain, each its own PR:

  1. Two static imports of the same path with different host-defined types in one module still bind to the first one. After Support for completion in Bash #403 and Implement a Error.captureStackTrace polyfill #258, the loader holds both modules, but import/export entries record only the attribute's Type, so Implement a Error.captureStackTrace polyfill #258 resolves a HostDefined binding to the module's first request for that specifier and type. Finishing it means carrying the attribute string through ImportEntry / ExportEntry / the star-export set and the seven JSC_JSModuleRecord__add* FFI entry points in src/jsc/bindings/BunAnalyzeTranspiledModule.cpp plus their Rust callers. Dynamic imports, and static imports of different paths or of one path with one attribute, are fully correct now.

  2. The bundler's module graph. PathToSourceIndexMap keys on path.text only; the attribute loader is computed right next to each lookup (bundle_v2.rs:6390) and then discarded, so bun build of the two-import file also produces one module, order dependent. A naive (path, loader) composite key is not safe: the map has 20+ consumers and four of them genuinely need a bare-path lookup.

Bundler map consumer audit (why that is its own PR)

Bare-path dependents that a composite key would break:

  • src/runtime/bake/dev_server/incremental_graph.rs:1620: on_file_deleted(abs_path) calls map.remove(abs_path) for every target map with the watcher's bare absolute path. A composite key would silently miss every (path, loader) entry and the dev server would serve stale source indices for deleted-then-recreated files.
  • src/bundler/bundle_v2.rs:2494: the dev-server invalidation re-enqueue does a bare-path get(path_slice).is_some() membership check (no import record, no attributes in scope). A miss duplicates parse work and source indices in watch mode.
  • src/bundler/LinkerContext.rs:692: looks up an HTML import's own source path and panic!s on a miss, so the key derivation must be bit-identical between its insert site (bundle_v2.rs:6509) and this read.
  • src/bundler/bundle_v2.rs:2107: the dual-package-hazard rewrite looks up a resolver secondary_path, whose effective loader is not the current record's.

On top of that, three sites overwrite an existing key in place (the CJS re-export shim at bundle_v2.rs:6679, the HTML manifest alias at :6771, the "use client" proxy at :7149), so any key function has to be deterministic and identical between the original insert and the overwrite. The shape that works is to keep the bare path.text key for every import whose effective loader matches the path's extension-derived one (every existing consumer is in that set) and only suffix the key when a with { type } attribute forces a different loader, plus a suffix sweep in on_file_deleted. That is roughly 18 touch points across bundle_v2.rs, PathToSourceIndexMap.rs, barrel_imports.rs, LinkerContext.rs, and the incremental graph, and deserves its own PR with watch-mode coverage rather than riding on a WebKit version bump. Line numbers are as of the original audit and may have drifted.


[decide:webkit] gate passed · iteration 10 · 5 files touched

fails on main (without fix)
ASAN without fix: BUILD FAILED (no junit output)
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/resolve/import-attributes.test.ts
ninja: Entering directory `/workspace/bun/build/debug'
[1/28] gen cpp.rs (cppbind)
[2/28] gen generated_host_exports.rs
generated_host_exports.rs: 93 exports (host=3, lazy=10, generic=80, rust=0); 239 extern-C blocks audited
[2/28] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

[11/28] cxx obj/unified/UnifiedSource-src_jsc_bindings-6.cpp.o
FAILED: obj/unified/UnifiedSource-src_jsc_bindings-6.cpp.o 
/usr/bin/ccache /usr/lib/llvm-21/bin/clang++ -march=nehalem -O0 -glldb -g3 -gz=zstd -fno-standalone-debug -fsanitize=address -fno-exceptions -fno-c++-static-destructors -fno-rtti -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -fvisibility=hidden -fvisibility-inlines-hidden -fno-unwind-tables -fno-asynchronous-unwind-tables -Wno-c23-extensions -ffunction-sections -fdata-sections -faddrsig -fno-semantic-interposition -fno-delete-null-pointer-checks -fdiagnostics-color=a
... (truncated)

release without fix: 6 FAILED
bun test v1.4.0-canary.1 (da3851e57)

test/js/bun/resolve/import-attributes.test.ts:
52 |         sameNamespace: a === b,
53 |       }));
54 |     `,
55 |     });
56 |     const { abs, ...rest } = out;
57 |     expect(rest).toEqual({ text: "hello\n", file: abs, sameNamespace: false });
                      ^
error: expect(received).toEqual(expected)

  {
-   "file": "/tmp/import-attr-key_z1TXSO/data.txt",
-   "sameNamespace": false,
+   "file": 
+ "hello
+ "
+ ,
+   "sameNamespace": true,
    "text": 
  "hello
  "
  ,
  }

- Expected  - 2
+ Received  + 5

      at <anonymous> (/workspace/bun/test/js/bun/resolve/import-attributes.test.ts:57:18)
(fail) dynamic import of one path with type text then type file yields two distinct modules [18.31ms]
52 |         sameNamespace: a === b,
53 |       }));
54 |     `,
55 |     });
56 |     const { abs, ...rest } = out;
57 |     expect(rest).toEqual({ text: "hello\n", file: abs, sameNamespace: false });
                      ^
error: expect(received).toEqual(expected)

  {
    "file": "/tmp/import-attr-key_Iqcj22/data.txt",
-   "sameNamespace": false,
-   "text": 
- "hello
- "
- ,
+   "sameNamespace": true,
+   "text": "/tmp/i
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/resolve/import-attributes.test.ts
bun test v1.4.0 (29a818ff3)

test/js/bun/resolve/import-attributes.test.ts:
(pass) dynamic import of one path with type text then type file yields two distinct modules [747.22ms]
(pass) dynamic import of one path with type file then type text yields two distinct modules [606.37ms]
(pass) repeating the same attribute still resolves to one shared module [349.69ms]
(pass) three different attribute types on one path yield three distinct module namespaces [370.84ms]
(pass) an .html imported with no attribute, type html, and type file yields three modules [505.16ms]
(pass) an .html imported with no attribute, type file, and type html yields three modules [507.40ms]
(pass) static imports and re-exports with host-defined attributes link to the right modules [507.78ms]

 7 pass
 0 fail
 7 expect() calls
Ran 7 tests across 1 file. [8.11s]
__F:0:S:0

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped)
  target       linux-x64-gnu
  build type   Release
  build dir    ./build/release
  revision     29a818ff39
  features     baseline

22 deps, 107 codegen, 1176 objects in 1111ms

ninja: Entering directory `/workspace/bun/build/release'
[1/1238] gen ErrorCode+*.h
[2/1238] gen bindgenv2
[3/1238] gen JSBuffer.lut.h
Generating /workspace/bun/build/release/codegen/JSBuffer.lut.h from /workspace/bun/src/jsc/bindings/JSBuffer.cpp
[4/1238] fetch zlib
[zlib] up to date
[5/1238] fetch libjpeg-turbo
[libjpeg-turbo] up to date
[6/1238] gen ProcessBindingConstants.lut.h
Generating /workspace/bun/build/release/codegen/ProcessBindingConstants.lut.h from /workspace/bun/src/jsc/bindings/ProcessBindingConstants.cpp
[7/1238] gen .bind.ts → GeneratedBindings.cpp
[8/1238] fetch tinycc
[tinycc] up to date
[9/1237] subst deps/zlib/zlib.h
[10/1237] gen ProcessBindingBuffer.lut.h
Generating /workspace/bun/build/release/codegen/ProcessBindingBuffer.lut.h from /workspace/bun/src/jsc/bindings/ProcessBindingBuffer.cpp
[11/1237] gen ProcessBindingHTTPParser.lut.h
Generating /workspace/bun/build/release/codege
... (truncated)
diff hotspot
scripts/build/deps/webkit.ts                  |   2 +-
 src/jsc/bindings/InspectorLifecycleAgent.cpp  |   4 +-
 src/jsc/bindings/NodeVMSourceTextModule.cpp   |   2 +-
 src/jsc/bindings/ZigGlobalObject.cpp          |   5 +-
 test/js/bun/resolve/import-attributes.test.ts | 178 ++++++++++++++++++++++++++
 5 files changed, 185 insertions(+), 6 deletions(-)

gate history · 3 passed · 0 rejected · iteration 10

evidence per changed file
file                                           reads  edits  tests
scripts/build/deps/webkit.ts                       4      6     13
src/jsc/bindings/InspectorLifecycleAgent.cpp       1      1     13
src/jsc/bindings/NodeVMSourceTextModule.cpp        1      1     13
src/jsc/bindings/ZigGlobalObject.cpp               2      1     13
test/js/bun/resolve/import-attributes.test.ts      2      7     13

@robobun

robobun commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 9:33 PM PT - Aug 12th, 2026

@robobun, your commit 29a818f has some failures in Build #93754 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 32999

That installs a local version of the PR into your bun-32999 executable, so you can run:

bun-32999 --bun

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. Importing HTML bundle with different with { type } are cached incorrectly #19834 - Reports the exact same bug: importing the same HTML file with different with { type } attributes returns the same cached module due to the registry not keying on the attribute type

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #19834

🤖 Generated with Claude Code

@robobun
robobun force-pushed the farm/5cc6cbfd/import-attribute-module-key branch from adb530c to ff51a6c Compare June 28, 2026 06:06
@robobun

robobun commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator Author

Verified: #19834 is the same bug and this PR fixes it. Added Fixes #19834 to the description and its HTML reproduction to the test.

Two details worth stating precisely, since the issue is from 1.2.13:

  • The exact snippet in Importing HTML bundle with different with { type } are cached incorrectly #19834's body (a bare import("./index.html") versus import("./index.html", { with: { type: "file" } })) already behaves correctly on main. An attribute-less import keys on ScriptFetchParameters::Type::JavaScript and an attributed one on Type::HostDefined, and that two-value registry key landed in the WebKit fork after 1.2.13.
  • What the issue's title reports, two different with { type } values on one path sharing a cache entry, was still broken on main and is what this PR fixes. Reproduced on the released bun: import("./index.html", { with: { type: "html" } }) followed by import("./index.html", { with: { type: "file" } }) returns the same HTMLBundle for both (and the path string for both if the order is reversed).

The new test covers both: the bare-vs-attributed pair from the issue as a regression guard, and the attributed-vs-attributed pair that this PR changes, in both import orders. USE_SYSTEM_BUN=1 bun test test/js/bun/resolve/import-attributes.test.ts is 0 pass, 6 fail; against the oven-sh/WebKit#258 preview artifact it is 6 pass, 0 fail.

@robobun

robobun commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator Author

CI status for ff51a6c1: 282 passed, 2 failed, 2 expired out of 286 jobs. None of the non-green jobs are a test or build failure (https://buildkite.com/bun/bun/builds/66233):

  • The 2 failures are both :darwin: 26 aarch64 - test-bun shards that hit the same infrastructure error before running a single test:

    Error: buildkite-agent artifact download timed out after 120s for step 'darwin-aarch64-build-bun'. Refusing to continue with a partial download

    Their build step, darwin-aarch64-build-bun, passed; the shards timed out pulling the already-built binary from Buildkite artifact storage.

  • The 2 expired jobs are :darwin: 14 aarch64 - test-bun shards that never got an agent before the build finished.

Every build-cpp and build-bun step passed on every platform and variant (darwin, Linux glibc/musl/asan/baseline, Android, FreeBSD, Windows), so the autobuild-preview-pr-258-adc8aebb WebKit artifact resolves and links across the whole matrix. Format, Lint JavaScript, and cargo clippy are green.

I am deliberately not pushing a ci: retrigger for the two darwin flakes. This PR is a draft that cannot merge until oven-sh/WebKit#258 lands, and retargeting WEBKIT_VERSION to that merge commit will re-run CI anyway, so a re-roll now would repeat 286 jobs for no benefit. The diff is green; the only thing it is waiting on is oven-sh/WebKit#258.

@robobun

robobun commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

The original fuzz report for this bug was a concurrent variant that this test suite does not cover: two import()s of one path in flight at once, where one attribute's loader fails and the other's succeeds. Without the key fix the failing loader's rejection lands on the entry the succeeding loader is mid-fetch on, and a later, unrelated import() of the path with the succeeding attribute permanently rejects with the wrong loader's BuildMessage. I reproduced it 5/5 on 1.4.0. On an asserts build it also trips ASSERTION FAILED: m_status == Status::Fetching in ModuleRegistryEntry::fetchComplete.

This passes on a build of oven-sh/WebKit#258 alone (I verified it locally from source), so it belongs with these tests. Suggested addition, adapted to the run helper already in this file:

// The original report: two concurrent import()s of one path where one
// loader fails. The failing loader's rejection must stay on its own
// registry entry: it must neither settle the other attribute's in-flight
// fetch nor poison a later, unrelated import of the path with the
// succeeding attribute.
test("a concurrently failing loader does not poison another attribute's entry", async () => {
  const out = await run("import-attr-poison", {
    // "=" alone is a TOML parse error but loads fine as text.
    "data.toml": "=\n",
    "index.ts": /* ts */ `
      const p = import.meta.dir + "/data.toml";
      const [toml, text] = await Promise.allSettled([
        import(p, { with: { type: "toml" } }),
        import(p, { with: { type: "text" } }),
      ]);
      const later = await import(p, { with: { type: "text" } });
      console.log(JSON.stringify({
        toml: toml.status === "rejected" ? toml.reason?.name : toml.status,
        text: text.status === "rejected" ? text.reason?.name : text.value.default,
        later: later.default,
      }));
    `,
  });
  expect(out).toEqual({ toml: "BuildMessage", text: "=\n", later: "=\n" });
});

On released Bun this fails with later absent (the child throws, exits nonzero); with this PR's pinned WebKit it passes.

Unrelated to the key fix but found in the same rejected branch: moduleLoadTopSettled mis-records the toml entry's BuildMessage as an evaluation error instead of a fetch error because a BuildMessage is not an ErrorInstance, which strands every later importer of that key that goes through hostLoadImportedModule. That is a separate bug with its own WebKit PR (oven-sh/WebKit#262) and Bun PR; it does not affect this test.

robobun added a commit that referenced this pull request Jun 30, 2026
test/CLAUDE.md prefers test.concurrent when multiple independent tests in
one file spawn processes; both tests here use their own tempDir and share
no state.

sync-webkit-source did `git checkout main && git pull && git checkout
WEBKIT_VERSION`, which cannot resolve an autobuild-preview-* release tag:
the tagged commit lives on a PR branch, not main, so the plain pull never
fetches it. Add a `git fetch --tags origin` so the checkout works while a
preview tag is pinned (here and in #32999). No behavior change for the
normal 40-hex SHA pin.
@robobun
robobun force-pushed the farm/5cc6cbfd/import-attribute-module-key branch from ff51a6c to 1eb210c Compare July 1, 2026 10:11
@robobun

robobun commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

CI status for 1eb210ce (the rebase onto main plus the new oven-sh/WebKit#258 preview artifact): 283 passed, 3 failed (https://buildkite.com/bun/bun/builds/67542). None of the 3 is a test or build failure in this PR's changes:

  1. :darwin: 26 aarch64 - test-bun: the same infrastructure error as the previous build, on the same agent host (darwin-aarch64-26-3-1-1):

    Error: buildkite-agent artifact download timed out after 120s for step 'darwin-aarch64-build-bun'. Refusing to continue with a partial download

    The darwin-aarch64-build-bun step itself passed; the shard timed out pulling the binary from Buildkite artifact storage before running a single test. This is the third consecutive build on which that specific runner host's artifact download times out.

  2. :alpine: 3.23 x64 - test-bun and :alpine: 3.23 x64-baseline - test-bun: both failed the same test, test/js/node/test/parallel/test-net-connect-memleak.js, a "GC collected the socket after globalThis.gc()" assertion (collected came back false). This is a pre-existing flake, not a regression from this PR:

    • the test has already been deflaked twice in-repo (2914a9643c "deflake test-net-connect-memleak.js", 583c97bdcd "another")
    • the same test passed on every glibc lane (x64, x64-baseline, aarch64, and all ~20 x64-asan shards) and on alpine aarch64 with this PR's WebKit; a real C++ regression would fail on every architecture, not only alpine x64
    • it does not fail once across 40 local runs on a released Bun with no WebKit change at all
    • the test uses no import attributes, so it only exercises this PR's non-HostDefined code path, which is behavior-preserving by construction

Every build-cpp and build-bun step passed on every platform and variant against the rebased preview artifact (autobuild-preview-pr-258-9435ca11), and all ~20 x64-asan test shards (the closest CI lane to the debug + ASAN build I verified locally) are green.

As before I am not pushing a ci: retrigger: this PR is a draft that cannot merge until oven-sh/WebKit#258 lands, and retargeting WEBKIT_VERSION to that merge commit will re-run CI anyway. The diff is green; the only thing it is waiting on is oven-sh/WebKit#258.

@robobun
robobun force-pushed the farm/5cc6cbfd/import-attribute-module-key branch 2 times, most recently from 5e8ef39 to 16aae77 Compare July 14, 2026 09:46
@robobun

robobun commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator Author

The two test/napi/napi.test.ts and test/napi/napi-value-ffi.test.ts failures on build 72861 were a break at this PR's rebase base, not in this diff. test/napi/napi-app/js_test_helpers.cpp at c71f98ba58 (the commit I had rebased onto) defines static napi_value get_all_property_names(...) twice, at lines 255 and 307, and registers it twice at lines 511 and 515, so node-gyp fails to compile the fixture. This diff does not touch test/napi/. The duplicate was already removed on main by #34129 / #34133:

$ git show c71f98ba58:test/napi/napi-app/js_test_helpers.cpp | grep -n 'static napi_value get_all_property_names'
255:static napi_value get_all_property_names(const Napi::CallbackInfo &info) {
307:static napi_value get_all_property_names(const Napi::CallbackInfo &info) {
$ git show origin/main:test/napi/napi-app/js_test_helpers.cpp | grep -n 'static napi_value get_all_property_names'
290:static napi_value get_all_property_names(const Napi::CallbackInfo &info) {

Rebased onto cc0c1e8355 (clean, no conflicts; none of this PR's five files changed in the intervening range, and WEBKIT_VERSION is still 4895f45d so the autobuild-preview-pr-258-41bae541 artifact is still the right one). Re-verified end to end: 6/6 on bun bd against the preview artifact, 0/6 on a released Bun (1.3.14+0d9b296af), and 48 existing module-loader tests still pass. Pushed as 16aae77596.

Still blocked on oven-sh/WebKit#258; once that lands I will retarget WEBKIT_VERSION to the merge commit and undraft.

@robobun

robobun commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator Author

CI status for 16aae775: 279 passed, 3 failed, 2 timed_out plus 2 darwin-14-aarch64 shards still waiting on an agent (https://buildkite.com/bun/bun/builds/72877). None of the five non-green jobs is a test or build failure in this PR's changes; binary size is +0.0 KB on 12 of 15 platforms (+1 to 2 KB on the 3 Windows variants). Per failure:

  • test/js/node/test/parallel/test-net-connect-memleak.js on alpine 3.23 x64 + x64-baseline: the same GC-collected-after-gc() assertion that hit build 67542. Already deflaked twice in-repo (2914a9643c, 583c97bdcd), passed on every glibc lane and on alpine aarch64, and 0/60 failures running it on this PR's debug+ASAN build locally (which has this PR's WebKit) plus 0/40 on a released Bun. Reported via the main-break harness; another session already owns it.
  • test/js/node/test/parallel/test-worker-message-port-transfer-terminate.js on one x64-asan shard: SIGABRT on ASSERTION FAILED: !scope.exception() || !result in JSObject::getOwnPropertyDescriptor. This diff does not touch JSObject.cpp, workers, or MessagePort. 0/20 local repro on this PR's debug+ASAN build; the test is on test/no-validate-leaksan.txt; and node:worker_threads: +48 Node.js tests passing — MessagePort, stdio, SHARE_ENV, exit codes, transfer semantics, postMessageToThread + inspector #31216 (on main) restructured MessagePort.prototype, which is exactly the getOwnPropertyDescriptor surface a termination race would hit. Reported via the main-break harness; another session already owns it.
  • Four test/bake/* dev-server tests (deinitialization, dev-and-prod, dev/css, dev/esm), all timing out at 60s on ONE darwin-14-x64 shard (darwin-x64-mini-2-1) with ConnectionRefused / "websocket 0 failed before open" / "killed 1 dangling process". One darwin box with networking issues, and the same tests were YELLOW (passed on retry) in earlier builds' flaky annotations.
  • linux x64-baseline verify-baseline timed out: the baseline binary sanity step, not a test.

Every build-cpp and build-bun step passed on every platform against the autobuild-preview-pr-258-41bae541 artifact, and all ~20 x64-asan shards are green apart from the one worker-terminate race above.

This is the fourth consecutive CI result for this diff where every non-green job is unrelated flake or infra (66233: darwin artifact-download timeouts; 67542: alpine memleak flake + darwin artifact timeout; 72861: a main break in test/napi/napi-app/js_test_helpers.cpp that main had already fixed, picked up by rebasing; 72877: the above). I am still not pushing a ci: retrigger: this draft cannot merge until oven-sh/WebKit#258 lands, and retargeting WEBKIT_VERSION to that merge commit will re-run CI anyway.

Importing the same path with two different `with { type }` attributes
returned one shared module: JSC's ModuleMapKey was
(specifier, ScriptFetchParameters::Type), and every Bun-defined attribute
value ("text", "file", "toml", "html", ...) maps onto the single
Type::HostDefined enum value, so whichever import evaluated first decided
what both callers got.

The key now also carries the host-defined attribute string. The fix lives
in oven-sh/WebKit (oven-sh/WebKit#258); this commit
bumps WEBKIT_VERSION, updates Bun's three ModuleMapKey users to the widened
key (NodeVMSourceTextModule uses the new ModuleRequest::moduleMapKey()
helper; two diagnostic iterations switch from .first to std::get<0>), and
adds the regression test, including the HTML reproduction from issue 19834.

Fixes #19834
@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Status check against current main (f426a8e), since #19834 was closed as fixed in the meantime.

The closing comment on #19834 verified the literal snippet from that issue (a bare import("./index.html") vs one with { type: "file" }), which the description above already notes works on main because an attribute-less import keys on a different ScriptFetchParameters::Type. The case this PR is about, two different with { type } values on one path, still reproduces on main.

I copied this PR's test/js/bun/resolve/import-attributes.test.ts onto main and ran it: 0 of 6 pass. In every case the second attribute form gets the first form's module (type: "html" then type: "file" on one .html returns the HTMLBundle for both, and the reverse order returns the path for both; text/file/base64 on one data.txt all collapse to whichever was imported first), while the bare-vs-attributed assertion in the same tests passes, consistent with the #19834 closure. Source/JavaScriptCore/runtime/ModuleMap.h at the WebKit commit main currently pins (7b763944) still has the two-component ModuleMapKey, and oven-sh/WebKit#258 is still open.

So this is not covered by main; leaving it open. It remains blocked on oven-sh/WebKit#258 and will need the WEBKIT_VERSION bump redone against whatever that lands as (the branch currently conflicts with main).

@robobun
robobun force-pushed the farm/5cc6cbfd/import-attribute-module-key branch from 16aae77 to 29a818f Compare August 13, 2026 00:31
@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

CI status for 29a818ff (rebased onto current main, pinned to the oven-sh/WebKit#258 a024b4c8 artifact): 177 passed, 0 failed (https://buildkite.com/bun/bun/builds/93754). There are no failing tests or build steps in this build; it shows as failing only because of darwin queue capacity:

  • 2 :darwin: 26 aarch64 - test-bun shards were canceled before ever starting (started_at null, no agent, empty log). They require release-tier=latest arm64 agents, and the Buildkite agents API currently shows exactly two such agent slots in the whole fleet (both on darwin-aarch64-26.6.1-1, both busy).
  • 2 :darwin: 14 aarch64 - test-bun shards are still queued behind the 8 release-tier=previous arm64 slots, all of which are busy.
  • The one annotation is a yellow flake (test/cli/install/migration/complex-workspace.test.ts, passed on retry) on a test this diff does not touch.

Every build step passed on every platform against the preview artifact, all x64-asan test shards are green, and binary size is +0.0 KB on every platform that has a main comparison.

Not pushing a retrigger: it would re-enter the same darwin queue, and this is still a draft blocked on oven-sh/WebKit#258 (retargeting WEBKIT_VERSION to its merge commit will re-run CI anyway).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Importing HTML bundle with different with { type } are cached incorrectly

1 participant