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
The snapshot prelude deleted Node's web globals (fetch/Headers/Request/
Response/FormData/WebSocket/.../Blob/File) at build time to keep the heap
serializable, but never restored them, so `globalThis.fetch` was a dead
stub after restore.
The prelude now gates the delete to the snapshot build
(EGG_BUNDLE_SNAPSHOT=build, so a plain `node worker.js` keeps native globals)
and defines `__installWebGlobalsLazy`, which EntryGenerator's deserialize
main calls after installing `__RUNTIME_REQUIRE` (now exposing `.resolve`).
It re-installs the globals as lazy accessors: the fetch family from the
app's undici (resolved directly, or via urllib under pnpm), Blob/File from
node:buffer. The accessor must not cache `undefined`, because undici reads
`globalThis.Headers` re-entrantly while its own require() is in flight.
Also fixes four build-serializability gaps found bringing a real app
(cnpmcore) snapshot up, all build-gated in the prelude:
- supply module/exports so the bundle's UMD external wrapper takes the
require/externalRequire branch (`node --build-snapshot` runs the worker
as a non-CommonJS main, so `typeof module === 'undefined'`).
- keep Blob/File (node:buffer-backed, serializable); deleting them only
broke build-eval (e.g. undici webidl's MakeTypeAssertion(Blob)).
- replace deleted globals with marked, extendable stub classes so a fetch
ponyfill doing `class Request extends globalThis.Request` builds without
crashing; the restore installer replaces the marked stub with the real value.
- expose the hardcoded http constants in the lazy proxy's ownKeys /
getOwnPropertyDescriptor so an ESM `import * as http; for (const m of
http.METHODS)` works (turbopack interopEsm copies a static namespace).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: site/docs/advanced/snapshot.md
+16Lines changed: 16 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -73,3 +73,19 @@ module.exports = AppBootHook;
73
73
-`snapshotDidDeserialize()` runs after the process starts from the snapshot.
74
74
75
75
Use these hooks for resources such as timers, sockets, process listeners, loggers, or other handles that must be recreated in a live runtime.
76
+
77
+
## Web Globals After Restore (bundled snapshots)
78
+
79
+
When you build a bundled snapshot (`egg-bin snapshot build`, which uses `@eggjs/egg-bundler`), Node's lazy web globals — `fetch`, `Headers`, `Request`, `Response`, `FormData`, `WebSocket`, `EventSource`, `MessageEvent`, `CloseEvent`, `Blob`, and `File` — are deleted at build time. Touching any of them lazily initializes undici, whose llhttp `HTTPParser` / `WebAssembly` instance is a native binding that cannot be written into a V8 startup snapshot.
80
+
81
+
They are re-installed automatically when the snapshot is restored, as lazy accessors:
82
+
83
+
- the fetch family is backed by the application's `undici` (resolved directly, or through `urllib` — egg's direct dependency — under pnpm layouts where undici is not hoisted);
84
+
-`Blob` / `File` come from `node:buffer`.
85
+
86
+
Each one loads its real implementation on first access, so a restore stays fast when the app never touches them, and `globalThis.fetch(...)` (or a bare `fetch(...)`) and the related constructors all work normally after restore.
87
+
88
+
Two things to keep in mind:
89
+
90
+
- The bundle output must be run where `undici` is resolvable. An egg app always satisfies this through its `urllib` dependency; if your deployment strips dependencies, keep `urllib` (or `undici`) installed next to the bundle.
91
+
- Reference web globals at call time, not at module top level. During a snapshot build the deleted globals are replaced with extendable stub classes, so a module that does `class MyRequest extends globalThis.Request` at load time builds without crashing — but a binding captured at build time (`const f = fetch`, or a class that `extends` a web global) freezes that stub into the blob and is not upgraded at restore. Call `fetch(...)` / `new Headers(...)` where you use them and the lazy accessors resolve the real implementation after restore.
0 commit comments