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
|
const loaderId = 'internal/bootstrap/loaders';
|
||||||
// written in CommonJS style.
|
const {
|
||||||
const loaderExports = {
|
moduleIds,
|
||||||
internalBinding,
|
compileFunction
|
||||||
NativeModule,
|
} = internalBinding('native_module');
|
||||||
require: nativeModuleRequire
|
|
||||||
|
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.
|
constructor(id) {
|
||||||
function NativeModule(id) {
|
|
||||||
this.filename = `${id}.js`;
|
this.filename = `${id}.js`;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
this.canBeRequiredByUsers = !id.startsWith('internal/');
|
||||||
|
|
||||||
|
// The CJS exports object of the module.
|
||||||
this.exports = {};
|
this.exports = {};
|
||||||
this.module = undefined;
|
// States used to work around circular dependencies.
|
||||||
this.exportKeys = undefined;
|
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.canBeRequiredByUsers = !id.startsWith('internal/');
|
|
||||||
}
|
|
||||||
|
|
||||||
// To be called during pre-execution when --expose-internals is on.
|
// The following properties are used by the ESM implementation and only
|
||||||
// Enables the user-land module loader to access internal modules.
|
// initialized when the native module is loaded by users.
|
||||||
NativeModule.exposeInternals = function() {
|
/**
|
||||||
|
* 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) {
|
for (const [id, mod] of NativeModule.map) {
|
||||||
// Do not expose this to user land even with --expose-internals.
|
// Do not expose this to user land even with --expose-internals.
|
||||||
if (id !== loaderId) {
|
if (id !== loaderId) {
|
||||||
mod.canBeRequiredByUsers = true;
|
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);
|
static exists(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) {
|
|
||||||
return NativeModule.map.has(id);
|
return NativeModule.map.has(id);
|
||||||
};
|
}
|
||||||
|
|
||||||
NativeModule.canBeRequiredByUsers = function(id) {
|
static canBeRequiredByUsers(id) {
|
||||||
const mod = NativeModule.map.get(id);
|
const mod = NativeModule.map.get(id);
|
||||||
return mod && mod.canBeRequiredByUsers;
|
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
|
// Used by user-land module loaders to compile and load builtins.
|
||||||
NativeModule.prototype.compileForPublicLoader = function() {
|
compileForPublicLoader() {
|
||||||
if (!this.canBeRequiredByUsers) {
|
if (!this.canBeRequiredByUsers) {
|
||||||
// No code because this is an assertion against bugs
|
// No code because this is an assertion against bugs
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
throw new Error(`Should not compile ${this.id} for public use`);
|
throw new Error(`Should not compile ${this.id} for public use`);
|
||||||
}
|
}
|
||||||
this.compile();
|
this.compileForInternalLoader();
|
||||||
if (!this.exportKeys) {
|
if (!this.exportKeys) {
|
||||||
// When using --expose-internals, we do not want to reflect the named
|
// When using --expose-internals, we do not want to reflect the named
|
||||||
// exports from core modules as this can trigger unnecessary getters.
|
// exports from core modules as this can trigger unnecessary getters.
|
||||||
@ -229,22 +223,12 @@ NativeModule.prototype.compileForPublicLoader = function() {
|
|||||||
this.getESMFacade();
|
this.getESMFacade();
|
||||||
this.syncExports();
|
this.syncExports();
|
||||||
return this.exports;
|
return this.exports;
|
||||||
};
|
}
|
||||||
|
|
||||||
const getOwn = (target, property, receiver) => {
|
getESMFacade() {
|
||||||
return ObjectPrototypeHasOwnProperty(target, property) ?
|
|
||||||
ReflectGet(target, property, receiver) :
|
|
||||||
undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
NativeModule.prototype.getURL = function() {
|
|
||||||
return `node:${this.id}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
NativeModule.prototype.getESMFacade = function() {
|
|
||||||
if (this.module) return this.module;
|
if (this.module) return this.module;
|
||||||
const { ModuleWrap } = internalBinding('module_wrap');
|
const { ModuleWrap } = internalBinding('module_wrap');
|
||||||
const url = this.getURL();
|
const url = `node:${this.id}`;
|
||||||
const nativeModule = this;
|
const nativeModule = this;
|
||||||
this.module = new ModuleWrap(
|
this.module = new ModuleWrap(
|
||||||
url, undefined, [...this.exportKeys, 'default'],
|
url, undefined, [...this.exportKeys, 'default'],
|
||||||
@ -256,13 +240,13 @@ NativeModule.prototype.getESMFacade = function() {
|
|||||||
this.module.instantiate();
|
this.module.instantiate();
|
||||||
this.module.evaluate(-1, false);
|
this.module.evaluate(-1, false);
|
||||||
return this.module;
|
return this.module;
|
||||||
};
|
}
|
||||||
|
|
||||||
// Provide named exports for all builtin libraries so that the libraries
|
// 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
|
// 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
|
// as the entire namespace (module.exports) and updates when this function is
|
||||||
// called so that APMs and other behavior are supported.
|
// called so that APMs and other behavior are supported.
|
||||||
NativeModule.prototype.syncExports = function() {
|
syncExports() {
|
||||||
const names = this.exportKeys;
|
const names = this.exportKeys;
|
||||||
if (this.module) {
|
if (this.module) {
|
||||||
for (let i = 0; i < names.length; i++) {
|
for (let i = 0; i < names.length; i++) {
|
||||||
@ -272,9 +256,9 @@ NativeModule.prototype.syncExports = function() {
|
|||||||
getOwn(this.exports, exportName, this.exports));
|
getOwn(this.exports, exportName, this.exports));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
NativeModule.prototype.compile = function() {
|
compileForInternalLoader() {
|
||||||
if (this.loaded || this.loading) {
|
if (this.loaded || this.loading) {
|
||||||
return this.exports;
|
return this.exports;
|
||||||
}
|
}
|
||||||
@ -296,7 +280,37 @@ NativeModule.prototype.compile = function() {
|
|||||||
|
|
||||||
moduleLoadList.push(`NativeModule ${id}`);
|
moduleLoadList.push(`NativeModule ${id}`);
|
||||||
return this.exports;
|
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;
|
return loaderExports;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user