lib: refactor NativeModule
Refactor the internal NativeModule class to a JS class and add more documentation about its properties. PR-URL: https://github.com/nodejs/node/pull/30856 Reviewed-By: Denys Otrishko <shishugi@gmail.com> Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Rich Trott <rtrott@gmail.com>
This commit is contained in:
parent
1807c3eadf
commit
e4e5a835b8
@ -137,89 +137,83 @@ let internalBinding;
|
||||
};
|
||||
}
|
||||
|
||||
// Think of this as module.exports in this file even though it is not
|
||||
// written in CommonJS style.
|
||||
const loaderExports = {
|
||||
internalBinding,
|
||||
NativeModule,
|
||||
require: nativeModuleRequire
|
||||
const loaderId = 'internal/bootstrap/loaders';
|
||||
const {
|
||||
moduleIds,
|
||||
compileFunction
|
||||
} = internalBinding('native_module');
|
||||
|
||||
const getOwn = (target, property, receiver) => {
|
||||
return ObjectPrototypeHasOwnProperty(target, property) ?
|
||||
ReflectGet(target, property, receiver) :
|
||||
undefined;
|
||||
};
|
||||
|
||||
const loaderId = 'internal/bootstrap/loaders';
|
||||
/**
|
||||
* An internal abstraction for the built-in JavaScript modules of Node.js.
|
||||
* Be careful not to expose this to user land unless --expose-internals is
|
||||
* used, in which case there is no compatibility guarantee about this class.
|
||||
*/
|
||||
class NativeModule {
|
||||
/**
|
||||
* A map from the module IDs to the module instances.
|
||||
* @type {Map<string, NativeModule>}
|
||||
*/
|
||||
static map = new Map(moduleIds.map((id) => [id, new NativeModule(id)]));
|
||||
|
||||
// Set up NativeModule.
|
||||
function NativeModule(id) {
|
||||
constructor(id) {
|
||||
this.filename = `${id}.js`;
|
||||
this.id = id;
|
||||
this.canBeRequiredByUsers = !id.startsWith('internal/');
|
||||
|
||||
// The CJS exports object of the module.
|
||||
this.exports = {};
|
||||
this.module = undefined;
|
||||
this.exportKeys = undefined;
|
||||
// States used to work around circular dependencies.
|
||||
this.loaded = false;
|
||||
this.loading = false;
|
||||
this.canBeRequiredByUsers = !id.startsWith('internal/');
|
||||
}
|
||||
|
||||
// To be called during pre-execution when --expose-internals is on.
|
||||
// Enables the user-land module loader to access internal modules.
|
||||
NativeModule.exposeInternals = function() {
|
||||
// The following properties are used by the ESM implementation and only
|
||||
// initialized when the native module is loaded by users.
|
||||
/**
|
||||
* The C++ ModuleWrap binding used to interface with the ESM implementation.
|
||||
* @type {ModuleWrap|undefined}
|
||||
*/
|
||||
this.module = undefined;
|
||||
/**
|
||||
* Exported names for the ESM imports.
|
||||
* @type {string[]|undefined}
|
||||
*/
|
||||
this.exportKeys = undefined;
|
||||
}
|
||||
|
||||
// To be called during pre-execution when --expose-internals is on.
|
||||
// Enables the user-land module loader to access internal modules.
|
||||
static exposeInternals() {
|
||||
for (const [id, mod] of NativeModule.map) {
|
||||
// Do not expose this to user land even with --expose-internals.
|
||||
if (id !== loaderId) {
|
||||
mod.canBeRequiredByUsers = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const {
|
||||
moduleIds,
|
||||
compileFunction
|
||||
} = internalBinding('native_module');
|
||||
|
||||
NativeModule.map = new Map();
|
||||
for (let i = 0; i < moduleIds.length; ++i) {
|
||||
const id = moduleIds[i];
|
||||
const mod = new NativeModule(id);
|
||||
NativeModule.map.set(id, mod);
|
||||
}
|
||||
|
||||
function nativeModuleRequire(id) {
|
||||
if (id === loaderId) {
|
||||
return loaderExports;
|
||||
}
|
||||
|
||||
const mod = NativeModule.map.get(id);
|
||||
// Can't load the internal errors module from here, have to use a raw error.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
if (!mod) throw new TypeError(`Missing internal module '${id}'`);
|
||||
return mod.compile();
|
||||
}
|
||||
|
||||
NativeModule.exists = function(id) {
|
||||
static exists(id) {
|
||||
return NativeModule.map.has(id);
|
||||
};
|
||||
}
|
||||
|
||||
NativeModule.canBeRequiredByUsers = function(id) {
|
||||
static canBeRequiredByUsers(id) {
|
||||
const mod = NativeModule.map.get(id);
|
||||
return mod && mod.canBeRequiredByUsers;
|
||||
};
|
||||
|
||||
// Allow internal modules from dependencies to require
|
||||
// other modules from dependencies by providing fallbacks.
|
||||
function requireWithFallbackInDeps(request) {
|
||||
if (!NativeModule.map.has(request)) {
|
||||
request = `internal/deps/${request}`;
|
||||
}
|
||||
return nativeModuleRequire(request);
|
||||
}
|
||||
|
||||
// This is exposed for public loaders
|
||||
NativeModule.prototype.compileForPublicLoader = function() {
|
||||
// Used by user-land module loaders to compile and load builtins.
|
||||
compileForPublicLoader() {
|
||||
if (!this.canBeRequiredByUsers) {
|
||||
// No code because this is an assertion against bugs
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
throw new Error(`Should not compile ${this.id} for public use`);
|
||||
}
|
||||
this.compile();
|
||||
this.compileForInternalLoader();
|
||||
if (!this.exportKeys) {
|
||||
// When using --expose-internals, we do not want to reflect the named
|
||||
// exports from core modules as this can trigger unnecessary getters.
|
||||
@ -229,22 +223,12 @@ NativeModule.prototype.compileForPublicLoader = function() {
|
||||
this.getESMFacade();
|
||||
this.syncExports();
|
||||
return this.exports;
|
||||
};
|
||||
}
|
||||
|
||||
const getOwn = (target, property, receiver) => {
|
||||
return ObjectPrototypeHasOwnProperty(target, property) ?
|
||||
ReflectGet(target, property, receiver) :
|
||||
undefined;
|
||||
};
|
||||
|
||||
NativeModule.prototype.getURL = function() {
|
||||
return `node:${this.id}`;
|
||||
};
|
||||
|
||||
NativeModule.prototype.getESMFacade = function() {
|
||||
getESMFacade() {
|
||||
if (this.module) return this.module;
|
||||
const { ModuleWrap } = internalBinding('module_wrap');
|
||||
const url = this.getURL();
|
||||
const url = `node:${this.id}`;
|
||||
const nativeModule = this;
|
||||
this.module = new ModuleWrap(
|
||||
url, undefined, [...this.exportKeys, 'default'],
|
||||
@ -256,13 +240,13 @@ NativeModule.prototype.getESMFacade = function() {
|
||||
this.module.instantiate();
|
||||
this.module.evaluate(-1, false);
|
||||
return this.module;
|
||||
};
|
||||
}
|
||||
|
||||
// Provide named exports for all builtin libraries so that the libraries
|
||||
// may be imported in a nicer way for ESM users. The default export is left
|
||||
// as the entire namespace (module.exports) and updates when this function is
|
||||
// called so that APMs and other behavior are supported.
|
||||
NativeModule.prototype.syncExports = function() {
|
||||
// Provide named exports for all builtin libraries so that the libraries
|
||||
// may be imported in a nicer way for ESM users. The default export is left
|
||||
// as the entire namespace (module.exports) and updates when this function is
|
||||
// called so that APMs and other behavior are supported.
|
||||
syncExports() {
|
||||
const names = this.exportKeys;
|
||||
if (this.module) {
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
@ -272,9 +256,9 @@ NativeModule.prototype.syncExports = function() {
|
||||
getOwn(this.exports, exportName, this.exports));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
NativeModule.prototype.compile = function() {
|
||||
compileForInternalLoader() {
|
||||
if (this.loaded || this.loading) {
|
||||
return this.exports;
|
||||
}
|
||||
@ -296,7 +280,37 @@ NativeModule.prototype.compile = function() {
|
||||
|
||||
moduleLoadList.push(`NativeModule ${id}`);
|
||||
return this.exports;
|
||||
}
|
||||
}
|
||||
|
||||
// Think of this as module.exports in this file even though it is not
|
||||
// written in CommonJS style.
|
||||
const loaderExports = {
|
||||
internalBinding,
|
||||
NativeModule,
|
||||
require: nativeModuleRequire
|
||||
};
|
||||
|
||||
// This will be passed to internal/bootstrap/node.js.
|
||||
function nativeModuleRequire(id) {
|
||||
if (id === loaderId) {
|
||||
return loaderExports;
|
||||
}
|
||||
|
||||
const mod = NativeModule.map.get(id);
|
||||
// Can't load the internal errors module from here, have to use a raw error.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
if (!mod) throw new TypeError(`Missing internal module '${id}'`);
|
||||
return mod.compileForInternalLoader();
|
||||
}
|
||||
|
||||
// Allow internal modules from dependencies to require
|
||||
// other modules from dependencies by providing fallbacks.
|
||||
function requireWithFallbackInDeps(request) {
|
||||
if (!NativeModule.map.has(request)) {
|
||||
request = `internal/deps/${request}`;
|
||||
}
|
||||
return nativeModuleRequire(request);
|
||||
}
|
||||
|
||||
// Pass the exports back to C++ land for C++ internals to use.
|
||||
return loaderExports;
|
||||
|
Loading…
x
Reference in New Issue
Block a user