nstdlib-nightly
Version:
Node.js standard library converted to runtime-agnostic ES modules.
623 lines (576 loc) • 20.3 kB
JavaScript
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/modules/esm/translators.js
import * as __hoisted_internal_modules_esm_load__ from "nstdlib/lib/internal/modules/esm/load";
import * as __hoisted_internal_deps_cjs_module_lexer_dist_lexer__ from "nstdlib/stub/internal/deps/cjs-module-lexer/dist/lexer";
import * as __hoisted_internal_deps_cjs_module_lexer_lexer__ from "nstdlib/stub/internal/deps/cjs-module-lexer/lexer";
import * as __hoisted_internal_encoding__ from "nstdlib/lib/internal/encoding";
import * as __hoisted_internal_modules_esm_utils__ from "nstdlib/lib/internal/modules/esm/utils";
import * as __hoisted_internal_modules_esm_loader__ from "nstdlib/lib/internal/modules/esm/loader";
import * as __hoisted_internal_modules_esm_create_dynamic_module__ from "nstdlib/lib/internal/modules/esm/create_dynamic_module";
import { compileFunctionForCJSLoader } from "nstdlib/stub/binding/contextify";
import { BuiltinModule } from "nstdlib/lib/internal/bootstrap/realm";
import * as assert from "nstdlib/lib/internal/assert";
import { readFileSync } from "nstdlib/lib/fs";
import { dirname, extname, isAbsolute } from "nstdlib/lib/path";
import {
loadBuiltinModule,
stripTypeScriptTypes,
stripBOM,
urlToFilename,
} from "nstdlib/lib/internal/modules/helpers";
import {
kIsCachedByESMLoader,
Module as CJSModule,
wrapModuleLoad,
kModuleSource,
kModuleExport,
kModuleExportNames,
} from "nstdlib/lib/internal/modules/cjs/loader";
import { fileURLToPath, pathToFileURL, URL } from "nstdlib/lib/internal/url";
import {
emitExperimentalWarning,
kEmptyObject,
setOwnProperty,
isWindows,
} from "nstdlib/lib/internal/util";
import { codes as __codes__ } from "nstdlib/lib/internal/errors";
import { maybeCacheSourceMap } from "nstdlib/lib/internal/source_map/source_map_cache";
import * as moduleWrap from "nstdlib/stub/binding/module_wrap";
import * as __hoisted_internal_util_types__ from "nstdlib/lib/internal/util/types";
/** @type {import('internal/util/types')} */
let _TYPES = null;
/**
* Lazily loads and returns the internal/util/types module.
*/
function lazyTypes() {
if (_TYPES !== null) {
return _TYPES;
}
return (_TYPES = __hoisted_internal_util_types__);
}
const { ERR_UNKNOWN_BUILTIN_MODULE, ERR_INVALID_RETURN_PROPERTY_VALUE } =
__codes__;
const { ModuleWrap } = moduleWrap;
// Lazy-loading to avoid circular dependencies.
let getSourceSync;
/**
* @param {Parameters<typeof import('./load').getSourceSync>[0]} url
* @returns {ReturnType<typeof import('./load').getSourceSync>}
*/
function getSource(url) {
getSourceSync ??= __hoisted_internal_modules_esm_load__.getSourceSync;
return getSourceSync(url);
}
/** @type {import('deps/cjs-module-lexer/lexer.js').parse} */
let cjsParse;
/**
* Initializes the CommonJS module lexer parser.
* If WebAssembly is available, it uses the optimized version from the dist folder.
* Otherwise, it falls back to the JavaScript version from the lexer folder.
*/
async function initCJSParse() {
if (typeof WebAssembly === "undefined") {
initCJSParseSync();
} else {
const { parse, init } =
__hoisted_internal_deps_cjs_module_lexer_dist_lexer__;
try {
await init();
cjsParse = parse;
} catch {
initCJSParseSync();
}
}
}
function initCJSParseSync() {
// TODO(joyeecheung): implement a binding that directly compiles using
// v8::WasmModuleObject::Compile() synchronously.
if (cjsParse === undefined) {
cjsParse = __hoisted_internal_deps_cjs_module_lexer_lexer__.parse;
}
}
const translators = new Map();
let _exports_ = {};
_exports_.translators = translators;
let DECODER = null;
/**
* Asserts that the given body is a buffer source (either a string, array buffer, or typed array).
* Throws an error if the body is not a buffer source.
* @param {string | ArrayBufferView | ArrayBuffer} body - The body to check.
* @param {boolean} allowString - Whether or not to allow a string as a valid buffer source.
* @param {string} hookName - The name of the hook being called.
* @throws {ERR_INVALID_RETURN_PROPERTY_VALUE} If the body is not a buffer source.
*/
function assertBufferSource(body, allowString, hookName) {
if (allowString && typeof body === "string") {
return;
}
const { isArrayBufferView, isAnyArrayBuffer } = lazyTypes();
if (isArrayBufferView(body) || isAnyArrayBuffer(body)) {
return;
}
throw new ERR_INVALID_RETURN_PROPERTY_VALUE(
`${allowString ? "string, " : ""}array buffer, or typed array`,
hookName,
"source",
body,
);
}
/**
* Converts a buffer or buffer-like object to a string.
* @param {string | ArrayBuffer | ArrayBufferView} body - The buffer or buffer-like object to convert to a string.
* @returns {string} The resulting string.
*/
function stringify(body) {
if (typeof body === "string") {
return body;
}
assertBufferSource(body, false, "load");
const { TextDecoder } = __hoisted_internal_encoding__;
DECODER = DECODER === null ? new TextDecoder() : DECODER;
return DECODER.decode(body);
}
/**
* Converts a URL to a file path if the URL protocol is 'file:'.
* @param {string} url - The URL to convert.
*/
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, source, isMain) {
assertBufferSource(source, true, "load");
source = stringify(source);
{
/* debug */
}
const { compileSourceTextModule } = __hoisted_internal_modules_esm_utils__;
const module = compileSourceTextModule(url, source, this);
return module;
});
/**
* 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
*/
const _module_ = { exports: _exports_ };
function loadCJSModule(_module_, source, url, filename, isMain) {
const compileResult = compileFunctionForCJSLoader(
source,
filename,
false /* is_sea_main */,
false,
);
const { function: compiledWrapper, sourceMapURL } = compileResult;
// Cache the source map for the cjs module if present.
if (sourceMapURL) {
maybeCacheSourceMap(url, source, module, false, undefined, sourceMapURL);
}
const cascadedLoader =
__hoisted_internal_modules_esm_loader__.getOrInitializeCascadedLoader();
const __dirname = dirname(filename);
// eslint-disable-next-line func-name-matching,func-style
const requireFn = function require(specifier) {
let importAttributes = kEmptyObject;
if (
!String.prototype.startsWith.call(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":
return wrapModuleLoad(specifier, module);
default:
// fall through
}
specifier = `${pathToFileURL(path)}`;
}
const job = cascadedLoader.getModuleJobSync(
specifier,
url,
importAttributes,
);
job.runSync();
return cjsCache.get(job.url).exports;
};
setOwnProperty(requireFn, "resolve", function resolve(specifier) {
if (!String.prototype.startsWith.call(specifier, "node:")) {
const path = CJSModule._resolveFilename(specifier, module);
if (specifier !== path) {
specifier = `${pathToFileURL(path)}`;
}
}
const { url: resolvedURL } = cascadedLoader.resolveSync(
specifier,
url,
kEmptyObject,
);
return urlToFilename(resolvedURL);
});
setOwnProperty(requireFn, "main", process.mainModule);
ReflectApply(compiledWrapper, module.exports, [
module.exports,
requireFn,
module,
filename,
__dirname,
]);
setOwnProperty(module, "loaded", true);
}
// TODO: can we use a weak map instead?
const cjsCache = new Map();
/**
* Creates a ModuleWrap object for a CommonJS module.
* @param {string} url - The URL of the module.
* @param {string} source - The source code of the module.
* @param {boolean} isMain - Whether the module is the main module.
* @param {typeof loadCJSModule} [loadCJS=loadCJSModule] - The function to load the CommonJS module.
* @returns {ModuleWrap} The ModuleWrap object for the CommonJS module.
*/
function createCJSModuleWrap(url, source, isMain, loadCJS = loadCJSModule) {
{
/* debug */
}
const filename = urlToFilename(url);
// In case the source was not provided by the `load` step, we need fetch it now.
source = stringify(source ?? getSource(new URL(url)).source);
const { exportNames, module } = cjsPreparseModuleExports(filename, source);
cjsCache.set(url, module);
const namesWithDefault = exportNames.has("default")
? [...exportNames]
: ["default", ...exportNames];
if (isMain) {
setOwnProperty(process, "mainModule", module);
}
return new ModuleWrap(
url,
undefined,
namesWithDefault,
function () {
{
/* debug */
}
if (!module.loaded) {
loadCJS(module, source, url, filename, !!isMain);
}
let exports;
if (module[kModuleExport] !== undefined) {
exports = module[kModuleExport];
module[kModuleExport] = undefined;
} else {
({ exports } = module);
}
for (const exportName of exportNames) {
if (
!Object.prototype.hasOwnProperty.call(exports, exportName) ||
exportName === "default"
) {
continue;
}
// We might trigger a getter -> dont fail.
let value;
try {
value = exports[exportName];
} catch {
// Continue regardless of error.
}
this.setExport(exportName, value);
}
this.setExport("default", exports);
},
module,
);
}
translators.set("commonjs-sync", function requireCommonJS(url, source, isMain) {
initCJSParseSync();
assert(!isMain); // This is only used by imported CJS modules.
return createCJSModuleWrap(
url,
source,
isMain,
(module, source, url, filename, isMain) => {
assert(module === CJSModule._cache[filename]);
assert(!isMain);
wrapModuleLoad(filename, null, isMain);
},
);
});
// Handle CommonJS modules referenced by `require` calls.
// This translator function must be sync, as `require` is sync.
translators.set("require-commonjs", (url, source, isMain) => {
assert(cjsParse);
return createCJSModuleWrap(url, source);
});
// Handle CommonJS modules referenced by `require` calls.
// This translator function must be sync, as `require` is sync.
translators.set("require-commonjs-typescript", (url, source, isMain) => {
emitExperimentalWarning("Type Stripping");
assert(cjsParse);
const code = stripTypeScriptTypes(stringify(source), url);
return createCJSModuleWrap(url, code);
});
// 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",
async function commonjsStrategy(url, source, isMain) {
if (!cjsParse) {
await initCJSParse();
}
// For backward-compatibility, it's possible to return a nullish value for
// CJS source associated with a file: URL. In this case, the source is
// obtained by calling the monkey-patchable CJS loader.
const cjsLoader =
source == null
? (module, source, url, filename, isMain) => {
assert(module === CJSModule._cache[filename]);
wrapModuleLoad(filename, undefined, isMain);
}
: loadCJSModule;
try {
// We still need to read the FS to detect the exports.
source ??= readFileSync(new URL(url), "utf8");
} catch {
// Continue regardless of error.
}
return createCJSModuleWrap(url, source, isMain, cjsLoader);
},
);
/**
* 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.
*/
function cjsPreparseModuleExports(filename, source) {
// TODO: Do we want to keep hitting the user mutable CJS loader here?
let module = CJSModule._cache[filename];
if (module && module[kModuleExportNames] !== undefined) {
return { module, exportNames: module[kModuleExportNames] };
}
const loaded = Boolean(module);
if (!loaded) {
module = new CJSModule(filename);
module.filename = filename;
module.paths = CJSModule._nodeModulePaths(module.path);
module[kIsCachedByESMLoader] = true;
module[kModuleSource] = source;
CJSModule._cache[filename] = module;
}
let exports, reexports;
try {
({ exports, reexports } = cjsParse(source || ""));
} catch {
exports = [];
reexports = [];
}
const exportNames = new Set(
new (Array.prototype[Symbol.iterator]())(exports),
);
// Set first for cycles.
module[kModuleExportNames] = exportNames;
if (reexports.length) {
module.filename = filename;
module.paths = CJSModule._nodeModulePaths(module.path);
for (let i = 0; i < reexports.length; i++) {
const reexport = reexports[i];
let resolved;
try {
// TODO: this should be calling the `resolve` hook chain instead.
// Doing so would mean dropping support for CJS in the loader thread, as
// this call needs to be sync from the perspective of the main thread,
// which we can do via HooksProxy and Atomics, but we can't do within
// the loaders thread. Until this is done, the lexer will use the
// monkey-patchable CJS loader to get the path to the module file to
// load (which may or may not be aligned with the URL that the `resolve`
// hook have returned).
resolved = CJSModule._resolveFilename(reexport, module);
} catch {
continue;
}
// TODO: this should be calling the `load` hook chain and check if it returns
// `format: 'commonjs'` instead of relying on file extensions.
const ext = extname(resolved);
if (
(ext === ".js" || ext === ".cjs" || !CJSModule._extensions[ext]) &&
isAbsolute(resolved)
) {
// TODO: this should be calling the `load` hook chain to get the source
// (and fallback to reading the FS only if the source is nullish).
const source = readFileSync(resolved, "utf-8");
const { exportNames: reexportNames } = cjsPreparseModuleExports(
resolved,
source,
);
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) {
{
/* debug */
}
// Slice 'node:' scheme
const id = String.prototype.slice.call(url, 5);
const module = loadBuiltinModule(id, url);
cjsCache.set(url, module);
if (!String.prototype.startsWith.call(url, "node:") || !module) {
throw new ERR_UNKNOWN_BUILTIN_MODULE(url);
}
{
/* debug */
}
return module.getESMFacade();
});
// Strategy for loading a JSON file
translators.set("json", function jsonStrategy(url, source) {
emitExperimentalWarning("Importing JSON modules");
assertBufferSource(source, true, "load");
{
/* debug */
}
const pathname = String.prototype.startsWith.call(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 &&
!String.prototype.includes.call(url, "?") &&
!String.prototype.includes.call(url, "#");
let modulePath;
let module;
if (shouldCheckAndPopulateCJSModuleCache) {
modulePath = isWindows
? String.prototype.replaceAll.call(pathname, "/", "\\")
: pathname;
module = CJSModule._cache[modulePath];
if (module && 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 && 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 */
}
this.setExport("default", module.exports);
});
});
// Strategy for loading a wasm module
translators.set("wasm", async function (url, source) {
emitExperimentalWarning("Importing WebAssembly modules");
assertBufferSource(source, false, "load");
{
/* debug */
}
let compiled;
try {
// TODO(joyeecheung): implement a binding that directly compiles using
// v8::WasmModuleObject::Compile() synchronously.
compiled = await WebAssembly.compile(source);
} catch (err) {
err.message = errPath(url) + ": " + err.message;
throw err;
}
const imports = Array.prototype.map.call(
WebAssembly.Module.imports(compiled),
({ module }) => module,
);
const exports = Array.prototype.map.call(
WebAssembly.Module.exports(compiled),
({ name }) => name,
);
const createDynamicModule =
__hoisted_internal_modules_esm_create_dynamic_module__;
return createDynamicModule(imports, exports, url, (reflect) => {
const { exports } = new WebAssembly.Instance(compiled, reflect.imports);
for (const expt of Object.keys(exports)) {
reflect.exports[expt].set(exports[expt]);
}
}).module;
});
// Strategy for loading a commonjs TypeScript module
translators.set("commonjs-typescript", function (url, source) {
emitExperimentalWarning("Type Stripping");
assertBufferSource(source, false, "load");
const code = stripTypeScriptTypes(stringify(source), url);
{
/* debug */
}
return Function.prototype.call.call(
translators.get("commonjs"),
this,
url,
code,
false,
);
});
// Strategy for loading an esm TypeScript module
translators.set("module-typescript", function (url, source) {
emitExperimentalWarning("Type Stripping");
assertBufferSource(source, false, "load");
const code = stripTypeScriptTypes(stringify(source), url);
{
/* debug */
}
return Function.prototype.call.call(
translators.get("module"),
this,
url,
code,
false,
);
});
export default _module_.exports;