Skip to content

feat(bun): unlock Phase C selective plugin reload via bun#39426 - #2

Open
ebowwa wants to merge 6 commits into
feat/bun-compatfrom
feat/bun-selective-reload
Open

feat(bun): unlock Phase C selective plugin reload via bun#39426#2
ebowwa wants to merge 6 commits into
feat/bun-compatfrom
feat/bun-selective-reload

Conversation

@ebowwa

@ebowwa ebowwa commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Summary

Unlocks Phase C — selective plugin reload on Bun — using the public-API primitive fixed by our upstream PR oven-sh/bun#39426 ("keep a file:// URL's query in module keys", fixes #21346).

Before #39426, import(url + "?v=N") returned the same cached module instance for every query on Bun, so per-module reload via public APIs was impossible — the reason selective HMR was deferred to Phase C in docs/BUN_COMPATIBILITY.md. @cordisjs/plugin-hmr gets this capability on Node only through --expose-internals module-loader internals, which Bun does not expose.

With the fix, the whole primitive is two public calls:

const mod = await import(`${pathToFileURL(pluginPath).href}?gen=${n}`) // fresh instance
const fiber = await root.plugin(mod)   // activate under the root
await previous.dispose()               // graceful swap — old disposers run,
                                       // old timers stop, root keeps running

What's included

  • tests/bun/selective-reload.spec.ts (+ fixtures/selective-reload-driver.ts) — asserts the full swap contract from the child process transcript:
    • fresh module instance per ?gen=N (captured=reloaded proves a new evaluation)
    • old plugin's fiber disposed only after the new one applied; its timers never fire again
    • root-owned effects tick through the swap unduplicated, disposed exactly once at shutdown
  • tests/bun/repros/selective-reload.ts — manual narrative version (README updated)
  • Docs — Phase C section in BUN_COMPATIBILITY.md rewritten from "deferred" to "primitive done" (remaining production gaps honestly listed: fs-watch → generations, config preservation, rollback); Phase 10 recorded in BUN_PORT_STATUS.md

Capability gating (not version gating)

The spec probes the runner binary for query-busting behavior and skips cleanly when absent — so:

  • CI today: skip (stock Bun 1.3.14 lacks the fix) — no red, no green lie
  • The moment a shipped Bun includes #39426, the test activates automatically
  • Same gating pattern as the existing hot.spec.ts (#32856-gated)

Test plan

  • bun test tests/bun/selective-reload.spec.ts against a release build of the PR branch (1.4.0-canary.1+c16333e9e) — 1 pass / 14 expects (transcript-ordering assertions); also verified against the debug build
  • Same spec on stock Bun (no capable binary) — clean skip
  • Full suite with a capable build present — 69/69 (68 + this test)
  • Real-app smoke on the release binary: bin.bun.js boots Cordis, loads a plugin, SIGINT disposes the root, exit 0
  • Runner detection: BUN_QUERY_BUSTING_BIN (now resolve()d — the driver child runs from a temp cwd) → .upstream/bun/build/release/bunbuild/debug/bun-debug → skip
  • Full Cordis suite runs green on the bun#39426 build overall (drop-in compatibility verified)

Notes

ebowwa added 6 commits August 17, 2026 09:25
Our upstream PR (a file:// URL's query is part of the module key) is the
missing public-API primitive for per-module reload on Bun — the
capability @cordisjs/plugin-hmr gets on Node from --expose-internals
internals.

- tests/bun/fixtures/selective-reload-driver.ts + selective-reload.spec.ts:
  capability-gated spec proving the full swap contract — fresh module
  instance per ?gen=N, old fiber disposed only after the new applies,
  root effects survive unduplicated, single disposal at shutdown.
  Probes the runner binary (BUN_QUERY_BUSTING_BIN or a local
  .upstream/bun debug build); stock Bun skips cleanly.
- repros/selective-reload.ts: manual narrative version.
- docs: Phase C section rewritten from 'deferred' to 'primitive done';
  suite now 69 tests (68 + the gated one); Phase 10 status recorded.

Verified: 69/69 with the bun#39426 debug build present; the full Cordis
suite also runs green on that build; skip path exercised on stock Bun.
build/release/bun (the ~60-90MB usable binary) checked before the debug
build; env override unchanged.
…dling

- build:release from the PR branch → 1.4.0-canary.1+c16333e9e; preserved
  at .upstream/bin/bun-39426 (59 MB, git-excluded); build dir reclaimed
- verified: selective-reload spec + full 69/69 suite against it; real-app
  smoke (bin.bun.js boots, loads plugin, SIGINT root disposal, exit 0)
- spec: resolve() the BUN_QUERY_BUSTING_BIN override — the driver child
  runs from a temp cwd, so a relative path silently broke spawn (caught
  during release verification)
- docs: 'Running Cordis on the contributed Bun today' usage section;
  Phase 10 binary record incl. prior-art note re #35601
The release binary (1.4.0-canary.1+c16333e9e) that Cordis is verified
to run on: checksum recorded for verification, rebuild path documented.
ebowwa pushed a commit that referenced this pull request Aug 21, 2026
… + disclosure

Our #39426 partially duplicates robobun's earlier #35601 (same diagnosis,
broader coverage, also fixes #13391, but stalled/dirty since Jul 26).
Disclosed on both PRs; Cordis PR #2 unaffected (capability gating).
Lesson: run the is:pr <issue> prior-art search before opening a fix.
ebowwa pushed a commit that referenced this pull request Aug 21, 2026
…lter, history, adjacent issues)

Map: #35601 (our dup, stalled), #35703 (fragments, claimed), #37702
(plugin prefilter vs query dots, adjacent non-conflicting), #16456
(merged Jan 2025 — introduced relative+query), #13391 (astro, covered),
#35345 (lcov re-import bug — becomes reachable via file:// queries once
either fix lands; cross-reference kept local), #7823 (mock.restore).

Verdict: neighborhood fully occupied by the Bun team's in-flight work;
no uncontested target remains. Our plays: fresh-on-main #39426,
downstream validation, Cordis PR #2 capability gating.
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