You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(bundler): re-install web globals after a V8 snapshot restore (#6012)
## Motivation
In a bundled V8 startup snapshot, the prelude replaces Node's
undici-backed web globals
(`fetch`/`Headers`/`Request`/`Response`/`FormData`/`WebSocket`/`EventSource`/`MessageEvent`/`CloseEvent`)
with constructable stubs at build time — touching them pulls in undici's
native `HTTPParser`/http2 bindings, which a snapshot can't serialize.
But **the restored process kept the stubs**, so `globalThis.fetch(...)`
was a dead no-op after restore. (This is the "Web globals are no-op
stubs after restore" known limitation from the docs.)
## What changed
- The generated snapshot-restore entry now installs `__RUNTIME_REQUIRE`
(now carrying `.resolve`) and then calls a new
`globalThis.__installWebGlobalsLazy` (defined by the prelude, serialized
into the blob). It re-installs the web globals as **lazy accessors**:
- the fetch family from the app's real `undici` — kept external (e.g.
`--force-external undici urllib`, as the cnpmcore e2e already does), so
it loads for real on restore; resolved directly, or through `urllib`
under pnpm where undici isn't hoisted;
- `Blob`/`File` from `node:buffer`.
- The lazy accessor must **not cache `undefined`**: undici reads
`globalThis.Headers` re-entrantly while its own `require()` is still in
flight, so caching there would poison `Headers`.
- **`Blob`/`File` are no longer stubbed on `node:buffer`.** They are
`node:buffer`-backed (not undici), serialize into a snapshot fine, and
stay real — the previous buffer-getter stub made them unrecoverable at
restore. Verified the cnpmcore snapshot still serializes with them
un-stubbed.
Net effect: `globalThis.fetch(...)` and the related constructors work
normally after a restore. Docs updated (EN + ZH): the limitation becomes
a "works after restore" description, with the one remaining caveat (a
web global captured at build time — `const f = fetch`, or `class X
extends globalThis.Request` — freezes the stub; reference web globals at
call time).
## Test evidence
- New/updated unit + real-`@utoo/pack`-build tests in
`snapshot-lazy-external.test.ts` and `snapshot-lazy.realbuild.test.ts`:
lazy re-install from undici/`node:buffer`, re-entrancy,
replace-stub-but-keep-genuine, non-configurable skip, and a real build
where the prelude stubs the globals and the installer brings back real
undici-backed `fetch`/`Headers`/`Blob`.
- Verified end-to-end with a real `node --build-snapshot` + restore on
**Node 24**: all web globals work — real `fetch` round-trip + `new
Headers` + `new Blob`.
- Verified against the **cnpmcore** snapshot (`next`'s recipe:
`--force-external undici urllib @cnpmjs/packument koa-onerror`, no
manual stubs): build → restore → `GET /-/ping` HTTP 200, with the real
ORM/HTTP client live at restore.
- `egg-bundler` suite green except pre-existing macOS-only env failures;
lint / format / typecheck clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Snapshot restores now lazily reinstall web globals after startup via
`__installWebGlobalsLazy` (including
`fetch`/`Headers`/`Request`/`Response`), with `Blob`/`File` restored
from `node:buffer`.
* Snapshot module loading now supports `__RUNTIME_REQUIRE.resolve` for
resolver lookups.
* **Bug Fixes**
* Build-time snapshot stubbing is safer and no longer interferes with
`node:buffer` web types.
* **Documentation**
* Updated “Web globals” known-limits guidance (EN and zh-CN) to
emphasize using globals at call time.
* **Tests**
* Expanded coverage for lazy install, re-entrancy, and real
undici-backed restore behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
0 commit comments