Skip to content

docs: correct what --hot watches and its sample output, document built-in npm package replacements - #42718

Merged
alii merged 2 commits into
mainfrom
robobun/72ca13e9/docs-hot-watch-builtin-packages
Sep 14, 2026
Merged

alii merged 2 commits into
mainfrom
robobun/72ca13e9/docs-hot-watch-builtin-packages

Conversation

@robobun

@robobun robobun commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • docs/guides/http/hot.mdx:7 says "When any module or file changes, Bun re-runs the file." bun --hot watches only the entry file and the files it imports. An edit to .env, bunfig.toml, or tsconfig.json triggers no reload.
  • docs/runtime/watch-mode.mdx:124 shows Reloaded 1 times first. The sample logs before it increments, so the first line is Reloaded 0 times.
  • No docs page says that Bun resolves undici, node-fetch, ws, and other npm specifiers to built-in modules, even when the package is installed.

Fix

  • hot.mdx: say what --hot watches, name three files it does not watch, and say to restart Bun for those.
  • watch-mode.mdx: the output sample starts at Reloaded 0 times.
  • module-resolution.mdx: new section "Built-in replacements for npm packages". It lists every third-party specifier in the alias table (src/resolve_builtins/HardcodedModule.rs:746-799), what each loads, how subpaths and require.resolve() behave, and what bun build does per target.
  • Verified: I ran every claim on a release build of main (1.4.3-canary.1+09bb54630). Output is in Notes. prettier --check passes.

Background

  • --hot adds a file to the watcher when the module loader loads it, and skips node_modules. Bun reads .env and bunfig.toml at startup, outside the module loader.
  • The alias table maps a specifier to a built-in module before the resolver looks at node_modules. It applies when the target is bun: the runtime and bun build --target=bun.
  • Resolve bare undici to the installed package, keep the shim as fallback #36102 (open) makes bare undici prefer the installed package. If it lands, the undici row changes with it.
Notes

No hunk overlaps #30912 (watch-mode.mdx lines 91-99 and 146-153) or #38408 (module-resolution.mdx line 309 and below).

--hot triggers. server.ts is the sample from watch-mode.mdx:104-116, with .env, tsconfig.json, and bunfig.toml in the same directory. A driver script edits one file, waits, and counts the Reloaded lines.

[after start] lines=1
[after .env edit] lines=1
[after tsconfig edit] lines=1
[after bunfig edit] lines=1
[after server.ts edit 1] lines=2
[after server.ts edit 2] lines=3
--- out.log
Reloaded 0 times
Reloaded 1 times
Reloaded 2 times

Resolution. node_modules/{undici,node-fetch,ws,isomorphic-fetch} each hold a stub package that exports { fromNodeModules: true }.

$ bun b.cjs
undici             resolve: undici             fromNodeModules: false
node-fetch         resolve: node-fetch         fromNodeModules: false
ws                 resolve: ws                 fromNodeModules: false
isomorphic-fetch   resolve: isomorphic-fetch   fromNodeModules: false
undici/package.json -> /tmp/dv/undici-check/node_modules/undici/package.json
$ node b.cjs
undici             resolve: /tmp/dv/undici-check/node_modules/undici/index.js fromNodeModules: true
...

require.resolve() for every specifier in the table:

@vercel/fetch                    -> @vercel/fetch
isomorphic-fetch                 -> isomorphic-fetch
node-fetch                       -> node-fetch
undici                           -> undici
utf-8-validate                   -> utf-8-validate
ws                               -> ws
ws/lib/websocket                 -> ws
abort-controller                 -> abort-controller
abort-controller/polyfill        -> abort-controller
next/dist/compiled/ws            -> ws
next/dist/compiled/node-fetch    -> node-fetch
next/dist/compiled/undici        -> undici
undici/package.json              -> /tmp/dv/undici-check/node_modules/undici/package.json
ws/package.json                  -> /tmp/dv/undici-check/node_modules/ws/package.json

import.meta.resolve("undici") and import.meta.resolve("node-fetch") return undici and node-fetch.

What the built-in modules do. A local Bun.serve answers direct:<path>.

undici.Request/Response/Headers/FormData/File/WebSocket === the globals: true (each)
undici.fetch === Bun.fetch: true
undici.fetch with dead ProxyAgent -> 200 direct:/x
global fetch after setGlobalDispatcher(dead proxy) -> direct:/x
Agent has dispatch/close/destroy/compose: undefined,undefined,undefined,undefined
undici.request -> 200 body is Readable: true direct:/x
undici.stream() / pipeline() / connect() / upgrade() -> This function is not yet implemented in Bun
node-fetch res instanceof Response: true | body is Readable: true | buffer(): function | headers.raw(): function
abort-controller default === AbortController: true, .AbortSignal === AbortSignal: true
utf-8-validate: function, returns true for Buffer.from("hi")

ws: WebSocket wraps the global WebSocket (src/js/thirdparty/ws.js:312). WebSocketServer uses node:http and server.upgrade() (ws.js:1373, ws.js:1566). utf-8-validate exports jsBufferConstructorFunction_isUtf8 (src/jsc/modules/UTF8ValidateModule.h:16), the function behind isUtf8 in node:buffer.

bun build. The entry imports fetch from undici and the default export of node-fetch, with the stub packages installed.

--target=bun      keeps  import { fetch as f1 } from "undici"; import f2 from "node-fetch";
--target=node     bundles node_modules/undici/index.js and node_modules/node-fetch/index.js
--target=browser  bundles node_modules/undici/index.js and node_modules/node-fetch/index.js

The bun test rewrite of @jest/globals and vitest to bun:test uses the same table. It is already in docs/guides/test/migrate-from-jest.mdx:19 and is not repeated here.

…cument built-in package replacements

- guides/http/hot.mdx: --hot reloads when the entry file or a file it imports
  changes. Files that no module imports (.env, bunfig.toml, tsconfig.json) do
  not trigger a reload.
- runtime/watch-mode.mdx: the sample logs before it increments, so the first
  line is "Reloaded 0 times".
- runtime/module-resolution.mdx: list the npm specifiers that Bun resolves to
  built-in modules (undici, node-fetch, isomorphic-fetch, @vercel/fetch, ws,
  utf-8-validate, abort-controller) and what that means for resolution and
  bun build.
@robobun

robobun commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

Status: ready for review. This PR changes docs only.

How I checked it: I ran each statement on a release build of main (1.4.3-canary.1+09bb54630). The --hot probe edits .env, tsconfig.json, bunfig.toml, and then server.ts, and counts the reloads. The resolution probe installs stub packages in node_modules and compares bun with node. The output is in the Notes block of the PR description.

@coderabbitai

coderabbitai Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: 85f88d6a-3b7c-49c4-b15c-1889a9d171ef

📥 Commits

Reviewing files that changed from the base of the PR and between ed4083a and 0a8a939.

📒 Files selected for processing (1)
  • docs/runtime/module-resolution.mdx

Included review availability: Your plan provides up to 10 included reviews per hour; 4 remain after this review.


Walkthrough

The documentation clarifies hot reload watch scope, corrects the reload output example, and describes Bun’s built-in replacements for selected npm packages.

Changes

Runtime documentation

Layer / File(s) Summary
Hot reload behavior and output
docs/guides/http/hot.mdx, docs/runtime/watch-mode.mdx
The documentation limits hot reload watching to the entry file and its imports. It excludes unimported configuration and environment files and updates the reload count example from 0 through 2.
Built-in module replacements
docs/runtime/module-resolution.mdx
The documentation describes supported replacements, API limitations, unmatched subpath resolution, and target-specific bun build behavior.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 0a8a9

The PR documents runtime behavior and does not indicate a merge-blocking production change.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main documentation changes: it corrects --hot watcher behavior and sample output, and documents built-in npm package replacements.
Description check ✅ Passed The description explains the problem, lists the documentation fixes, and provides detailed verification results. It does not use the template headings exactly, but it includes the required information…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Comment thread docs/runtime/module-resolution.mdx Resolved
…t specifier

ws/lib/websocket resolves to "ws", abort-controller/polyfill to
"abort-controller", and next/dist/compiled/* to the target module name.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — thanks for the follow-up commit; the require.resolve() wording now correctly says it returns the built-in module name (with the next/dist/compiled/undici → "undici" example), which was my only note.

What was reviewed:

  • Cross-checked the specifier table and subpath/resolve claims against src/resolve_builtins/HardcodedModule.rs — rows and alias-target semantics match.
  • --hot scope narrowing and the Reloaded 0/1/2 sample match the log-then-increment code in the guide.
  • Prose follows the docs voice rules (active voice, present tense, no "will"/"just"); the bun.com/issues link matches existing usage in nodejs-compat.mdx.
Extended reasoning...

Overview

Docs-only PR touching three MDX files: tightens the --hot description in docs/guides/http/hot.mdx to state that only the entry file and its imports are watched (naming .env/bunfig.toml/tsconfig.json as unwatched), corrects the --hot sample output in docs/runtime/watch-mode.mdx to start at Reloaded 0 times, and adds a "Built-in replacements for npm packages" section to docs/runtime/module-resolution.mdx documenting the third-party specifiers Bun overrides with built-in modules, their limitations, subpath fall-through, require.resolve() behavior, and per-target bun build handling.

Security risks

None. Pure documentation; no code, config, scripts, or CI changes. No external content is fetched at build time.

Level of scrutiny

Low-to-moderate — the main risk for docs changes is inaccuracy. I checked the alias table in src/resolve_builtins/HardcodedModule.rs against the documented specifiers and the resolve-returns-target-name claim; the follow-up commit 0a8a939a fixed the one inaccuracy I flagged last run (aliased subpaths resolve to the target name, not the input). The bun.com/issues link pattern already appears in neighboring docs. The prose conforms to the repo's docs voice conventions (active voice with Bun as subject, present tense, imperative remedy, no hedging adverbs).

Other factors

No CODEOWNERS entry covers docs/. No outstanding third-party CHANGES_REQUESTED reviews. The hunt exited on dry_streak with no findings. The only prior inline comment (mine) was addressed by the second commit, so approving is appropriate.

@alii
alii merged commit b298470 into main Sep 14, 2026
5 checks passed
@alii
alii deleted the robobun/72ca13e9/docs-hot-watch-builtin-packages branch September 14, 2026 22:52
usrbinkat pushed a commit to usrbinkat/bun that referenced this pull request Sep 15, 2026
…t-in npm package replacements (oven-sh#42718)

### Problem

- `docs/guides/http/hot.mdx:7` says "When any module or file changes,
Bun re-runs the file." `bun --hot` watches only the entry file and the
files it imports. An edit to `.env`, `bunfig.toml`, or `tsconfig.json`
triggers no reload.
- `docs/runtime/watch-mode.mdx:124` shows `Reloaded 1 times` first. The
sample logs before it increments, so the first line is `Reloaded 0
times`.
- No docs page says that Bun resolves `undici`, `node-fetch`, `ws`, and
other npm specifiers to built-in modules, even when the package is
installed.

### Fix

- `hot.mdx`: say what `--hot` watches, name three files it does not
watch, and say to restart Bun for those.
- `watch-mode.mdx`: the output sample starts at `Reloaded 0 times`.
- `module-resolution.mdx`: new section "Built-in replacements for npm
packages". It lists every third-party specifier in the alias table
(`src/resolve_builtins/HardcodedModule.rs:746-799`), what each loads,
how subpaths and `require.resolve()` behave, and what `bun build` does
per target.
- Verified: I ran every claim on a release build of main
(`1.4.3-canary.1+09bb54630`). Output is in Notes. `prettier --check`
passes.

### Background

- `--hot` adds a file to the watcher when the module loader loads it,
and skips `node_modules`. Bun reads `.env` and `bunfig.toml` at startup,
outside the module loader.
- The alias table maps a specifier to a built-in module before the
resolver looks at `node_modules`. It applies when the target is `bun`:
the runtime and `bun build --target=bun`.
- oven-sh#36102 (open) makes bare `undici` prefer the installed package. If it
lands, the `undici` row changes with it.

<details><summary>Notes</summary>

No hunk overlaps oven-sh#30912 (`watch-mode.mdx` lines 91-99 and 146-153) or
oven-sh#38408 (`module-resolution.mdx` line 309 and below).

**`--hot` triggers.** `server.ts` is the sample from
`watch-mode.mdx:104-116`, with `.env`, `tsconfig.json`, and
`bunfig.toml` in the same directory. A driver script edits one file,
waits, and counts the `Reloaded` lines.

```
[after start] lines=1
[after .env edit] lines=1
[after tsconfig edit] lines=1
[after bunfig edit] lines=1
[after server.ts edit 1] lines=2
[after server.ts edit 2] lines=3
--- out.log
Reloaded 0 times
Reloaded 1 times
Reloaded 2 times
```

**Resolution.** `node_modules/{undici,node-fetch,ws,isomorphic-fetch}`
each hold a stub package that exports `{ fromNodeModules: true }`.

```
$ bun b.cjs
undici             resolve: undici             fromNodeModules: false
node-fetch         resolve: node-fetch         fromNodeModules: false
ws                 resolve: ws                 fromNodeModules: false
isomorphic-fetch   resolve: isomorphic-fetch   fromNodeModules: false
undici/package.json -> /tmp/dv/undici-check/node_modules/undici/package.json
$ node b.cjs
undici             resolve: /tmp/dv/undici-check/node_modules/undici/index.js fromNodeModules: true
...
```

`require.resolve()` for every specifier in the table:

```
@vercel/fetch                    -> @vercel/fetch
isomorphic-fetch                 -> isomorphic-fetch
node-fetch                       -> node-fetch
undici                           -> undici
utf-8-validate                   -> utf-8-validate
ws                               -> ws
ws/lib/websocket                 -> ws
abort-controller                 -> abort-controller
abort-controller/polyfill        -> abort-controller
next/dist/compiled/ws            -> ws
next/dist/compiled/node-fetch    -> node-fetch
next/dist/compiled/undici        -> undici
undici/package.json              -> /tmp/dv/undici-check/node_modules/undici/package.json
ws/package.json                  -> /tmp/dv/undici-check/node_modules/ws/package.json
```

`import.meta.resolve("undici")` and `import.meta.resolve("node-fetch")`
return `undici` and `node-fetch`.

**What the built-in modules do.** A local `Bun.serve` answers
`direct:<path>`.

```
undici.Request/Response/Headers/FormData/File/WebSocket === the globals: true (each)
undici.fetch === Bun.fetch: true
undici.fetch with dead ProxyAgent -> 200 direct:/x
global fetch after setGlobalDispatcher(dead proxy) -> direct:/x
Agent has dispatch/close/destroy/compose: undefined,undefined,undefined,undefined
undici.request -> 200 body is Readable: true direct:/x
undici.stream() / pipeline() / connect() / upgrade() -> This function is not yet implemented in Bun
node-fetch res instanceof Response: true | body is Readable: true | buffer(): function | headers.raw(): function
abort-controller default === AbortController: true, .AbortSignal === AbortSignal: true
utf-8-validate: function, returns true for Buffer.from("hi")
```

`ws`: `WebSocket` wraps the global `WebSocket`
(`src/js/thirdparty/ws.js:312`). `WebSocketServer` uses `node:http` and
`server.upgrade()` (`ws.js:1373`, `ws.js:1566`). `utf-8-validate`
exports `jsBufferConstructorFunction_isUtf8`
(`src/jsc/modules/UTF8ValidateModule.h:16`), the function behind
`isUtf8` in `node:buffer`.

**`bun build`.** The entry imports `fetch` from `undici` and the default
export of `node-fetch`, with the stub packages installed.

```
--target=bun      keeps  import { fetch as f1 } from "undici"; import f2 from "node-fetch";
--target=node     bundles node_modules/undici/index.js and node_modules/node-fetch/index.js
--target=browser  bundles node_modules/undici/index.js and node_modules/node-fetch/index.js
```

The `bun test` rewrite of `@jest/globals` and `vitest` to `bun:test`
uses the same table. It is already in
`docs/guides/test/migrate-from-jest.mdx:19` and is not repeated here.

</details>
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.

3 participants