current commit
261028.1 · module: cache compiled WebAssembly modules in the compile cache
'use strict';

const {
  ArrayPrototypePush,
  FunctionPrototypeCall,
  JSONParse,
  ObjectAssign,
  ObjectPrototypeHasOwnProperty,
  SafeMap,
  SafeSet,
  SafeWeakMap,
  StringPrototypeIncludes,
  StringPrototypeReplaceAll,
  StringPrototypeSlice,
  StringPrototypeStartsWith,
  Uint8Array,
  globalThis,
} = primordials;

const {
  compileFunctionForCJSLoader,
} = internalBinding('contextify');

const { BuiltinModule } = require('internal/bootstrap/realm');
const assert = require('internal/assert');
const { dirname, extname } = require('path');
const {
  assertBufferSource,
  loadBuiltinModule,
  stringify,
  stripBOM,
  urlToFilename,
} = require('internal/modules/helpers');
function stripTypeScriptModuleTypes(source, url) {
  // Only needed for TypeScript sources; keep it out of the loader's startup path.
  return require('internal/modules/typescript').stripTypeScriptModuleTypes(source, url);
}
const {
  kIsCachedByESMLoader,
  Module: CJSModule,
  wrapModuleLoad,
  kModuleSource,
  kModuleExport,
  kModuleExportNames,
  findLongestRegisteredExtension,
  resolveForCJSWithHooks,
  loadSourceForCJSWithHooks,
  populateCJSExportsFromESM,
} = require('internal/modules/cjs/loader');
const { fileURLToPath, pathToFileURL, URL } = require('internal/url');
const { isAnyArrayBuffer } = require('internal/util/types');
let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {
  debug = fn;
});
const { emitExperimentalWarning, kEmptyObject, setOwnProperty, isWindows } = require('internal/util');
const {
  ERR_INVALID_RETURN_PROPERTY_VALUE,
  ERR_UNKNOWN_BUILTIN_MODULE,
  ERR_WEBASSEMBLY_NOT_SUPPORTED,
} = require('internal/errors').codes;
const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache');
const moduleWrap = internalBinding('module_wrap');
const { ModuleWrap, kEvaluationPhase } = moduleWrap;

const { getSourceSync } = require('internal/modules/esm/load');

const { parse: cjsParse } = internalBinding('cjs_lexer');
const {
  getWasmCompileCacheEntry,
  saveWasmCompileCacheEntry,
} = internalBinding('modules');

const translators = new SafeMap();
exports.translators = translators;

/**
 * Converts a URL to a file path if the URL protocol is 'file:'.
 * @param {string} url - The URL to convert.
 * @returns {string|URL}
 */
function errPath(url) {
  const parsed = new URL(url);
  if (parsed.protocol === 'file:') {
    return fileURLToPath(parsed);
  }
  return url;
}

// Strategy for loading a standard JavaScript module.
translators.set('module', function moduleStrategy(url, translateContext, parentURL) {
  let { source } = translateContext;
  const isMain = (parentURL === undefined);
  assertBufferSource(source, true, 'load');
  source = stringify(source);
  debug(`Translating StandardModule ${url}`, translateContext);
  const {
    compileSourceTextModule, SourceTextModuleTypes: { kUser },
  } = require('internal/modules/esm/utils');
  const context = isMain ? { isMain } : undefined;
  const module = compileSourceTextModule(url, source, kUser, context);
  return module;
});

const { requestTypes: { kRequireInImportedCJS } } = require('internal/modules/esm/utils');
const kShouldSkipModuleHooks = { __proto__: null, shouldSkipModuleHooks: true };
const kShouldNotSkipModuleHooks = { __proto__: null, shouldSkipModuleHooks: false };

/**
 * This may be eventually removed when module.register() reaches end-of-life.
 *
 * Loads a CommonJS module via the ESM Loader sync CommonJS translator.
 * This translator creates its own version of the `require` function passed into CommonJS modules.
 * Any monkey patches applied to the CommonJS Loader will not affect this module.
 * Any `require` calls in this module will load all children in the same way.
 * @param {import('internal/modules/cjs/loader').Module} module - The module to load.
 * @param {string} source - The source code of the module.
 * @param {string} url - The URL of the module.
 * @param {string} filename - The filename of the module.
 * @param {boolean} isMain - Whether the module is the entrypoint
 * @param {TranslateContext} translateContext Context for the translator
 */
function loadCJSModuleWithSpecialRequire(module, source, url, filename, isMain, translateContext) {
  // Use the full URL as the V8 resource name so that any search params
  // (e.g. ?node-test-mock) are preserved in coverage reports.
  const compileResult = compileFunctionForCJSLoader(source, url, false /* is_sea_main */, false);

  const { function: compiledWrapper, sourceMapURL, sourceURL } = compileResult;
  // Cache the source map for the cjs module if present.
  if (sourceMapURL) {
    maybeCacheSourceMap(url, source, module, false, sourceURL, sourceMapURL);
  }
  const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
  const __dirname = dirname(filename);
  // eslint-disable-next-line node-core/func-name-matching,func-style
  const requireFn = function require(specifier) {
    let importAttributes = kEmptyObject;
    if (!StringPrototypeStartsWith(specifier, 'node:') && !BuiltinModule.normalizeRequirableId(specifier)) {
      // TODO: do not depend on the monkey-patchable CJS loader here.
      const path = CJSModule._resolveFilename(specifier, module);
      switch (extname(path)) {
        case '.json':
          importAttributes = { __proto__: null, type: 'json' };
          break;
        case '.node':
          // If it gets here in the translators, the hooks must have already been invoked
          // in the loader. Skip them in the synthetic module evaluation step.
          return wrapModuleLoad(specifier, module, false, kShouldSkipModuleHooks);
        default:
            // fall through
      }
      specifier = `${pathToFileURL(path)}`;
    }

    // NOTE: This re-invented require() is only used on the loader-hook worker thread.
    // On the main thread, the authentic require() is used instead (fixed by #60380).
    const request = { specifier, attributes: importAttributes, phase: kEvaluationPhase, __proto__: null };
    const job = cascadedLoader.getOrCreateModuleJob(url, request, kRequireInImportedCJS);
    job.runSync(module);
    let mod = cjsCache.get(job.url);
    assert(job.module, `Imported CJS module ${url} failed to load module ${job.url} using require() due to race condition`);

    if (job.module.synthetic) {
      assert(mod, `Imported CJS module ${url} failed to load module ${job.url} using require() due to missed cache`);
      return mod.exports;
    }

    // The module being required is a source text module.
    if (!mod) {
      mod = cjsEmplaceModuleCacheEntry(job.url);
      cjsCache.set(job.url, mod);
    }
    // The module has been cached by the re-invented require. Update the exports object
    // from the namespace object and return the evaluated exports.
    if (!mod.loaded) {
      debug('populateCJSExportsFromESM from require(esm) in imported CJS', url, mod, job.module);
      populateCJSExportsFromESM(mod, job.module, job.module.getNamespace());
      mod.loaded = true;
    }
    return mod.exports;
  };
  setOwnProperty(requireFn, 'resolve', function resolve(specifier) {
    if (!StringPrototypeStartsWith(specifier, 'node:')) {
      const {
        filename, url: resolvedURL,
      } = resolveForCJSWithHooks(specifier, module, false, kShouldNotSkipModuleHooks);
      if (specifier !== filename) {
        specifier = resolvedURL ?? `${pathToFileURL(filename)}`;
      }
    }

    const request = { specifier, __proto__: null, attributes: kEmptyObject };
    // Skip sync hooks in resolveSync since resolveForCJSWithHooks already ran them above.
    const { url: resolvedURL } = cascadedLoader.resolveSync(url, request, /* shouldSkipSyncHooks */ true);
    return urlToFilename(resolvedURL);
  });
  setOwnProperty(requireFn, 'main', process.mainModule);

  FunctionPrototypeCall(compiledWrapper, module.exports,
                        module.exports, requireFn, module, filename, __dirname);
  setOwnProperty(module, 'loaded', true);
}

// TODO: can we use a weak map instead?
const cjsCache = new SafeMap();

/**
 * Creates a ModuleWrap object for a CommonJS module.
 * @param {string} url - The URL of the module.
 * @param {import('./loader').TranslateContext} translateContext Context for the translator
 * @param {string|undefined} parentURL URL of the module initiating the module loading for the first time.
 *   Undefined if it's the entry point.
 * @returns {ModuleWrap} The ModuleWrap object for the CommonJS module.
 */
function createCJSModuleWrap(url, translateContext, parentURL) {
  debug(`Translating CJSModule ${url}`, translateContext);

  const { format: sourceFormat } = translateContext;
  let { source } = translateContext;
  const isMain = (parentURL === undefined);
  const filename = urlToFilename(url);
  try {
    // In case the source was not provided by the `load` step, we need fetch it now.
    source = stringify(source ?? getSourceSync(new URL(url)).source);
  } catch {
    // Continue regardless of error.
  }

  const { exportNames, module } = cjsPreparseModuleExports(filename, source, sourceFormat);
  cjsCache.set(url, module);

  const wrapperNames = [...exportNames];
  if (!exportNames.has('default')) {
    ArrayPrototypePush(wrapperNames, 'default');
  }
  if (!exportNames.has('module.exports')) {
    ArrayPrototypePush(wrapperNames, 'module.exports');
  }

  if (isMain) {
    setOwnProperty(process, 'mainModule', module);
  }

  return new ModuleWrap(url, undefined, wrapperNames, function() {
    debug(`Loading CJSModule ${url}`);

    if (!module.loaded) {
      // For backward-compatibility, it's possible for async hooks to return a nullish value for
      // CJS source associated with a `file:` URL - that usually means the source is not
      // customized (is loaded by default load) or the hook author wants it to be reloaded
      // through CJS routine. In this case, the source is obtained by calling the
      // Module._load().
      if (translateContext.translatorKey === 'commonjs-sync' ||
          translateContext.isSourceLoadedSynchronously ||
          translateContext.source == null) {
        loadCJSModuleWithModuleLoad(module, source, url, filename, !!isMain, translateContext);
      } else {  // CommonJS with source customized by async hooks
        // This may be eventually removed when module.register() reaches end-of-life.
        loadCJSModuleWithSpecialRequire(module, source, url, filename, !!isMain, translateContext);
      }
    }

    let exports;
    if (module[kModuleExport] !== undefined) {
      exports = module[kModuleExport];
      module[kModuleExport] = undefined;
    } else {
      ({ exports } = module);
    }
    for (const exportName of exportNames) {
      if (exportName === 'default' || exportName === 'module.exports' ||
          !ObjectPrototypeHasOwnProperty(exports, exportName)) {
        continue;
      }
      // We might trigger a getter -> don't fail.
      let value;
      try {
        value = exports[exportName];
      } catch {
        // Continue regardless of error.
      }
      this.setExport(exportName, value);
    }
    this.setExport('default', exports);
    this.setExport('module.exports', exports);
  }, module);
}

/**
 * Creates a ModuleWrap object for a CommonJS module without source texts.
 * @param {string} url - The URL of the module.
 * @param {string|undefined} parentURL - URL of the parent module, if any.
 * @returns {ModuleWrap} The ModuleWrap object for the CommonJS module.
 */
function createCJSNoSourceModuleWrap(url, parentURL) {
  debug(`Translating CJSModule without source ${url}`);
  const isMain = (parentURL === undefined);

  const filename = urlToFilename(url);

  const module = cjsEmplaceModuleCacheEntry(filename);
  cjsCache.set(url, module);

  if (isMain) {
    setOwnProperty(process, 'mainModule', module);
  }

  // Addon export names are not known until the addon is loaded.
  const exportNames = ['default', 'module.exports'];
  return new ModuleWrap(url, undefined, exportNames, function evaluationCallback() {
    debug(`Loading CJSModule ${url}`);

    if (!module.loaded) {
      // If it gets here in the translators, the hooks must have already been invoked
      // in the loader. Skip them in the synthetic module evaluation step.
      wrapModuleLoad(filename, null, isMain, kShouldSkipModuleHooks);
    }

    /** @type {import('./loader').ModuleExports} */
    let exports;
    if (module[kModuleExport] !== undefined) {
      exports = module[kModuleExport];
      module[kModuleExport] = undefined;
    } else {
      ({ exports } = module);
    }

    this.setExport('default', exports);
    this.setExport('module.exports', exports);
  }, module);
}

translators.set('commonjs-sync', function requireCommonJS(url, translateContext, parentURL) {
  return createCJSModuleWrap(url, translateContext, parentURL);
});

// Handle CommonJS modules referenced by `require` calls using re-invented require.
// This path is only used by require() from imported CJS customized by the *async*
// loader hooks.
translators.set('require-commonjs', (url, translateContext, parentURL) => {
  return createCJSModuleWrap(url, translateContext, parentURL);
});

// Handle TypeScript CommonJS modules referenced by `require` calls using re-invented require.
// This path is only used by require() from imported CJS customized by the *async*
// loader hooks.
translators.set('require-commonjs-typescript', (url, translateContext, parentURL) => {
  translateContext.source = stripTypeScriptModuleTypes(stringify(translateContext.source), url);
  return createCJSModuleWrap(url, translateContext, parentURL);
});

// This goes through Module._load to accommodate monkey-patchers.
/**
 * Loads a CommonJS module through Module._load to accommodate monkey-patchers.
 * If the module was resolved by synchronous hooks (i.e. not by the default resolver),
 * passes the pre-resolved information and source to Module._load to avoid
 * re-resolving and re-loading.
 * @param {import('internal/modules/cjs/loader').Module} module - The module to load.
 * @param {string} source - The source code of the module.
 * @param {string} url - The URL of the module.
 * @param {string} filename - The filename of the module.
 * @param {boolean} isMain - Whether the module is the entrypoint
 * @param {import('./loader').TranslateContext} translateContext Context for the translator
 */
function loadCJSModuleWithModuleLoad(module, source, url, filename, isMain, translateContext) {
  assert(module === CJSModule._cache[filename]);
  debug(`loadCJSModuleWithModuleLoad ${url}`);
  let exports;
  if (translateContext.isResolvedBySyncHooks) {
    exports = wrapModuleLoad(filename, undefined, isMain, {
      __proto__: null,
      resolved: {
        __proto__: null,
        filename,
        format: translateContext.format,
        url,
      },
      shouldSkipModuleHooks: true,
      source,
    });
  } else {
    // If it gets here in the translators, the hooks must have already been invoked
    // in the loader. Skip them in the synthetic module evaluation step.
    exports = wrapModuleLoad(filename, undefined, isMain, kShouldSkipModuleHooks);
  }

  // Patched Module._load implementations may return exports without updating the
  // ESM-created cache entry. Mirror the returned value into the translator-owned
  // module so the synthetic module namespace observes the loaded exports.
  if (!module.loaded) {
    module.exports = exports;
    module.loaded = true;
  }
  module[kModuleExport] = exports;
}

// Handle CommonJS modules referenced by `import` statements or expressions,
// or as the initial entry point when the ESM loader handles a CommonJS entry.
translators.set('commonjs', function commonjsStrategy(url, translateContext, parentURL) {
  return createCJSModuleWrap(url, translateContext, parentURL);
});

/**
 * Get or create an entry in the CJS module cache for the given filename.
 * @param {string} filename CJS module filename
 * @param {CJSModule} parent The parent CJS module
 * @returns {CJSModule} the cached CJS module entry
 */
function cjsEmplaceModuleCacheEntry(filename, parent) {
  // TODO: Do we want to keep hitting the user mutable CJS loader here?
  let cjsMod = CJSModule._cache[filename];
  if (cjsMod) {
    return cjsMod;
  }

  cjsMod = new CJSModule(filename, parent);
  cjsMod.filename = filename;
  cjsMod.paths = CJSModule._nodeModulePaths(cjsMod.path);
  cjsMod[kIsCachedByESMLoader] = true;
  CJSModule._cache[filename] = cjsMod;

  return cjsMod;
}

/**
 * Pre-parses a CommonJS module's exports and re-exports.
 * @param {string} filename - The filename of the module.
 * @param {string} [source] - The source code of the module.
 * @param {string} [format]
 * @returns {{module: CJSModule, exportNames: string[]}}
 */
function cjsPreparseModuleExports(filename, source, format) {
  const module = cjsEmplaceModuleCacheEntry(filename);
  if (module[kModuleExportNames] !== undefined) {
    return { module, exportNames: module[kModuleExportNames] };
  }

  if (source === undefined) {
    ({ source } = loadSourceForCJSWithHooks(module, filename, format));
  }
  module[kModuleSource] = source;

  debug(`Preparsing exports of ${filename}`);
  const { 0: exportNames, 1: reexports } = cjsParse(source);

  // Set first for cycles.
  module[kModuleExportNames] = exportNames;

  // If there are any re-exports e.g. `module.exports = { ...require(...) }`,
  // pre-parse the dependencies to find transitively exported names.
  if (reexports.length) {
    module.filename ??= filename;
    module.paths ??= CJSModule._nodeModulePaths(dirname(filename));

    for (let i = 0; i < reexports.length; i++) {
      debug(`Preparsing re-exports of '${filename}'`);
      const reexport = reexports[i];
      let resolved;
      let format;
      try {
        ({ format, filename: resolved } = resolveForCJSWithHooks(reexport, module, false, kShouldNotSkipModuleHooks));
      } catch (e) {
        debug(`Failed to resolve '${reexport}', skipping`, e);
        continue;
      }

      if (format === 'commonjs' ||
        (!BuiltinModule.normalizeRequirableId(resolved) && findLongestRegisteredExtension(resolved) === '.js')) {
        const { exportNames: reexportNames } = cjsPreparseModuleExports(resolved, undefined, format);
        for (const name of reexportNames) {
          exportNames.add(name);
        }
      }
    }
  }

  return { module, exportNames };
}

// Strategy for loading a node builtin CommonJS module that isn't
// through normal resolution
translators.set('builtin', function builtinStrategy(url, translateContext) {
  debug(`Translating BuiltinModule ${url}`, translateContext);
  // Slice 'node:' scheme
  const id = StringPrototypeSlice(url, 5);
  const module = loadBuiltinModule(id, url);
  cjsCache.set(url, module);
  if (!StringPrototypeStartsWith(url, 'node:') || !module) {
    throw new ERR_UNKNOWN_BUILTIN_MODULE(url);
  }
  debug(`Loading BuiltinModule ${url}`);
  return module.getESMFacade();
});

// Strategy for loading a JSON file
translators.set('json', function jsonStrategy(url, translateContext) {
  let { source } = translateContext;
  assertBufferSource(source, true, 'load');
  debug(`Loading JSONModule ${url}`);
  const pathname = StringPrototypeStartsWith(url, 'file:') ?
    fileURLToPath(url) : null;
  const shouldCheckAndPopulateCJSModuleCache =
    // We want to involve the CJS loader cache only for `file:` URL with no search query and no hash.
    pathname && !StringPrototypeIncludes(url, '?') && !StringPrototypeIncludes(url, '#');
  let modulePath;
  let module;
  if (shouldCheckAndPopulateCJSModuleCache) {
    modulePath = isWindows ?
      StringPrototypeReplaceAll(pathname, '/', '\\') : pathname;
    module = CJSModule._cache[modulePath];
    if (module?.loaded) {
      const exports = module.exports;
      return new ModuleWrap(url, undefined, ['default'], function() {
        this.setExport('default', exports);
      });
    }
  }
  source = stringify(source);
  if (shouldCheckAndPopulateCJSModuleCache) {
    // A require call could have been called on the same file during loading and
    // that resolves synchronously. To make sure we always return the identical
    // export, we have to check again if the module already exists or not.
    // TODO: remove CJS loader from here as well.
    module = CJSModule._cache[modulePath];
    if (module?.loaded) {
      const exports = module.exports;
      return new ModuleWrap(url, undefined, ['default'], function() {
        this.setExport('default', exports);
      });
    }
  }
  try {
    const exports = JSONParse(stripBOM(source));
    module = {
      exports,
      loaded: true,
    };
  } catch (err) {
    // TODO (BridgeAR): We could add a NodeCore error that wraps the JSON
    // parse error instead of just manipulating the original error message.
    // That would allow to add further properties and maybe additional
    // debugging information.
    err.message = errPath(url) + ': ' + err.message;
    throw err;
  }
  if (shouldCheckAndPopulateCJSModuleCache) {
    CJSModule._cache[modulePath] = module;
  }
  cjsCache.set(url, module);
  return new ModuleWrap(url, undefined, ['default'], function() {
    debug(`Parsing JSONModule ${url}`);
    this.setExport('default', module.exports);
  });
});

// Strategy for loading a wasm module
// This logic should collapse into WebAssembly Module Record in future.
/**
 * @type {WeakMap<
 *   import('internal/modules/esm/utils').ModuleNamespaceObject,
 *   WebAssembly.Instance
 * >} [[Instance]] slot proxy for WebAssembly Module Record
 */
const wasmInstances = new SafeWeakMap();
// Compile options per https://webassembly.github.io/esm-integration/js-api/index.html#parse-a-webassembly-module.
const wasmCompileOptions = {
  __proto__: null,
  builtins: ['js-string'],
  importedStringConstants: 'wasm:js/string-constants',
};
translators.set('wasm', function(url, translateContext) {
  const { source } = translateContext;
  // WebAssembly global is not available during snapshot building, so we need to get it lazily.
  const { WebAssembly } = globalThis;
  if (WebAssembly === undefined) {
    throw new ERR_WEBASSEMBLY_NOT_SUPPORTED('WebAssembly module imports');
  }
  assertBufferSource(source, false, 'load');

  debug(`Translating WASMModule ${url}`, translateContext);

  // When the compile cache is enabled, the cache entry holds the module
  // deserialized from cached optimized code if V8 accepted it.
  const cacheEntry = getWasmCompileCacheEntry(
    isAnyArrayBuffer(source) ? new Uint8Array(source) : source, url, wasmCompileOptions);
  let compiled = cacheEntry?.module;
  if (compiled === undefined) {
    try {
      compiled = new WebAssembly.Module(source, wasmCompileOptions);
    } catch (err) {
      err.message = errPath(url) + ': ' + err.message;
      throw err;
    }
    if (cacheEntry !== undefined) {
      saveWasmCompileCacheEntry(cacheEntry.external, compiled);
    }
  }

  const importsList = new SafeSet();
  const wasmGlobalImports = [];
  for (const impt of WebAssembly.Module.imports(compiled)) {
    if (impt.kind === 'global') {
      ArrayPrototypePush(wasmGlobalImports, impt);
    }
    // Prefix reservations per https://webassembly.github.io/esm-integration/js-api/index.html#parse-a-webassembly-module.
    if (impt.module.startsWith('wasm-js:')) {
      throw new WebAssembly.LinkError(`Invalid Wasm import "${impt.module}" in ${url}`);
    }
    if (impt.name.startsWith('wasm:') || impt.name.startsWith('wasm-js:')) {
      throw new WebAssembly.LinkError(`Invalid Wasm import name "${impt.name}" in ${url}`);
    }
    importsList.add(impt.module);
  }

  const exportsList = new SafeSet();
  const wasmGlobalExports = new SafeSet();
  for (const expt of WebAssembly.Module.exports(compiled)) {
    if (expt.kind === 'global') {
      wasmGlobalExports.add(expt.name);
    }
    if (expt.name.startsWith('wasm:') || expt.name.startsWith('wasm-js:')) {
      throw new WebAssembly.LinkError(`Invalid Wasm export name "${expt.name}" in ${url}`);
    }
    exportsList.add(expt.name);
  }

  const createDynamicModule = require('internal/modules/esm/create_dynamic_module');

  const { module } = createDynamicModule([...importsList], [...exportsList], url, (reflect) => {
    emitExperimentalWarning('Importing WebAssembly module instances');
    for (const impt of importsList) {
      const importNs = reflect.imports[impt];
      const wasmInstance = wasmInstances.get(importNs);
      if (wasmInstance) {
        const wrappedModule = ObjectAssign({ __proto__: null }, reflect.imports[impt]);
        for (const { module, name } of wasmGlobalImports) {
          if (module !== impt) {
            continue;
          }
          // Import of Wasm module global -> get direct WebAssembly.Global wrapped value.
          // JS API validations otherwise remain the same.
          wrappedModule[name] = wasmInstance[name];
        }
        reflect.imports[impt] = wrappedModule;
      }
    }
    // In cycles importing unexecuted Wasm, wasmInstance will be undefined, which will fail during
    // instantiation, since all bindings will be in the Temporal Deadzone (TDZ).
    const { exports } = new WebAssembly.Instance(compiled, reflect.imports);
    wasmInstances.set(module.getNamespace(), exports);
    for (const expt of exportsList) {
      let val = exports[expt];
      // Unwrap WebAssembly.Global for JS bindings
      if (wasmGlobalExports.has(expt)) {
        try {
          // v128 will throw in GetGlobalValue, see:
          // https://webassembly.github.io/esm-integration/js-api/index.html#getglobalvalue
          val = val.value;
        } catch {
          // v128 doesn't support ToJsValue() -> use undefined (ideally should stay in TDZ)
          continue;
        }
      }
      reflect.exports[expt].set(val);
    }
  });
  // WebAssembly modules support source phase imports, to import the compiled module
  // separate from the linked instance.
  module.setModuleSourceObject(compiled);
  return module;
});

// Strategy for loading a addon
translators.set('addon', function translateAddon(url, translateContext, parentURL) {
  emitExperimentalWarning('Importing addons');

  const { source } = translateContext;
  // The addon must be loaded from file system with dlopen. Assert
  // the source is null.
  if (source !== null) {
    throw new ERR_INVALID_RETURN_PROPERTY_VALUE(
      'null',
      'load',
      'source',
      source);
  }

  debug(`Translating addon ${url}`, translateContext);

  return createCJSNoSourceModuleWrap(url, parentURL);
});

// Strategy for loading a commonjs TypeScript module
translators.set('commonjs-typescript', function(url, translateContext, parentURL) {
  const { source } = translateContext;
  assertBufferSource(source, true, 'load');
  debug(`Translating TypeScript ${url}`, translateContext);
  translateContext.source = stripTypeScriptModuleTypes(stringify(source), url);
  return FunctionPrototypeCall(translators.get('commonjs'), this, url, translateContext, parentURL);
});

// Strategy for loading an esm TypeScript module
translators.set('module-typescript', function(url, translateContext, parentURL) {
  const { source } = translateContext;
  assertBufferSource(source, true, 'load');
  debug(`Translating TypeScript ${url}`, translateContext);
  translateContext.source = stripTypeScriptModuleTypes(stringify(source), url);
  return FunctionPrototypeCall(translators.get('module'), this, url, translateContext, parentURL);
});

// Strategy for loading source as text.
translators.set('text', function textStrategy(url, translateContext) {
  emitExperimentalWarning('Text import');
  let { source } = translateContext;
  assertBufferSource(source, true, 'load');
  source = stringify(source);
  return new ModuleWrap(url, undefined, ['default'], function() {
    this.setExport('default', source);
  });
});