Skip to content

Commit d823cd5

Browse files
NathanFlurryclaude
andcommitted
feat: US-025 - Remove JS-side ESM hacks after V8 native ESM support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e0b0fa6 commit d823cd5

5 files changed

Lines changed: 28 additions & 12 deletions

File tree

packages/core/isolate-runtime/src/inject/setup-dynamic-import.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
// Browser-only fallback: V8-backed execution handles import() natively via
2+
// HostImportModuleDynamicallyCallback (US-023). This shim is only exercised
3+
// by the browser worker which doesn't use the V8 sidecar. The global is still
4+
// installed in V8 snapshots but never called since transformDynamicImport is
5+
// no longer applied to V8-loaded code (US-025).
16
import { isObjectLike } from "../common/global-access";
27
import { getRuntimeExposeCustomGlobal } from "../common/global-exposure";
38

packages/core/src/shared/esm-utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ export function isESM(code: string, filePath?: string): boolean {
1717

1818
/**
1919
* Transform dynamic import() calls to __dynamicImport() calls.
20+
*
21+
* Browser-only fallback: V8-backed execution handles import() natively via
22+
* HostImportModuleDynamicallyCallback (US-023). This transform is only needed
23+
* by the browser worker which doesn't use the V8 sidecar.
2024
*/
2125
export function transformDynamicImport(code: string): string {
2226
return code.replace(/(?<![a-zA-Z_$])import\s*\(/g, "__dynamicImport(");

packages/core/src/shared/global-exposure.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,12 +331,12 @@ export const NODE_CUSTOM_GLOBAL_INVENTORY: readonly CustomGlobalInventoryEntry[]
331331
{
332332
name: "_dynamicImport",
333333
classification: "hardened",
334-
rationale: "Runtime-owned host callback reference for dynamic import resolution.",
334+
rationale: "Runtime-owned host callback reference for dynamic import resolution. Browser-only fallback — V8-backed execution uses native HostImportModuleDynamicallyCallback (US-023).",
335335
},
336336
{
337337
name: "__dynamicImport",
338338
classification: "hardened",
339-
rationale: "Runtime-owned dynamic-import shim entrypoint.",
339+
rationale: "Runtime-owned dynamic-import shim entrypoint. Browser-only fallback — V8-backed execution uses native import() (US-023).",
340340
},
341341
{
342342
name: "_moduleCache",

packages/nodejs/src/bridge-contract.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ function valuesOf<T extends Record<string, string>>(object: T): Array<ValueOf<T>
1717

1818
/** Globals injected by the host before the bridge bundle executes. */
1919
export const HOST_BRIDGE_GLOBAL_KEYS = {
20+
/** Browser-only fallback — V8-backed execution uses native import() (US-023). */
2021
dynamicImport: "_dynamicImport",
2122
loadPolyfill: "_loadPolyfill",
2223
resolveModule: "_resolveModule",

packages/nodejs/src/bridge-handlers.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {
3535
} from "@secure-exec/core";
3636
import { normalizeBuiltinSpecifier } from "./builtin-modules.js";
3737
import { resolveModule, loadFile } from "./package-bundler.js";
38-
import { transformDynamicImport, isESM } from "@secure-exec/core/internal/shared/esm-utils";
38+
import { isESM, wrapCJSForESMWithModulePath } from "@secure-exec/core/internal/shared/esm-utils";
3939
import { bundlePolyfill, hasPolyfill } from "./polyfills.js";
4040
import { getStaticBuiltinWrapperSource, getEmptyBuiltinESMWrapper } from "./esm-compiler.js";
4141
import {
@@ -1171,16 +1171,16 @@ export function buildModuleResolutionBridgeHandlers(
11711171
};
11721172

11731173
// Sync file read — translates sandbox path and reads via readFileSync.
1174-
// Transforms dynamic import() to __dynamicImport() and converts ESM to CJS
1175-
// for npm packages so require() can load ESM-only dependencies.
1174+
// Converts ESM to CJS for npm packages so require() can load ESM-only
1175+
// dependencies. V8 handles import() natively via dynamic_import_callback
1176+
// (US-023), so no transformDynamicImport is needed here.
11761177
handlers[K.loadFileSync] = (filePath: unknown) => {
11771178
const sandboxPath = String(filePath);
11781179
const hostPath = deps.sandboxToHostPath(sandboxPath) ?? sandboxPath;
11791180

11801181
try {
1181-
let source = readFileSync(hostPath, "utf-8");
1182-
source = convertEsmToCjs(source, hostPath);
1183-
return transformDynamicImport(source);
1182+
const source = readFileSync(hostPath, "utf-8");
1183+
return convertEsmToCjs(source, hostPath);
11841184
} catch {
11851185
return null;
11861186
}
@@ -1345,8 +1345,9 @@ export function buildModuleLoadingBridgeHandlers(
13451345
};
13461346

13471347
// Dynamic import bridge — returns null to fall back to require() in the sandbox.
1348-
// V8 ESM module mode handles static imports natively via module_resolve_callback;
1349-
// this handler covers the __dynamicImport() path used in exec mode.
1348+
// No longer exercised for V8-backed execution since V8 handles import()
1349+
// natively via HostImportModuleDynamicallyCallback (US-023). Retained for
1350+
// browser worker backward compatibility where __dynamicImport() is still used.
13501351
handlers[K.dynamicImport] = async (): Promise<null> => null;
13511352

13521353
// Async file read + dynamic import transform.
@@ -1365,9 +1366,14 @@ export function buildModuleLoadingBridgeHandlers(
13651366
return `const _p = (function(){var module={exports:{}};var exports=module.exports;${code};return module.exports})();\nexport default _p;\n` +
13661367
`for(const[k,v]of Object.entries(_p)){if(k!=='default'&&/^[A-Za-z_$]/.test(k))globalThis['__esm_'+k]=v;}\n`;
13671368
}
1368-
// Regular file — keep source intact for V8 module system
1369-
// V8 handles import() natively via dynamic_import_callback (US-023)
1369+
// Regular file — V8 handles import() natively via dynamic_import_callback (US-023)
13701370
const source = await loadFile(p, deps.filesystem);
1371+
if (source === null) return null;
1372+
// Wrap CJS files as ESM so V8's module system can import them correctly
1373+
// (CJS uses module.exports which isn't available in ESM context)
1374+
if (!isESM(source, p)) {
1375+
return wrapCJSForESMWithModulePath(source, p);
1376+
}
13711377
return source;
13721378
};
13731379

0 commit comments

Comments
 (0)