node/lib/internal/modules/esm/translators.js
Joyee Cheung 472edc775d
src: disambiguate terms used to refer to builtins and addons
The term "native module" dates back to some of the oldest code
in the code base. Within the context of Node.js core it usually
refers to modules that are native to Node.js (e.g. fs, http),
but it can cause confusion for people who don't work on this
part of the code base, as "native module" can also refer to
native addons - which is even the case in some of the API
docs and error messages.

This patch tries to make the usage of these terms more consistent.
Now within the context of Node.js core:

- JavaScript scripts that are built-in to Node.js are now referred
  to as "built-in(s)". If they are available as modules,
  they can also be referred to as "built-in module(s)".
- Dynamically-linked shared objects that are loaded into
  the Node.js processes are referred to as "addons".

We will try to avoid using the term "native modules" because it could
be ambiguous.

Changes in this patch:

File names:
- node_native_module.h -> node_builtins.h,
- node_native_module.cc -> node_builtins.cc

C++ binding names:
- `native_module` -> `builtins`

`node::Environment`:
- `native_modules_without_cache` -> `builtins_without_cache`
- `native_modules_with_cache` -> `builtins_with_cache`
- `native_modules_in_snapshot` -> `builtins_in_cache`
- `native_module_require` -> `builtin_module_require`

`node::EnvSerializeInfo`:
- `native_modules` -> `builtins

`node::native_module::NativeModuleLoader`:
- `native_module` namespace -> `builtins` namespace
- `NativeModuleLoader` -> `BuiltinLoader`
- `NativeModuleRecordMap` -> `BuiltinSourceMap`
- `NativeModuleCacheMap` -> `BuiltinCodeCacheMap`
- `ModuleIds` -> `BuiltinIds`
- `ModuleCategories` -> `BuiltinCategories`
- `LoadBuiltinModuleSource` -> `LoadBuiltinSource`

`loader.js`:
- `NativeModule` -> `BuiltinModule` (the `NativeModule` name used in
  `process.moduleLoadList` is kept for compatibility)

And other clarifications in the documentation and comments.

PR-URL: https://github.com/nodejs/node/pull/44135
Fixes: https://github.com/nodejs/node/issues/44036
Reviewed-By: Jacob Smith <jacob@frende.me>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Michael Dawson <midawson@redhat.com>
Reviewed-By: Richard Lau <rlau@redhat.com>
Reviewed-By: Jiawen Geng <technicalcute@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Jan Krems <jan.krems@gmail.com>
2022-08-09 01:36:49 +08:00

350 lines
10 KiB
JavaScript

'use strict';
const {
ArrayPrototypeForEach,
ArrayPrototypeMap,
Boolean,
JSONParse,
ObjectGetPrototypeOf,
ObjectPrototypeHasOwnProperty,
ObjectKeys,
SafeArrayIterator,
SafeMap,
SafeSet,
StringPrototypeReplaceAll,
StringPrototypeSlice,
StringPrototypeStartsWith,
SyntaxErrorPrototype,
globalThis: { WebAssembly },
} = primordials;
let _TYPES = null;
function lazyTypes() {
if (_TYPES !== null) return _TYPES;
return _TYPES = require('internal/util/types');
}
const { readFileSync } = require('fs');
const { extname, isAbsolute } = require('path');
const {
hasEsmSyntax,
loadBuiltinModule,
stripBOM,
} = require('internal/modules/cjs/helpers');
const {
Module: CJSModule,
cjsParseCache
} = require('internal/modules/cjs/loader');
const internalURLModule = require('internal/url');
const createDynamicModule = require(
'internal/modules/esm/create_dynamic_module');
const { fileURLToPath, URL } = require('url');
let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {
debug = fn;
});
const { emitExperimentalWarning } = require('internal/util');
const {
ERR_UNKNOWN_BUILTIN_MODULE,
ERR_INVALID_RETURN_PROPERTY_VALUE
} = require('internal/errors').codes;
const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache');
const moduleWrap = internalBinding('module_wrap');
const { ModuleWrap } = moduleWrap;
const asyncESM = require('internal/process/esm_loader');
const { emitWarningSync } = require('internal/process/warning');
const { TextDecoder } = require('internal/encoding');
let cjsParse;
async function initCJSParse() {
if (typeof WebAssembly === 'undefined') {
cjsParse = require('internal/deps/cjs-module-lexer/lexer').parse;
} else {
const { parse, init } =
require('internal/deps/cjs-module-lexer/dist/lexer');
try {
await init();
cjsParse = parse;
} catch {
cjsParse = require('internal/deps/cjs-module-lexer/lexer').parse;
}
}
}
const translators = new SafeMap();
exports.translators = translators;
exports.enrichCJSError = enrichCJSError;
let DECODER = null;
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
);
}
function stringify(body) {
if (typeof body === 'string') return body;
assertBufferSource(body, false, 'transformSource');
DECODER = DECODER === null ? new TextDecoder() : DECODER;
return DECODER.decode(body);
}
function errPath(url) {
const parsed = new URL(url);
if (parsed.protocol === 'file:') {
return fileURLToPath(parsed);
}
return url;
}
async function importModuleDynamically(specifier, { url }, assertions) {
return asyncESM.esmLoader.import(specifier, url, assertions);
}
// Strategy for loading a standard JavaScript module.
translators.set('module', async function moduleStrategy(url, source, isMain) {
assertBufferSource(source, true, 'load');
source = stringify(source);
maybeCacheSourceMap(url, source);
debug(`Translating StandardModule ${url}`);
const module = new ModuleWrap(url, undefined, source, 0, 0);
moduleWrap.callbackMap.set(module, {
initializeImportMeta: (meta, wrap) => this.importMetaInitialize(meta, { url }),
importModuleDynamically,
});
return module;
});
/**
* @param {Error | any} err
* @param {string} [content] Content of the file, if known.
* @param {string} [filename] Useful only if `content` is unknown.
*/
function enrichCJSError(err, content, filename) {
if (err != null && ObjectGetPrototypeOf(err) === SyntaxErrorPrototype &&
hasEsmSyntax(content || readFileSync(filename, 'utf-8'))) {
// Emit the warning synchronously because we are in the middle of handling
// a SyntaxError that will throw and likely terminate the process before an
// asynchronous warning would be emitted.
emitWarningSync(
'To load an ES module, set "type": "module" in the package.json or use ' +
'the .mjs extension.'
);
}
}
// Strategy for loading a node-style CommonJS module
const isWindows = process.platform === 'win32';
translators.set('commonjs', async function commonjsStrategy(url, source,
isMain) {
debug(`Translating CJSModule ${url}`);
let filename = internalURLModule.fileURLToPath(new URL(url));
if (isWindows)
filename = StringPrototypeReplaceAll(filename, '/', '\\');
if (!cjsParse) await initCJSParse();
const { module, exportNames } = cjsPreparseModuleExports(filename);
const namesWithDefault = exportNames.has('default') ?
[...exportNames] : ['default', ...exportNames];
return new ModuleWrap(url, undefined, namesWithDefault, function() {
debug(`Loading CJSModule ${url}`);
let exports;
if (asyncESM.esmLoader.cjsCache.has(module)) {
exports = asyncESM.esmLoader.cjsCache.get(module);
asyncESM.esmLoader.cjsCache.delete(module);
} else {
try {
exports = CJSModule._load(filename, undefined, isMain);
} catch (err) {
enrichCJSError(err, undefined, filename);
throw err;
}
}
for (const exportName of exportNames) {
if (!ObjectPrototypeHasOwnProperty(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);
});
});
function cjsPreparseModuleExports(filename) {
let module = CJSModule._cache[filename];
if (module) {
const cached = cjsParseCache.get(module);
if (cached)
return { module, exportNames: cached.exportNames };
}
const loaded = Boolean(module);
if (!loaded) {
module = new CJSModule(filename);
module.filename = filename;
module.paths = CJSModule._nodeModulePaths(module.path);
CJSModule._cache[filename] = module;
}
let source;
try {
source = readFileSync(filename, 'utf8');
} catch {
// Continue regardless of error.
}
let exports, reexports;
try {
({ exports, reexports } = cjsParse(source || ''));
} catch {
exports = [];
reexports = [];
}
const exportNames = new SafeSet(new SafeArrayIterator(exports));
// Set first for cycles.
cjsParseCache.set(module, { source, exportNames, loaded });
if (reexports.length) {
module.filename = filename;
module.paths = CJSModule._nodeModulePaths(module.path);
}
ArrayPrototypeForEach(reexports, (reexport) => {
let resolved;
try {
resolved = CJSModule._resolveFilename(reexport, module);
} catch {
return;
}
const ext = extname(resolved);
if ((ext === '.js' || ext === '.cjs' || !CJSModule._extensions[ext]) &&
isAbsolute(resolved)) {
const { exportNames: reexportNames } = cjsPreparseModuleExports(resolved);
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', async function builtinStrategy(url) {
debug(`Translating BuiltinModule ${url}`);
// Slice 'node:' scheme
const id = StringPrototypeSlice(url, 5);
const module = loadBuiltinModule(id, url);
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', async function jsonStrategy(url, source) {
emitExperimentalWarning('Importing JSON modules');
assertBufferSource(source, true, 'load');
debug(`Loading JSONModule ${url}`);
const pathname = StringPrototypeStartsWith(url, 'file:') ?
fileURLToPath(url) : null;
let modulePath;
let module;
if (pathname) {
modulePath = isWindows ?
StringPrototypeReplaceAll(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 (pathname) {
// 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.
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 (pathname) {
CJSModule._cache[modulePath] = module;
}
return new ModuleWrap(url, undefined, ['default'], function() {
debug(`Parsing JSONModule ${url}`);
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(`Translating WASMModule ${url}`);
let compiled;
try {
compiled = await WebAssembly.compile(source);
} catch (err) {
err.message = errPath(url) + ': ' + err.message;
throw err;
}
const imports =
ArrayPrototypeMap(WebAssembly.Module.imports(compiled),
({ module }) => module);
const exports =
ArrayPrototypeMap(WebAssembly.Module.exports(compiled),
({ name }) => name);
return createDynamicModule(imports, exports, url, (reflect) => {
const { exports } = new WebAssembly.Instance(compiled, reflect.imports);
for (const expt of ObjectKeys(exports))
reflect.exports[expt].set(exports[expt]);
}).module;
});