@scelar/nodepod 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/__sw__.js +642 -642
  2. package/dist/{child_process-hmVqFcF7.cjs → child_process-B9qsOKHs.cjs} +7434 -7434
  3. package/dist/child_process-B9qsOKHs.cjs.map +1 -0
  4. package/dist/{child_process-D6oDN2MX.js → child_process-PY34i_6n.js} +8233 -8233
  5. package/dist/child_process-PY34i_6n.js.map +1 -0
  6. package/dist/{index-BO1i013L.cjs → index-CyhVjVJU.cjs} +38383 -37240
  7. package/dist/index-CyhVjVJU.cjs.map +1 -0
  8. package/dist/index-D8Hn2kWU.js +36455 -0
  9. package/dist/index-D8Hn2kWU.js.map +1 -0
  10. package/dist/index.cjs +67 -65
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.ts +88 -86
  13. package/dist/index.mjs +61 -59
  14. package/dist/memory-handler.d.ts +57 -0
  15. package/dist/memory-volume.d.ts +157 -147
  16. package/dist/packages/installer.d.ts +44 -41
  17. package/dist/persistence/idb-cache.d.ts +7 -0
  18. package/dist/polyfills/wasi.d.ts +45 -4
  19. package/dist/script-engine.d.ts +84 -81
  20. package/dist/sdk/nodepod-process.d.ts +29 -28
  21. package/dist/sdk/nodepod.d.ts +59 -39
  22. package/dist/sdk/types.d.ts +64 -53
  23. package/dist/threading/process-manager.d.ts +1 -1
  24. package/dist/threading/worker-protocol.d.ts +1 -1
  25. package/package.json +1 -1
  26. package/src/index.ts +194 -192
  27. package/src/memory-handler.ts +168 -0
  28. package/src/memory-volume.ts +78 -8
  29. package/src/packages/installer.ts +49 -1
  30. package/src/packages/version-resolver.ts +421 -411
  31. package/src/persistence/idb-cache.ts +107 -0
  32. package/src/polyfills/child_process.ts +2288 -2288
  33. package/src/polyfills/events.ts +6 -2
  34. package/src/polyfills/fs.ts +2888 -2888
  35. package/src/polyfills/http.ts +1450 -1449
  36. package/src/polyfills/process.ts +27 -1
  37. package/src/polyfills/stream.ts +1621 -1620
  38. package/src/polyfills/wasi.ts +1306 -44
  39. package/src/polyfills/zlib.ts +881 -881
  40. package/src/request-proxy.ts +716 -716
  41. package/src/script-engine.ts +444 -118
  42. package/src/sdk/nodepod-process.ts +94 -86
  43. package/src/sdk/nodepod.ts +571 -509
  44. package/src/sdk/types.ts +82 -70
  45. package/src/syntax-transforms.ts +2 -2
  46. package/src/threading/offload-worker.ts +383 -383
  47. package/src/threading/offload.ts +271 -271
  48. package/src/threading/process-manager.ts +967 -956
  49. package/src/threading/process-worker-entry.ts +858 -854
  50. package/src/threading/worker-protocol.ts +358 -358
  51. package/dist/child_process-D6oDN2MX.js.map +0 -1
  52. package/dist/child_process-hmVqFcF7.cjs.map +0 -1
  53. package/dist/index-Ale2oba_.js +0 -35412
  54. package/dist/index-Ale2oba_.js.map +0 -1
  55. package/dist/index-BO1i013L.cjs.map +0 -1
@@ -10,6 +10,7 @@ import type {
10
10
  } from "./engine-types";
11
11
  import type { PackageManifest } from "./types/manifest";
12
12
  import { quickDigest } from "./helpers/digest";
13
+ import { LRUCache as _LRUCache } from "./memory-handler";
13
14
  import { bytesToBase64, bytesToHex } from "./helpers/byte-encoding";
14
15
  import { buildFileSystemBridge, FsBridge } from "./polyfills/fs";
15
16
  import * as pathPolyfill from "./polyfills/path";
@@ -121,7 +122,11 @@ import {
121
122
  hasTopLevelAwait,
122
123
  stripTopLevelAwait,
123
124
  } from "./syntax-transforms";
124
- import { getCachedModule, precompileWasm, compileWasmInWorker } from "./helpers/wasm-cache";
125
+ import {
126
+ getCachedModule,
127
+ precompileWasm,
128
+ compileWasmInWorker,
129
+ } from "./helpers/wasm-cache";
125
130
  import * as acorn from "acorn";
126
131
 
127
132
  // ── TypeScript type stripper ──
@@ -224,11 +229,8 @@ function stripTypeScript(source: string): string {
224
229
 
225
230
  function isTypeScriptFile(filename: string): boolean {
226
231
  const clean = filename.split("?")[0];
227
- if (
228
- clean.endsWith(".ts") ||
229
- clean.endsWith(".tsx") ||
230
- clean.endsWith(".mts")
231
- ) return true;
232
+ if (clean.endsWith(".ts") || clean.endsWith(".tsx") || clean.endsWith(".mts"))
233
+ return true;
232
234
  // Vite SFC query params like ?type=script&lang.ts
233
235
  if (filename.includes("lang.ts") || filename.includes("lang=ts")) return true;
234
236
  return false;
@@ -245,9 +247,11 @@ function isCSSFile(filename: string): boolean {
245
247
  clean.endsWith(".styl") ||
246
248
  clean.endsWith(".stylus") ||
247
249
  clean.endsWith(".postcss")
248
- ) return true;
250
+ )
251
+ return true;
249
252
  if (filename.includes("type=style")) return true;
250
- if (/lang[.=](?:css|scss|sass|less|styl|stylus|postcss)/.test(filename)) return true;
253
+ if (/lang[.=](?:css|scss|sass|less|styl|stylus|postcss)/.test(filename))
254
+ return true;
251
255
  return false;
252
256
  }
253
257
 
@@ -255,7 +259,9 @@ function isCSSFile(filename: string): boolean {
255
259
  function looksLikeTypeScript(source: string): boolean {
256
260
  return (
257
261
  /\b(?:interface|type)\s+\w+/.test(source) ||
258
- /:\s*(?:string|number|boolean|void|any|never|unknown|Record|Array|Promise)\b/.test(source) ||
262
+ /:\s*(?:string|number|boolean|void|any|never|unknown|Record|Array|Promise)\b/.test(
263
+ source,
264
+ ) ||
259
265
  /(?:as\s+(?:string|number|boolean|any|const)\b)/.test(source)
260
266
  );
261
267
  }
@@ -299,7 +305,12 @@ function convertModuleSyntax(source: string, filePath: string): string {
299
305
  try {
300
306
  return convertViaAst(source, filePath);
301
307
  } catch (astErr) {
302
- _nativeConsole.warn("[convertModuleSyntax] AST parse failed for", filePath, "falling back to regex:", astErr instanceof Error ? astErr.message : String(astErr));
308
+ _nativeConsole.warn(
309
+ "[convertModuleSyntax] AST parse failed for",
310
+ filePath,
311
+ "falling back to regex:",
312
+ astErr instanceof Error ? astErr.message : String(astErr),
313
+ );
303
314
  return convertViaRegex(source, filePath);
304
315
  }
305
316
  }
@@ -337,7 +348,7 @@ function convertViaAst(source: string, filePath: string): string {
337
348
 
338
349
  // apply all patches in one pass
339
350
  let output = source;
340
- patches.sort((a, b) => b[0] - a[0]);
351
+ patches.sort((a, b) => b[0] - a[0] || b[1] - a[1]);
341
352
  for (const [s, e, r] of patches)
342
353
  output = output.slice(0, s) + r + output.slice(e);
343
354
 
@@ -356,10 +367,7 @@ function convertViaAst(source: string, filePath: string): string {
356
367
  // Demote `const/let require =` to plain assignment to avoid TDZ with esmToCjs-generated require() calls
357
368
  function demoteLexicalRequire(code: string): string {
358
369
  if (!/\b(?:const|let)\s+require\s*=/.test(code)) return code;
359
- return code.replace(
360
- /\b(const|let)\s+(require)\s*=/g,
361
- "require =",
362
- );
370
+ return code.replace(/\b(const|let)\s+(require)\s*=/g, "require =");
363
371
  }
364
372
 
365
373
  // Builds the IIFE wrapper that sandboxes user code with shimmed globals
@@ -632,7 +640,9 @@ function createSyncPromise(): typeof Promise {
632
640
  res(onRejected(innerErr) as TResult2),
633
641
  ) as any;
634
642
  }
635
- return new SyncPromise<TResult2>((_, rej) => rej(innerErr)) as any;
643
+ return new SyncPromise<TResult2>((_, rej) =>
644
+ rej(innerErr),
645
+ ) as any;
636
646
  }
637
647
  return NativePromise.resolve(result).then(null, onRejected) as any;
638
648
  }
@@ -670,8 +680,7 @@ function createSyncPromise(): typeof Promise {
670
680
 
671
681
  // instanceof must work for native Promises too since we inject SyncPromise as `Promise`
672
682
  Object.defineProperty(SyncPromise, Symbol.hasInstance, {
673
- value: (instance: any) =>
674
- instance instanceof NativePromise,
683
+ value: (instance: any) => instance instanceof NativePromise,
675
684
  configurable: true,
676
685
  });
677
686
 
@@ -704,11 +713,13 @@ function createSyncPromise(): typeof Promise {
704
713
  allSync = false;
705
714
  break;
706
715
  }
707
- } else if (
708
- v && typeof v === "object" && typeof v.then === "function"
709
- ) {
710
- let probed = false, pVal: any;
711
- v.then((x: any) => { pVal = x; probed = true; });
716
+ } else if (v && typeof v === "object" && typeof v.then === "function") {
717
+ let probed = false,
718
+ pVal: any;
719
+ v.then((x: any) => {
720
+ pVal = x;
721
+ probed = true;
722
+ });
712
723
  if (probed) {
713
724
  results[i] = pVal;
714
725
  } else {
@@ -742,9 +753,7 @@ function createSyncPromise(): typeof Promise {
742
753
  allSync = false;
743
754
  break;
744
755
  }
745
- } else if (
746
- v && typeof v === "object" && typeof v.then === "function"
747
- ) {
756
+ } else if (v && typeof v === "object" && typeof v.then === "function") {
748
757
  allSync = false;
749
758
  break;
750
759
  } else {
@@ -786,9 +795,7 @@ function createSyncPromise(): typeof Promise {
786
795
  if (v instanceof SyncPromise && (v as any)._syncResolved) {
787
796
  return new SyncPromise((res: any) => res((v as any)._syncValue));
788
797
  }
789
- if (
790
- !(v && typeof v === "object" && typeof v.then === "function")
791
- ) {
798
+ if (!(v && typeof v === "object" && typeof v.then === "function")) {
792
799
  return new SyncPromise((res: any) => res(v));
793
800
  }
794
801
  }
@@ -830,12 +837,16 @@ function makeDynamicLoader(
830
837
  ) {
831
838
  return new SyncThenable(loaded);
832
839
  }
833
- const spread = loaded && (typeof loaded === "object" || typeof loaded === "function")
834
- ? Object.getOwnPropertyNames(loaded as object).reduce((acc, key) => {
835
- acc[key] = (loaded as any)[key];
836
- return acc;
837
- }, {} as Record<string, unknown>)
838
- : {};
840
+ const spread =
841
+ loaded && (typeof loaded === "object" || typeof loaded === "function")
842
+ ? Object.getOwnPropertyNames(loaded as object).reduce(
843
+ (acc, key) => {
844
+ acc[key] = (loaded as any)[key];
845
+ return acc;
846
+ },
847
+ {} as Record<string, unknown>,
848
+ )
849
+ : {};
839
850
  return new SyncThenable({
840
851
  default: loaded,
841
852
  ...spread,
@@ -866,6 +877,7 @@ export interface EngineOptions {
866
877
  workerData: unknown;
867
878
  threadId: number;
868
879
  };
880
+ handler?: import('./memory-handler').MemoryHandler;
869
881
  }
870
882
 
871
883
  export interface ResolverFn {
@@ -939,8 +951,8 @@ const CORE_MODULES: Record<string, unknown> = {
939
951
  sea: seaPolyfill,
940
952
  sqlite: sqlitePolyfill,
941
953
  quic: quicPolyfill,
942
- lightningcss: lightningcssPolyfill,
943
- "@tailwindcss/oxide": tailwindOxidePolyfill,
954
+ // lightningcss: lightningcssPolyfill,
955
+ // "@tailwindcss/oxide": tailwindOxidePolyfill,
944
956
  sys: helpersPolyfill,
945
957
  "util/types": helpersPolyfill.types,
946
958
  "path/posix": pathPolyfill,
@@ -1109,10 +1121,17 @@ function buildResolver(
1109
1121
  deAsyncImports = false,
1110
1122
  ): ResolverFn {
1111
1123
  // Shared across all resolvers — avoids re-resolving the same paths/manifests per module
1124
+ // Use bounded LRU when a memory handler is available, else plain Map
1112
1125
  const resolveCache: Map<string, string | null> =
1113
- (cache as any).__resolveCache ?? ((cache as any).__resolveCache = new Map());
1126
+ (cache as any).__resolveCache ??
1127
+ ((cache as any).__resolveCache = opts.handler
1128
+ ? new _LRUCache<string, string | null>(opts.handler.options.resolveCacheSize)
1129
+ : new Map());
1114
1130
  const manifestCache: Map<string, PackageManifest | null> =
1115
- (cache as any).__manifestCache ?? ((cache as any).__manifestCache = new Map());
1131
+ (cache as any).__manifestCache ??
1132
+ ((cache as any).__manifestCache = opts.handler
1133
+ ? new _LRUCache<string, PackageManifest | null>(opts.handler.options.manifestCacheSize)
1134
+ : new Map());
1116
1135
  // Shared across all resolvers — deduplicates same-version packages from nested node_modules
1117
1136
  const _pkgIdentityMap: Record<string, string> =
1118
1137
  (cache as any).__pkgIdentityMap ?? ((cache as any).__pkgIdentityMap = {});
@@ -1234,13 +1253,19 @@ function buildResolver(
1234
1253
  sync: noop,
1235
1254
  transform: async (filename: string, code: string, opts?: any) => {
1236
1255
  code = applyDefineReplacements(code, opts?.define);
1237
- if (!isCSSFile(filename) && (isTypeScriptFile(filename) || looksLikeTypeScript(code)))
1256
+ if (
1257
+ !isCSSFile(filename) &&
1258
+ (isTypeScriptFile(filename) || looksLikeTypeScript(code))
1259
+ )
1238
1260
  code = stripTypeScript(code);
1239
1261
  return { code, map: null, errors: [], warnings: [] };
1240
1262
  },
1241
1263
  transformSync: (filename: string, code: string, opts?: any) => {
1242
1264
  code = applyDefineReplacements(code, opts?.define);
1243
- if (!isCSSFile(filename) && (isTypeScriptFile(filename) || looksLikeTypeScript(code)))
1265
+ if (
1266
+ !isCSSFile(filename) &&
1267
+ (isTypeScriptFile(filename) || looksLikeTypeScript(code))
1268
+ )
1244
1269
  code = stripTypeScript(code);
1245
1270
  return { code, map: null, errors: [], warnings: [] };
1246
1271
  },
@@ -1250,7 +1275,10 @@ function buildResolver(
1250
1275
  opts?: any,
1251
1276
  ) => {
1252
1277
  code = applyDefineReplacements(code, opts?.define);
1253
- if (!isCSSFile(filename) && (isTypeScriptFile(filename) || looksLikeTypeScript(code)))
1278
+ if (
1279
+ !isCSSFile(filename) &&
1280
+ (isTypeScriptFile(filename) || looksLikeTypeScript(code))
1281
+ )
1254
1282
  code = stripTypeScript(code);
1255
1283
  return {
1256
1284
  code,
@@ -1267,7 +1295,10 @@ function buildResolver(
1267
1295
  opts?: any,
1268
1296
  ) => {
1269
1297
  code = applyDefineReplacements(code, opts?.define);
1270
- if (!isCSSFile(filename) && (isTypeScriptFile(filename) || looksLikeTypeScript(code)))
1298
+ if (
1299
+ !isCSSFile(filename) &&
1300
+ (isTypeScriptFile(filename) || looksLikeTypeScript(code))
1301
+ )
1271
1302
  code = stripTypeScript(code);
1272
1303
  return {
1273
1304
  code,
@@ -1303,12 +1334,18 @@ function buildResolver(
1303
1334
  warnings: [],
1304
1335
  }),
1305
1336
  moduleRunnerTransform: async (filename: string, code: string) => {
1306
- if (!isCSSFile(filename) && (isTypeScriptFile(filename) || looksLikeTypeScript(code)))
1337
+ if (
1338
+ !isCSSFile(filename) &&
1339
+ (isTypeScriptFile(filename) || looksLikeTypeScript(code))
1340
+ )
1307
1341
  code = stripTypeScript(code);
1308
1342
  return { code, map: null, deps: [], dynamicDeps: [], errors: [] };
1309
1343
  },
1310
1344
  moduleRunnerTransformSync: (filename: string, code: string) => {
1311
- if (!isCSSFile(filename) && (isTypeScriptFile(filename) || looksLikeTypeScript(code)))
1345
+ if (
1346
+ !isCSSFile(filename) &&
1347
+ (isTypeScriptFile(filename) || looksLikeTypeScript(code))
1348
+ )
1312
1349
  code = stripTypeScript(code);
1313
1350
  return { code, map: null, deps: [], dynamicDeps: [], errors: [] };
1314
1351
  },
@@ -1404,7 +1441,9 @@ function buildResolver(
1404
1441
  )
1405
1442
  specs.push(node.source.value);
1406
1443
  }
1407
- } catch { /* fallback to regex */ }
1444
+ } catch {
1445
+ /* fallback to regex */
1446
+ }
1408
1447
  const dynRe = /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
1409
1448
  let m;
1410
1449
  while ((m = dynRe.exec(code)) !== null) specs.push(m[1]);
@@ -1416,8 +1455,14 @@ function buildResolver(
1416
1455
  try {
1417
1456
  await p.buildStart(mockCtx, {});
1418
1457
  } catch (hookErr) {
1419
- const msg = hookErr instanceof Error ? hookErr.message : String(hookErr);
1420
- _nativeConsole.warn("[rolldown scan] buildStart hook error:", msg);
1458
+ const msg =
1459
+ hookErr instanceof Error
1460
+ ? hookErr.message
1461
+ : String(hookErr);
1462
+ _nativeConsole.warn(
1463
+ "[rolldown scan] buildStart hook error:",
1464
+ msg,
1465
+ );
1421
1466
  }
1422
1467
  }
1423
1468
  }
@@ -1446,8 +1491,15 @@ function buildResolver(
1446
1491
  if (r && typeof r === "object" && r.code)
1447
1492
  return r.code;
1448
1493
  } catch (loadErr) {
1449
- const msg = loadErr instanceof Error ? loadErr.message : String(loadErr);
1450
- _nativeConsole.warn("[rolldown scan] load hook error:", filePath, msg);
1494
+ const msg =
1495
+ loadErr instanceof Error
1496
+ ? loadErr.message
1497
+ : String(loadErr);
1498
+ _nativeConsole.warn(
1499
+ "[rolldown scan] load hook error:",
1500
+ filePath,
1501
+ msg,
1502
+ );
1451
1503
  }
1452
1504
  }
1453
1505
  }
@@ -1487,8 +1539,15 @@ function buildResolver(
1487
1539
  custom: undefined,
1488
1540
  });
1489
1541
  } catch (resolveErr) {
1490
- const msg = resolveErr instanceof Error ? resolveErr.message : String(resolveErr);
1491
- _nativeConsole.warn("[rolldown scan] resolveId hook error:", spec, msg);
1542
+ const msg =
1543
+ resolveErr instanceof Error
1544
+ ? resolveErr.message
1545
+ : String(resolveErr);
1546
+ _nativeConsole.warn(
1547
+ "[rolldown scan] resolveId hook error:",
1548
+ spec,
1549
+ msg,
1550
+ );
1492
1551
  }
1493
1552
  }
1494
1553
  }
@@ -1527,14 +1586,24 @@ function buildResolver(
1527
1586
  try {
1528
1587
  await p.buildEnd(mockCtx);
1529
1588
  } catch (endErr) {
1530
- const msg = endErr instanceof Error ? endErr.message : String(endErr);
1531
- _nativeConsole.warn("[rolldown scan] buildEnd hook error:", msg);
1589
+ const msg =
1590
+ endErr instanceof Error
1591
+ ? endErr.message
1592
+ : String(endErr);
1593
+ _nativeConsole.warn(
1594
+ "[rolldown scan] buildEnd hook error:",
1595
+ msg,
1596
+ );
1532
1597
  }
1533
1598
  }
1534
1599
  }
1535
1600
  } catch (scanErr) {
1536
- const msg = scanErr instanceof Error ? scanErr.message : String(scanErr);
1537
- const detail = scanErr instanceof Error && scanErr.stack ? `\n${scanErr.stack}` : "";
1601
+ const msg =
1602
+ scanErr instanceof Error ? scanErr.message : String(scanErr);
1603
+ const detail =
1604
+ scanErr instanceof Error && scanErr.stack
1605
+ ? `\n${scanErr.stack}`
1606
+ : "";
1538
1607
  _nativeConsole.warn("[rolldown scan]", msg);
1539
1608
  }
1540
1609
  }
@@ -1603,11 +1672,14 @@ function buildResolver(
1603
1672
  const parts = afterNm.split("/");
1604
1673
  const scoped = parts[0].startsWith("@");
1605
1674
  const pkgName = scoped ? parts.slice(0, 2).join("/") : parts[0];
1606
- const pkgRoot = origPath.slice(0, nmIdx) + "/node_modules/" + pkgName;
1675
+ const pkgRoot =
1676
+ origPath.slice(0, nmIdx) + "/node_modules/" + pkgName;
1607
1677
  const pkgJsonPath = pkgRoot + "/package.json";
1608
1678
  try {
1609
1679
  if (!vol.existsSync(pkgJsonPath)) return null;
1610
- const manifest = JSON.parse(vol.readFileSync(pkgJsonPath, "utf8"));
1680
+ const manifest = JSON.parse(
1681
+ vol.readFileSync(pkgJsonPath, "utf8"),
1682
+ );
1611
1683
  for (const conds of [
1612
1684
  { browser: true, import: true },
1613
1685
  { import: true },
@@ -1616,11 +1688,16 @@ function buildResolver(
1616
1688
  const resolved = resolveExports(manifest, ".", conds);
1617
1689
  if (resolved?.length) {
1618
1690
  const full = pathPolyfill.join(pkgRoot, resolved[0]);
1619
- if (vol.existsSync(full) && full !== origPath) return full;
1691
+ if (vol.existsSync(full) && full !== origPath)
1692
+ return full;
1620
1693
  }
1621
- } catch { /* try next */ }
1694
+ } catch {
1695
+ /* try next */
1696
+ }
1622
1697
  }
1623
- } catch { /* can't re-resolve */ }
1698
+ } catch {
1699
+ /* can't re-resolve */
1700
+ }
1624
1701
  return null;
1625
1702
  };
1626
1703
 
@@ -1646,7 +1723,10 @@ function buildResolver(
1646
1723
  target: "esnext",
1647
1724
  logLevel: "warning",
1648
1725
  });
1649
- return result.outputFiles?.[0]?.text ?? vol.readFileSync(ep, "utf8");
1726
+ return (
1727
+ result.outputFiles?.[0]?.text ??
1728
+ vol.readFileSync(ep, "utf8")
1729
+ );
1650
1730
  };
1651
1731
 
1652
1732
  try {
@@ -1669,8 +1749,15 @@ function buildResolver(
1669
1749
  }
1670
1750
  }
1671
1751
  } catch (buildErr) {
1672
- const buildMsg = buildErr instanceof Error ? buildErr.message : String(buildErr);
1673
- _nativeConsole.warn("[rolldown bundle] esbuild failed for", entryPath, buildMsg);
1752
+ const buildMsg =
1753
+ buildErr instanceof Error
1754
+ ? buildErr.message
1755
+ : String(buildErr);
1756
+ _nativeConsole.warn(
1757
+ "[rolldown bundle] esbuild failed for",
1758
+ entryPath,
1759
+ buildMsg,
1760
+ );
1674
1761
  try {
1675
1762
  code = vol.readFileSync(entryPath, "utf8");
1676
1763
  } catch {
@@ -1770,10 +1857,13 @@ function buildResolver(
1770
1857
  if (resolved) {
1771
1858
  return { id: resolved + querySuffix };
1772
1859
  }
1773
- } catch (_e) { /* not found */ }
1860
+ } catch (_e) {
1861
+ /* not found */
1862
+ }
1774
1863
 
1775
1864
  if (candidate.startsWith("/")) {
1776
- if (vol.existsSync(candidate)) return { id: candidate + querySuffix };
1865
+ if (vol.existsSync(candidate))
1866
+ return { id: candidate + querySuffix };
1777
1867
  const cwd =
1778
1868
  (typeof globalThis !== "undefined" &&
1779
1869
  (globalThis as any).process?.cwd?.()) ||
@@ -1782,7 +1872,8 @@ function buildResolver(
1782
1872
  const abs = cwd + candidate;
1783
1873
  if (vol.existsSync(abs)) return { id: abs + querySuffix };
1784
1874
  for (const ext of RESOLVE_EXTENSIONS) {
1785
- if (vol.existsSync(abs + ext)) return { id: abs + ext + querySuffix };
1875
+ if (vol.existsSync(abs + ext))
1876
+ return { id: abs + ext + querySuffix };
1786
1877
  }
1787
1878
  }
1788
1879
  }
@@ -2079,7 +2170,9 @@ function buildResolver(
2079
2170
  const cached = resolveCache.get(cacheKey);
2080
2171
  if (cached !== undefined) {
2081
2172
  if (cached === null) {
2082
- const e = new Error(`Cannot find module '${id}'`) as Error & { code: string };
2173
+ const e = new Error(`Cannot find module '${id}'`) as Error & {
2174
+ code: string;
2175
+ };
2083
2176
  e.code = "MODULE_NOT_FOUND";
2084
2177
  throw e;
2085
2178
  }
@@ -2124,7 +2217,9 @@ function buildResolver(
2124
2217
  }
2125
2218
 
2126
2219
  resolveCache.set(cacheKey, null);
2127
- const e = new Error(`Cannot find module '${id}' from '${fromDir}'`) as Error & { code: string };
2220
+ const e = new Error(
2221
+ `Cannot find module '${id}' from '${fromDir}'`,
2222
+ ) as Error & { code: string };
2128
2223
  e.code = "MODULE_NOT_FOUND";
2129
2224
  throw e;
2130
2225
  }
@@ -2173,7 +2268,8 @@ function buildResolver(
2173
2268
  for (let ai = 0; ai < execArgv.length; ai++) {
2174
2269
  const arg = execArgv[ai];
2175
2270
  if (arg === "--conditions" || arg === "-C") {
2176
- if (ai + 1 < execArgv.length) extraConditions.push(execArgv[++ai]);
2271
+ if (ai + 1 < execArgv.length)
2272
+ extraConditions.push(execArgv[++ai]);
2177
2273
  } else if (arg.startsWith("--conditions=")) {
2178
2274
  extraConditions.push(arg.slice("--conditions=".length));
2179
2275
  }
@@ -2181,9 +2277,8 @@ function buildResolver(
2181
2277
  const nodeOpts = proc.env?.NODE_OPTIONS || "";
2182
2278
  const condMatch = nodeOpts.matchAll(/(?:--conditions[= ]|-C )(\S+)/g);
2183
2279
  for (const m of condMatch) extraConditions.push(m[1]);
2184
- const condExtra = extraConditions.length > 0
2185
- ? { conditions: extraConditions }
2186
- : {};
2280
+ const condExtra =
2281
+ extraConditions.length > 0 ? { conditions: extraConditions } : {};
2187
2282
 
2188
2283
  const baseSets: Record<string, unknown>[] = preferEsm
2189
2284
  ? [
@@ -2275,12 +2370,17 @@ function buildResolver(
2275
2370
  }
2276
2371
 
2277
2372
  resolveCache.set(cacheKey, null);
2278
- const e = new Error(`Cannot find module '${id}' from '${fromDir}'`) as Error & { code: string };
2373
+ const e = new Error(
2374
+ `Cannot find module '${id}' from '${fromDir}'`,
2375
+ ) as Error & { code: string };
2279
2376
  e.code = "MODULE_NOT_FOUND";
2280
2377
  throw e;
2281
2378
  };
2282
2379
 
2283
- const loadModule = (resolved: string, parentRecord?: ModuleRecord): ModuleRecord => {
2380
+ const loadModule = (
2381
+ resolved: string,
2382
+ parentRecord?: ModuleRecord,
2383
+ ): ModuleRecord => {
2284
2384
  if (cache[resolved]) return cache[resolved];
2285
2385
 
2286
2386
  // Package dedup: reuse first instance of name@version:path to prevent
@@ -2289,12 +2389,17 @@ function buildResolver(
2289
2389
  if (nmIdx !== -1) {
2290
2390
  const afterNm = resolved.slice(nmIdx + "/node_modules/".length);
2291
2391
  const parts = afterNm.split("/");
2292
- const pkgName = parts[0].startsWith("@") ? parts[0] + "/" + parts[1] : parts[0];
2392
+ const pkgName = parts[0].startsWith("@")
2393
+ ? parts[0] + "/" + parts[1]
2394
+ : parts[0];
2293
2395
  const pkgDir = resolved.slice(0, nmIdx) + "/node_modules/" + pkgName;
2294
2396
  try {
2295
- const pkgJson = JSON.parse(vol.readFileSync(pkgDir + "/package.json", "utf8"));
2397
+ const pkgJson = JSON.parse(
2398
+ vol.readFileSync(pkgDir + "/package.json", "utf8"),
2399
+ );
2296
2400
  // Include file path so different subpath exports (svelte vs svelte/compiler) aren't deduped
2297
- const identity = pkgName + "@" + (pkgJson.version || "0.0.0") + ":" + afterNm;
2401
+ const identity =
2402
+ pkgName + "@" + (pkgJson.version || "0.0.0") + ":" + afterNm;
2298
2403
  if (!_pkgIdentityMap[identity]) {
2299
2404
  _pkgIdentityMap[identity] = resolved;
2300
2405
  } else if (_pkgIdentityMap[identity] !== resolved) {
@@ -2305,7 +2410,9 @@ function buildResolver(
2305
2410
  return cache[canonical];
2306
2411
  }
2307
2412
  }
2308
- } catch { /* no package.json */ }
2413
+ } catch {
2414
+ /* no package.json */
2415
+ }
2309
2416
  }
2310
2417
 
2311
2418
  const _loadDepth = ((globalThis as any).__loadModuleDepth ?? 0) + 1;
@@ -2334,11 +2441,15 @@ function buildResolver(
2334
2441
  return record;
2335
2442
  }
2336
2443
 
2337
- // Native addons can't run in browser — return empty module for fallback
2444
+ // Native .node addons cannot run in the browser — throw so that
2445
+ // callers (e.g. napi-rs packages) fall back to their WASM build.
2338
2446
  if (resolved.endsWith(".node")) {
2339
- record.exports = {};
2340
- record.loaded = true;
2341
- return record;
2447
+ delete cache[resolved];
2448
+ const e = new Error(
2449
+ `Cannot load native addon '${resolved}' — native .node binaries are not supported in the browser`,
2450
+ ) as Error & { code: string };
2451
+ e.code = "ERR_DLOPEN_FAILED";
2452
+ throw e;
2342
2453
  }
2343
2454
 
2344
2455
  const rawSource = vol.readFileSync(resolved, "utf8");
@@ -2366,12 +2477,19 @@ function buildResolver(
2366
2477
  const cjsPatches: Array<[number, number, string]> = [];
2367
2478
  const walkCjs = (node: any) => {
2368
2479
  if (!node || typeof node !== "object") return;
2369
- if (Array.isArray(node)) { for (const c of node) walkCjs(c); return; }
2480
+ if (Array.isArray(node)) {
2481
+ for (const c of node) walkCjs(c);
2482
+ return;
2483
+ }
2370
2484
  if (typeof node.type !== "string") return;
2371
2485
  if (node.type === "ImportExpression") {
2372
2486
  cjsPatches.push([node.start, node.start + 6, "__asyncLoad"]);
2373
2487
  }
2374
- if (node.type === "MetaProperty" && node.meta?.name === "import" && node.property?.name === "meta") {
2488
+ if (
2489
+ node.type === "MetaProperty" &&
2490
+ node.meta?.name === "import" &&
2491
+ node.property?.name === "meta"
2492
+ ) {
2375
2493
  cjsPatches.push([
2376
2494
  node.start,
2377
2495
  node.end,
@@ -2386,12 +2504,17 @@ function buildResolver(
2386
2504
  };
2387
2505
  walkCjs(cjsAst);
2388
2506
  if (cjsPatches.length > 0) {
2389
- cjsPatches.sort((a, b) => b[0] - a[0]);
2507
+ cjsPatches.sort((a, b) => b[0] - a[0] || b[1] - a[1]);
2390
2508
  for (const [start, end, replacement] of cjsPatches) {
2391
- processedCode = processedCode.slice(0, start) + replacement + processedCode.slice(end);
2509
+ processedCode =
2510
+ processedCode.slice(0, start) +
2511
+ replacement +
2512
+ processedCode.slice(end);
2392
2513
  }
2393
2514
  }
2394
- } catch { /* can't parse — leave untransformed */ }
2515
+ } catch {
2516
+ /* can't parse — leave untransformed */
2517
+ }
2395
2518
  } else {
2396
2519
  processedCode = convertModuleSyntax(processedCode, resolved);
2397
2520
  }
@@ -2401,7 +2524,11 @@ function buildResolver(
2401
2524
  const isCjs = resolved.endsWith(".cjs");
2402
2525
  const moduleHasTLA = !isCjs && hasTopLevelAwait(processedCode);
2403
2526
  const useFullDeAsync = deAsyncImports || moduleHasTLA;
2404
- if (!isCjs) processedCode = stripTopLevelAwait(processedCode, deAsyncImports ? "full" : "topLevelOnly");
2527
+ if (!isCjs)
2528
+ processedCode = stripTopLevelAwait(
2529
+ processedCode,
2530
+ deAsyncImports ? "full" : "topLevelOnly",
2531
+ );
2405
2532
 
2406
2533
  const childResolver = buildResolver(
2407
2534
  vol,
@@ -2432,7 +2559,9 @@ function buildResolver(
2432
2559
  }
2433
2560
 
2434
2561
  {
2435
- const srcMap = (globalThis as any).__dbgSrcMap || ((globalThis as any).__dbgSrcMap = new Map());
2562
+ const srcMap =
2563
+ (globalThis as any).__dbgSrcMap ||
2564
+ ((globalThis as any).__dbgSrcMap = new Map());
2436
2565
  srcMap.set(resolved, wrapper);
2437
2566
  }
2438
2567
 
@@ -2521,7 +2650,7 @@ function buildResolver(
2521
2650
  if (typeof id !== "string") {
2522
2651
  // Match real Node.js error: TypeError with ERR_INVALID_ARG_TYPE code
2523
2652
  const err: any = new TypeError(
2524
- `The "id" argument must be of type string. Received ${id === null ? "null" : typeof id}`
2653
+ `The "id" argument must be of type string. Received ${id === null ? "null" : typeof id}`,
2525
2654
  );
2526
2655
  err.code = "ERR_INVALID_ARG_TYPE";
2527
2656
  throw err;
@@ -2535,7 +2664,11 @@ function buildResolver(
2535
2664
  if (id === "worker_threads") {
2536
2665
  if (opts.workerThreadsOverride) {
2537
2666
  const base = CORE_MODULES["worker_threads"];
2538
- const override = Object.assign(Object.create(null), base, opts.workerThreadsOverride);
2667
+ const override = Object.assign(
2668
+ Object.create(null),
2669
+ base,
2670
+ opts.workerThreadsOverride,
2671
+ );
2539
2672
  override.default = override;
2540
2673
  return override;
2541
2674
  }
@@ -2558,7 +2691,16 @@ function buildResolver(
2558
2691
  fromPath = fromPath.slice(1);
2559
2692
  }
2560
2693
  const childDir = pathPolyfill.dirname(fromPath);
2561
- const child = buildResolver(vol, fsBridge, proc, childDir, cache, opts, codeCache, deAsyncImports);
2694
+ const child = buildResolver(
2695
+ vol,
2696
+ fsBridge,
2697
+ proc,
2698
+ childDir,
2699
+ cache,
2700
+ opts,
2701
+ codeCache,
2702
+ deAsyncImports,
2703
+ );
2562
2704
  child.cache = cache;
2563
2705
  return child;
2564
2706
  };
@@ -2572,9 +2714,7 @@ function buildResolver(
2572
2714
  options?: any,
2573
2715
  ) => {
2574
2716
  if (typeof request !== "string") {
2575
- const err: any = new Error(
2576
- `Cannot find module '${request}'`,
2577
- );
2717
+ const err: any = new Error(`Cannot find module '${request}'`);
2578
2718
  err.code = "MODULE_NOT_FOUND";
2579
2719
  throw err;
2580
2720
  }
@@ -2605,14 +2745,16 @@ function buildResolver(
2605
2745
  try {
2606
2746
  return resolveId(request, fromDir);
2607
2747
  } catch {
2608
- const err: any = new Error(
2609
- `Cannot find module '${request}'`,
2610
- );
2748
+ const err: any = new Error(`Cannot find module '${request}'`);
2611
2749
  err.code = "MODULE_NOT_FOUND";
2612
2750
  throw err;
2613
2751
  }
2614
2752
  };
2615
- PerEngineModule._load = (request: string, parent?: any, isMain?: boolean) => {
2753
+ PerEngineModule._load = (
2754
+ request: string,
2755
+ parent?: any,
2756
+ isMain?: boolean,
2757
+ ) => {
2616
2758
  try {
2617
2759
  return resolver(request);
2618
2760
  } catch {
@@ -2640,10 +2782,81 @@ function buildResolver(
2640
2782
  }
2641
2783
  if (CORE_MODULES[id]) return CORE_MODULES[id];
2642
2784
 
2643
- const resolved = resolveId(id, baseDir);
2785
+ let resolved: string;
2786
+ try {
2787
+ resolved = resolveId(id, baseDir);
2788
+ } catch (resolveErr: any) {
2789
+ if (
2790
+ resolveErr?.code === "MODULE_NOT_FOUND" &&
2791
+ !id.startsWith("./") &&
2792
+ !id.startsWith("../")
2793
+ ) {
2794
+ // --- Determine WASM alternative package name(s) to try ---
2795
+ const wasmAlts: string[] = [];
2796
+
2797
+ if (id.includes("wasm32-wasi")) {
2798
+ // Explicit wasm32-wasi package — auto-install as-is
2799
+ wasmAlts.push(id);
2800
+ } else {
2801
+ // Platform-specific native package pattern:
2802
+ // {name}-{platform}-{arch}[-{abi}]
2803
+ // e.g. lightningcss-linux-x64-gnu, @pkg/core-darwin-arm64
2804
+ const platformRe =
2805
+ /^(.+)-(darwin|linux|win32|freebsd|android|sunos)-(x64|x86|arm64|arm|ia32|s390x|ppc64|mips64el|riscv64)(-[a-z]+)?$/;
2806
+ const m = id.match(platformRe);
2807
+ if (m) {
2808
+ const baseName = m[1]; // e.g. "lightningcss" or "@scope/pkg"
2809
+ wasmAlts.push(baseName + "-wasm32-wasi");
2810
+ wasmAlts.push(baseName + "-wasm");
2811
+ }
2812
+ }
2813
+
2814
+ // Try resolving any of the WASM alternatives (already installed)
2815
+ for (const alt of wasmAlts) {
2816
+ resolveCache.delete(`${baseDir}|${alt}`);
2817
+ try {
2818
+ const altResolved = resolveId(alt, baseDir);
2819
+ const altRec = loadModule(altResolved, resolver._ownerRecord);
2820
+ return altRec.exports;
2821
+ } catch {
2822
+ // not installed yet
2823
+ }
2824
+ }
2825
+
2826
+ }
2827
+ throw resolveErr;
2828
+ }
2644
2829
  if (CORE_MODULES[resolved]) return CORE_MODULES[resolved];
2645
2830
 
2646
- const rec = loadModule(resolved, resolver._ownerRecord);
2831
+ let rec: ModuleRecord;
2832
+ try {
2833
+ rec = loadModule(resolved, resolver._ownerRecord);
2834
+ } catch (loadErr: any) {
2835
+ // When a bare module fails to load (e.g. native binding not found inside
2836
+ // the module), try a WASM drop-in replacement: {name}-wasm or {name}-wasm32-wasi
2837
+ if (
2838
+ !id.startsWith("./") &&
2839
+ !id.startsWith("../") &&
2840
+ !id.startsWith("/") &&
2841
+ (loadErr?.code === "MODULE_NOT_FOUND" ||
2842
+ loadErr?.code === "ERR_DLOPEN_FAILED" ||
2843
+ (loadErr?.message &&
2844
+ /cannot\s+(find|load)\s+(module|native)/i.test(loadErr.message)))
2845
+ ) {
2846
+ const wasmAlts = [id + "-wasm32-wasi", id + "-wasm"];
2847
+ for (const alt of wasmAlts) {
2848
+ try {
2849
+ resolveCache.delete(`${baseDir}|${alt}`);
2850
+ const altResolved = resolveId(alt, baseDir);
2851
+ const altRec = loadModule(altResolved, resolver._ownerRecord);
2852
+ return altRec.exports;
2853
+ } catch {
2854
+ // not available
2855
+ }
2856
+ }
2857
+ }
2858
+ throw loadErr;
2859
+ }
2647
2860
  // Proxy for async WASM — reads from rec.exports at access time so
2648
2861
  // reassigned module.exports is picked up after compilation finishes
2649
2862
  if ((rec as any).__wasmReady) {
@@ -2707,9 +2920,15 @@ export class ScriptEngine {
2707
2920
  private proc: ProcessObject;
2708
2921
  private moduleRegistry: Record<string, ModuleRecord> = {};
2709
2922
  private opts: EngineOptions;
2710
- private transformCache: Map<string, string> = new Map();
2923
+ private transformCache: Map<string, string>;
2711
2924
 
2712
2925
  constructor(vol: MemoryVolume, opts: EngineOptions = {}) {
2926
+ // Use handler's LRU transform cache if available, else a plain Map
2927
+ if (opts.handler) {
2928
+ this.transformCache = opts.handler.transformCache as unknown as Map<string, string>;
2929
+ } else {
2930
+ this.transformCache = new Map();
2931
+ }
2713
2932
  this.vol = vol;
2714
2933
  this.proc = buildProcessEnv({
2715
2934
  cwd: opts.cwd || "/",
@@ -2735,6 +2954,62 @@ export class ScriptEngine {
2735
2954
 
2736
2955
  (globalThis as any).__nodepodVolume = vol;
2737
2956
 
2957
+ // Intercept fetch() for file:// URLs — serve from VFS instead of network.
2958
+ // napi-rs wasm32-wasi packages use fetch(new URL('file.wasm', import.meta.url))
2959
+ // which browsers block. This patches fetch to read from the in-memory filesystem.
2960
+ if (!(globalThis.fetch as any).__nodepodPatched) {
2961
+ const origFetch = globalThis.fetch.bind(globalThis);
2962
+ const patchedFetch = (
2963
+ input: RequestInfo | URL,
2964
+ init?: RequestInit,
2965
+ ): Promise<Response> => {
2966
+ let url: string | undefined;
2967
+ if (typeof input === "string") url = input;
2968
+ else if (input instanceof URL) url = input.href;
2969
+ else if (input instanceof Request) url = input.url;
2970
+
2971
+ if (url?.startsWith("file://")) {
2972
+ // Convert file:// URL to VFS path
2973
+ let vfsPath: string;
2974
+ try {
2975
+ vfsPath = decodeURIComponent(new URL(url).pathname);
2976
+ } catch {
2977
+ vfsPath = decodeURIComponent(url.slice(7));
2978
+ }
2979
+ const v = (globalThis as any).__nodepodVolume as MemoryVolume | undefined;
2980
+ if (v) {
2981
+ try {
2982
+ const data = v.readFileSync(vfsPath);
2983
+ const bytes =
2984
+ data instanceof Uint8Array
2985
+ ? data
2986
+ : new TextEncoder().encode(String(data));
2987
+ const contentType = vfsPath.endsWith(".wasm")
2988
+ ? "application/wasm"
2989
+ : "application/octet-stream";
2990
+ return Promise.resolve(
2991
+ new Response(bytes.buffer.slice(
2992
+ bytes.byteOffset,
2993
+ bytes.byteOffset + bytes.byteLength,
2994
+ ) as ArrayBuffer, {
2995
+ status: 200,
2996
+ headers: { "Content-Type": contentType },
2997
+ }),
2998
+ );
2999
+ } catch {
3000
+ return Promise.resolve(
3001
+ new Response("Not found", { status: 404 }),
3002
+ );
3003
+ }
3004
+ }
3005
+ }
3006
+ return origFetch(input, init);
3007
+ };
3008
+ (globalThis as any).fetch = Object.assign(patchedFetch, {
3009
+ __nodepodPatched: true,
3010
+ });
3011
+ }
3012
+
2738
3013
  if (typeof globalThis.setImmediate === "undefined") {
2739
3014
  (globalThis as any).setImmediate = (
2740
3015
  fn: (...a: unknown[]) => void,
@@ -2766,7 +3041,9 @@ export class ScriptEngine {
2766
3041
  const cached2 = getCachedModule(bytes);
2767
3042
  if (cached2) return cached2;
2768
3043
  const compilePromise = compileWasmInWorker(
2769
- bytes instanceof ArrayBuffer ? new Uint8Array(bytes) : bytes as Uint8Array,
3044
+ bytes instanceof ArrayBuffer
3045
+ ? new Uint8Array(bytes)
3046
+ : (bytes as Uint8Array),
2770
3047
  );
2771
3048
  (globalThis as any).__wasmCompilePromise = compilePromise;
2772
3049
  }
@@ -2793,11 +3070,23 @@ export class ScriptEngine {
2793
3070
  const obj = {
2794
3071
  _id: id,
2795
3072
  _ref: true,
2796
- ref() { obj._ref = true; return obj; },
2797
- unref() { obj._ref = false; return obj; },
2798
- hasRef() { return obj._ref; },
2799
- refresh() { return obj; },
2800
- [Symbol.toPrimitive]() { return id; },
3073
+ ref() {
3074
+ obj._ref = true;
3075
+ return obj;
3076
+ },
3077
+ unref() {
3078
+ obj._ref = false;
3079
+ return obj;
3080
+ },
3081
+ hasRef() {
3082
+ return obj._ref;
3083
+ },
3084
+ refresh() {
3085
+ return obj;
3086
+ },
3087
+ [Symbol.toPrimitive]() {
3088
+ return id;
3089
+ },
2801
3090
  };
2802
3091
  return obj;
2803
3092
  };
@@ -2806,11 +3095,23 @@ export class ScriptEngine {
2806
3095
  const obj = {
2807
3096
  _id: id,
2808
3097
  _ref: true,
2809
- ref() { obj._ref = true; return obj; },
2810
- unref() { obj._ref = false; return obj; },
2811
- hasRef() { return obj._ref; },
2812
- refresh() { return obj; },
2813
- [Symbol.toPrimitive]() { return id; },
3098
+ ref() {
3099
+ obj._ref = true;
3100
+ return obj;
3101
+ },
3102
+ unref() {
3103
+ obj._ref = false;
3104
+ return obj;
3105
+ },
3106
+ hasRef() {
3107
+ return obj._ref;
3108
+ },
3109
+ refresh() {
3110
+ return obj;
3111
+ },
3112
+ [Symbol.toPrimitive]() {
3113
+ return id;
3114
+ },
2814
3115
  };
2815
3116
  return obj;
2816
3117
  };
@@ -3051,6 +3352,7 @@ export class ScriptEngine {
3051
3352
  code: string,
3052
3353
  filename: string = "/index.js",
3053
3354
  ): { exports: unknown; module: ModuleRecord } {
3355
+ this._trimModuleCache();
3054
3356
  const dir = pathPolyfill.dirname(filename);
3055
3357
  // Only write when the content differs to avoid triggering file watchers
3056
3358
  // (e.g. nodemon/chokidar) with a no-op write that causes restart loops.
@@ -3090,12 +3392,19 @@ export class ScriptEngine {
3090
3392
  const cjsPatches: Array<[number, number, string]> = [];
3091
3393
  const walkCjs = (node: any) => {
3092
3394
  if (!node || typeof node !== "object") return;
3093
- if (Array.isArray(node)) { for (const c of node) walkCjs(c); return; }
3395
+ if (Array.isArray(node)) {
3396
+ for (const c of node) walkCjs(c);
3397
+ return;
3398
+ }
3094
3399
  if (typeof node.type !== "string") return;
3095
3400
  if (node.type === "ImportExpression") {
3096
3401
  cjsPatches.push([node.start, node.start + 6, "__asyncLoad"]);
3097
3402
  }
3098
- if (node.type === "MetaProperty" && node.meta?.name === "import" && node.property?.name === "meta") {
3403
+ if (
3404
+ node.type === "MetaProperty" &&
3405
+ node.meta?.name === "import" &&
3406
+ node.property?.name === "meta"
3407
+ ) {
3099
3408
  cjsPatches.push([
3100
3409
  node.start,
3101
3410
  node.end,
@@ -3112,10 +3421,13 @@ export class ScriptEngine {
3112
3421
  if (cjsPatches.length > 0) {
3113
3422
  cjsPatches.sort((a, b) => b[0] - a[0]);
3114
3423
  for (const [start, end, replacement] of cjsPatches) {
3115
- processed = processed.slice(0, start) + replacement + processed.slice(end);
3424
+ processed =
3425
+ processed.slice(0, start) + replacement + processed.slice(end);
3116
3426
  }
3117
3427
  }
3118
- } catch { /* can't parse */ }
3428
+ } catch {
3429
+ /* can't parse */
3430
+ }
3119
3431
  } else {
3120
3432
  processed = convertModuleSyntax(processed, filename);
3121
3433
  }
@@ -3310,6 +3622,20 @@ export class ScriptEngine {
3310
3622
  (this.moduleRegistry as any).__resolveCache?.clear();
3311
3623
  (this.moduleRegistry as any).__manifestCache?.clear();
3312
3624
  delete (this.moduleRegistry as any).__pkgIdentityMap;
3625
+ this.transformCache.clear();
3626
+ }
3627
+
3628
+ /** Evict one node_modules entry when module cache exceeds soft limit. */
3629
+ private _trimModuleCache(): void {
3630
+ const limit = this.opts.handler?.options.moduleSoftCacheSize ?? 512;
3631
+ const keys = Object.keys(this.moduleRegistry);
3632
+ if (keys.length < limit) return;
3633
+ for (const k of keys) {
3634
+ if (k.includes('/node_modules/')) {
3635
+ delete this.moduleRegistry[k];
3636
+ return; // One eviction per call — amortized O(1)
3637
+ }
3638
+ }
3313
3639
  }
3314
3640
 
3315
3641
  getVolume(): MemoryVolume {