perf(ext): convert ext/cache, ext/canvas, ext/crypto JS sources to lazy-loaded scripts#33778
Conversation
fibibot
left a comment
There was a problem hiding this comment.
Substance follows the same pattern I approved in #33760. The 12k-line diff is dominated by ext/crypto/00_crypto.js (~11.5k lines) where every line is replaced as the file goes from ESM module to IIFE script — almost-zero net (5684+/5634-). The actual semantic surface is small:
Substance walk
ext/crypto/lib.rs:esm = ["00_crypto.js"]→lazy_loaded_js = ["00_crypto.js"]. Right shape, mirrors #33760.ext/cache/01_cache.js: ext/fetch dependencies (Request,toInnerRequest,toInnerResponse,getHeader) bridged viainternalssince ext/fetch is still ESM. Correct workaround pattern — scripts can'timportfrom other extensions, so cross-extension symbols flow through theinternalsnamespace.ext/crypto/00_crypto.js: same wrap.kKeyObjectsymbol bridge added atext/node/polyfills/internal/crypto/constants.ts(publishesinternals.kKeyObject = kKeyObject) so the now-script-form crypto can read it viainternals.kKeyObject.- Consumer migration:
ext/node/polyfills/crypto.ts:170andinternal/crypto/keys.ts:38both switchimport { ... } from "ext:deno_crypto/00_crypto.js"→const { ... } = core.loadExtScript("ext:deno_crypto/00_crypto.js").runtime/js/90_deno_ns.jsdoes the same forext:deno_canvas/02_surface.js. These are the exact paths that broke OffscreenCanvas in #33760 before thecore.loadExtScriptmigration — encouraging that they're handled in this PR's initial diff. ext/cronskipped: the body acknowledges01_cron.tsis TypeScript andlazy_loaded_jsdoesn't transpile. Right scope decision.
Smaller observations
internals.kKeyObjectcouples crypto.js to node/constants.ts: this is a load-order-sensitive coupling. Ifnode/internal/crypto/constants.tsdoesn't run before00_crypto.js's IIFE evaluates,internals.kKeyObjectwill beundefined. Worth confirming the bootstrap ordering — and worth a brief comment inconstants.tsexplaining whyinternals.kKeyObject = kKeyObjectis there ("publishes for ext/crypto's lazy-loaded script") so a future reader doesn't think it's an internals leak to clean up.- The
// @ts-checkand triple-slash references at the top of00_crypto.jsare dropped in the conversion. The IIFE form can't use// @ts-check— that's expected — but it does mean the crypto JS no longer benefits from the TypeScript checker. Worth a tracking issue if this matters for ongoing crypto polyfill work.
CI
26 of 38 relevant test unit / test node_compat / wpt / lint debug shards SUCCESS at this head, with 12 still in flight (macos-aarch64 debug + linux-aarch64 debug + lint debug macos-x86_64 — the slower runners). No failures yet. Given the #33760 precedent had a V8 abort that surfaced only after the build/lint shards had passed, holding COMMENT until the remaining macOS / linux-aarch64 runs land green — particularly the test unit shards which exercised the OffscreenCanvas bug last time. Substance is fine from my side; if the in-flight shards come back clean, this is APPROVE-ready.
598fa50 to
24b1177
Compare
fibibot
left a comment
There was a problem hiding this comment.
LGTM. The force-pushed delta is the same lazy_loaded_js conversion I walked previously; substance still tracks the established #33760 / #33779 / #33780 pattern, and the CI signal that was in-flight on my prior pass has now landed clean across the platforms that mattered for the OffscreenCanvas-style hazard:
- All 12
test unit_nodeshards pass — the canary for00_crypto.jsconsumers likeinternal/crypto/keys.tsandcrypto.tsthat now go throughcore.loadExtScript. - All 12
test unitshards pass — Web Crypto + CacheStorage globals from98_global_scope_shared.js. - All 36
test node_compatshards pass (3 shards × 6 platforms × 2 modes) — broadest signal that theinternals.kKeyObjectbridge resolves correctly across node:crypto entry paths. - 11 of 12
test specsshards pass. The lone failure istest specs (2/2) debug macos-aarch64cancelled at 30m19s — the 30-minute timeout, not a substantive failure. specs (1/2) on the same host passed in 5m30s, and every other macos-aarch64 shard (build / lint / integration / unit / unit_node / node_compat) succeeded. Same flake pattern that hit #33780.
Load-order coupling
The one substance question I left open in my prior pass — does internals.kKeyObject resolve when 00_crypto.js IIFE evaluates from 98_global_scope_shared.js? — checks out by chasing the import graph:
98_global_scope_shared.jsESM imports includeimport process from "node:process".ext/node/polyfills/process.ts:50importsnode:assert.ext/node/polyfills/assert.ts:21importsinternal/util/comparisons.ts.ext/node/polyfills/internal/util/comparisons.ts:35importsinternal/crypto/constants.tsforkKeyObject.internal/crypto/constants.ts(new in this PR) doesinternals.kKeyObject = kKeyObject;at module evaluation.
V8 module-evaluation contract: imports run before body. So by the time 98_global_scope_shared.js's body executes the core.loadExtScript("ext:deno_crypto/00_crypto.js") call, the entire process → assert → comparisons → constants chain has already evaluated and internals.kKeyObject is set. The destructure inside the crypto IIFE (const { kKeyObject } = internals;) gets the proper Symbol, not undefined.
The coupling is implicit but real, and worth the doc comment I flagged previously in constants.ts ("published for ext/crypto's lazy-loaded script"). If a future refactor drops the node:assert import chain from process.ts, kKeyObject silently becomes undefined and key[kKeyObject] writes go to property "undefined". Not a blocker for this PR; flagging for a follow-up doc tweak.
Pattern parity check
Bridge pattern matches the established convention from prior PRs in this series:
| Bridge | Set in | Read in |
|---|---|---|
internals.__fetchRequest |
98_global_scope_shared.js body before loadExtScript("01_cache.js") |
01_cache.js IIFE |
internals.__fetchResponse |
same | same |
internals.__fetchHeaders |
same | same |
internals.kKeyObject |
internal/crypto/constants.ts (this PR) |
00_crypto.js IIFE |
The cache bridges are explicit at the call site (read order obvious). The crypto bridge is implicit through the eager-import chain (acceptable, documented above). All four use the internals.* namespace consistent with prior precedent.
Not blocking on the macOS specs flake — pre-existing infrastructure, not introduced by this PR.
Follow-up to #33760 and #33779. Converts all 8 ESM JS sources in `ext/fetch` to IIFE-wrapped lazy-loaded scripts (`lazy_loaded_js`). - `20_headers.js`, `21_formdata.js`, `22_body.js`, `22_http_client.js`, `23_request.js`, `23_response.js`, `26_fetch.js`, `27_eventsource.js` - `26_fetch.js` imports from `ext/telemetry` (TypeScript ESM, can't be converted yet) -- bridged via `internals.__telemetry` / `internals.__telemetryUtil` set in the telemetry source files - All other cross-extension deps use `core.loadExtScript()` (ext/web, ext/webidl, ext/net are already `lazy_loaded_js`) This also eliminates the `internals.__fetchRequest/Response/Headers` bridges from #33778 since `ext/cache` can now use `core.loadExtScript()` for ext/fetch directly.
24b1177 to
ac5c672
Compare
…zy-loaded scripts
ac5c672 to
886dc19
Compare
fibibot
left a comment
There was a problem hiding this comment.
LGTM — re-affirming after the rebase. Substance is unchanged in shape but cleaner than the version I approved at 24b11772:
The internals.__fetchRequest / __fetchResponse / __fetchHeaders bridges I walked previously are gone. Now that #33784's ext/fetch lazy_loaded conversion has landed on main, 01_cache.js resolves its cross-extension dependencies directly via core.loadExtScript("ext:deno_fetch/23_request.js") etc., the same pattern every other lazy-loaded script uses. The bridge hack was always a workaround for ext/fetch still being ESM at the time; now there's no need.
What I re-verified at this head:
ext/cache/01_cache.jsusescore.loadExtScriptforRequest/RequestPrototype/toInnerRequest,toInnerResponse,getHeader— direct calls, nointernals.__*bridge. ✓98_global_scope_shared.jshas zerointernals.__fetch*writes — the bridge plumbing the prior version added has been excised. The body just callscore.loadExtScriptfor each dep. ✓internals.kKeyObjectbridge for ext/crypto → ext/node still in place, sinceext:deno_node/internal/crypto/constants.tsis still ESM (not lazy_loaded). The same load-order analysis from my prior review still holds:node:process→node:assert→internal/util/comparisons→internal/crypto/constants.tsruns as part of98_global_scope_shared.js's ESM imports, before its body'score.loadExtScript("ext:deno_crypto/00_crypto.js")evaluates the IIFE. Sointernals.kKeyObjectis set when crypto's IIFE reads it.- Same 11 files, same conversion shape, same JS consumers updated to
core.loadExtScript(ext/node/polyfills/crypto.ts,internal/crypto/keys.ts,runtime/js/90_deno_ns.js).
CI
123 pass, 1 pending, 1 skip, 0 fail at this head. The pending shard is the slower macos-aarch64 specs runner — same flake-prone shard that hit my prior review's pass too. All other shards across linux/macos/windows × x86_64 × debug+release pass.
Nothing else to add — this is a strict improvement over the version I previously approved.
… scripts (#33817) - Convert `ext/process/40_process.js` from ESM to IIFE-wrapped lazy-loaded script (`lazy_loaded_js`) - Convert `ext/http/00_serve.ts`, `ext/http/01_http.js`, `ext/http/02_websocket.ts` from ESM to IIFE-wrapped lazy-loaded scripts - Update all consumers (`runtime/js/90_deno_ns.js`, `runtime/js/99_main.js`, `ext/node/polyfills/child_process.ts`, `ext/node/polyfills/internal/child_process.ts`) to use `core.loadExtScript()` instead of ESM imports - Replace `import.meta.log` calls with `internals.log` in `00_serve.ts` Follows the same pattern established in #33778, #33780, #33784, #33800, and #33801.
…ripts (#33818) ## Summary - Convert `ext/kv/01_db.ts` from ESM to IIFE-wrapped lazy-loaded script (`lazy_loaded_js`) - Convert `ext/webgpu/00_init.js` from ESM to IIFE-wrapped lazy-loaded script (`lazy_loaded_js`) - Update all consumers (`runtime/js/90_deno_ns.js`, `runtime/js/98_global_scope_shared.js`, `runtime/js/98_global_scope_window.js`, `runtime/js/98_global_scope_worker.js`) to use `core.loadExtScript()` instead of ESM imports - Replace `import.meta.log` call with `internals.log` in `01_db.ts` Follows the same pattern established in #33778, #33780, #33784, #33800, #33801, and #33817.
Resolve KV conflicts after main's lazy-loading refactor (#33778, #33818, #33966) and host-object deserialize fix (#34380): - ext/kv/lib.rs: switch `esm` to `lazy_loaded_esm`, drop generics, use `Box<dyn DynamicDbHandler>` (matching #33966). - runtime/js/90_deno_ns.js: load 01_db.ts via `createLazyLoader` (ESM) instead of `loadExtScript` (lazy script). - ext/kv/01_db.ts, impl/*.ts: convert relative imports to `ext:deno_kv/` specifiers (cannot-be-a-base scheme rejects relative URLs in lazy ESM). - impl/sqlite_backend.ts, impl/remote_backend.ts: load now-lazy modules (`ext:deno_crypto`, `ext:deno_fetch`, `ext:deno_web/03_abort_signal`) via `core.loadExtScript` instead of static imports. - impl/kv.ts: same for `06_streams.js`; add `deserializers: cloneableDeserializers` to KV value/queue payload deserialization (port of #34380).
…and#33784) Follow-up to denoland#33760 and denoland#33779. Converts all 8 ESM JS sources in `ext/fetch` to IIFE-wrapped lazy-loaded scripts (`lazy_loaded_js`). - `20_headers.js`, `21_formdata.js`, `22_body.js`, `22_http_client.js`, `23_request.js`, `23_response.js`, `26_fetch.js`, `27_eventsource.js` - `26_fetch.js` imports from `ext/telemetry` (TypeScript ESM, can't be converted yet) -- bridged via `internals.__telemetry` / `internals.__telemetryUtil` set in the telemetry source files - All other cross-extension deps use `core.loadExtScript()` (ext/web, ext/webidl, ext/net are already `lazy_loaded_js`) This also eliminates the `internals.__fetchRequest/Response/Headers` bridges from denoland#33778 since `ext/cache` can now use `core.loadExtScript()` for ext/fetch directly.
…zy-loaded scripts (denoland#33778) Follow-up to denoland#33760. Converts the ESM JS sources in `ext/cache`, `ext/canvas`, and `ext/crypto` to IIFE-wrapped lazy-loaded scripts (`lazy_loaded_js`), eliminating ESM module resolution overhead. - **ext/canvas/02_surface.js** - simple re-export from `core.ops` - **ext/cache/01_cache.js** - ext/fetch dependencies (Request, toInnerRequest, toInnerResponse, getHeader) bridged via `internals` since ext/fetch is still ESM - **ext/crypto/00_crypto.js** - `kKeyObject` symbol bridged via `internals` from `ext/node`; updated `ext/node` consumers to use `core.loadExtScript()`
… scripts (denoland#33817) - Convert `ext/process/40_process.js` from ESM to IIFE-wrapped lazy-loaded script (`lazy_loaded_js`) - Convert `ext/http/00_serve.ts`, `ext/http/01_http.js`, `ext/http/02_websocket.ts` from ESM to IIFE-wrapped lazy-loaded scripts - Update all consumers (`runtime/js/90_deno_ns.js`, `runtime/js/99_main.js`, `ext/node/polyfills/child_process.ts`, `ext/node/polyfills/internal/child_process.ts`) to use `core.loadExtScript()` instead of ESM imports - Replace `import.meta.log` calls with `internals.log` in `00_serve.ts` Follows the same pattern established in denoland#33778, denoland#33780, denoland#33784, denoland#33800, and denoland#33801.
…ripts (denoland#33818) ## Summary - Convert `ext/kv/01_db.ts` from ESM to IIFE-wrapped lazy-loaded script (`lazy_loaded_js`) - Convert `ext/webgpu/00_init.js` from ESM to IIFE-wrapped lazy-loaded script (`lazy_loaded_js`) - Update all consumers (`runtime/js/90_deno_ns.js`, `runtime/js/98_global_scope_shared.js`, `runtime/js/98_global_scope_window.js`, `runtime/js/98_global_scope_worker.js`) to use `core.loadExtScript()` instead of ESM imports - Replace `import.meta.log` call with `internals.log` in `01_db.ts` Follows the same pattern established in denoland#33778, denoland#33780, denoland#33784, denoland#33800, denoland#33801, and denoland#33817.
Summary
Follow-up to #33760. Converts the ESM JS sources in
ext/cache,ext/canvas, andext/cryptoto IIFE-wrapped lazy-loaded scripts (lazy_loaded_js), eliminating ESM module resolution overhead.core.opsinternalssince ext/fetch is still ESMkKeyObjectsymbol bridged viainternalsfromext/node; updatedext/nodeconsumers to usecore.loadExtScript()ext/cronwas skipped because01_cron.tsis TypeScript which cannot be used withlazy_loaded_js(no transpilation for scripts).Test plan
cargo checkpassescargo build --bin denosucceeds (snapshot builds)tools/format.jsandtools/lint.js --jspasscrypto,crypto.subtle,CryptoKey,caches,CacheStorage,Deno.UnsafeWindowSurface(unstable-webgpu),node:cryptoall work correctly