mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
This enables a --loader flag for Node, which can provide custom "resolve" and "dynamicInstantiate" methods for custom ES module loading. In the process, module providers have been converted from classes into functions and the module APIs have been made to pass URL strings over objects. PR-URL: https://github.com/nodejs/node/pull/15445 Reviewed-By: Bradley Farias <bradley.meck@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Timothy Gu <timothygu99@gmail.com>
107 lines
3.2 KiB
JavaScript
107 lines
3.2 KiB
JavaScript
'use strict';
|
|
|
|
const { getURLFromFilePath } = require('internal/url');
|
|
|
|
const {
|
|
getNamespaceOfModuleWrap,
|
|
createDynamicModule
|
|
} = require('internal/loader/ModuleWrap');
|
|
|
|
const ModuleMap = require('internal/loader/ModuleMap');
|
|
const ModuleJob = require('internal/loader/ModuleJob');
|
|
const ModuleRequest = require('internal/loader/ModuleRequest');
|
|
const errors = require('internal/errors');
|
|
const debug = require('util').debuglog('esm');
|
|
|
|
function getBase() {
|
|
try {
|
|
return getURLFromFilePath(`${process.cwd()}/`).href;
|
|
} catch (e) {
|
|
e.stack;
|
|
// If the current working directory no longer exists.
|
|
if (e.code === 'ENOENT') {
|
|
return undefined;
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
class Loader {
|
|
constructor(base = getBase()) {
|
|
this.moduleMap = new ModuleMap();
|
|
if (typeof base !== 'string') {
|
|
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'base', 'string');
|
|
}
|
|
this.base = base;
|
|
this.resolver = ModuleRequest.resolve.bind(null);
|
|
this.dynamicInstantiate = undefined;
|
|
}
|
|
|
|
hook({ resolve = ModuleRequest.resolve, dynamicInstantiate }) {
|
|
this.resolver = resolve.bind(null);
|
|
this.dynamicInstantiate = dynamicInstantiate;
|
|
}
|
|
|
|
async resolve(specifier, parentURL = this.base) {
|
|
if (typeof parentURL !== 'string') {
|
|
throw new errors.TypeError('ERR_INVALID_ARG_TYPE',
|
|
'parentURL', 'string');
|
|
}
|
|
const { url, format } = await this.resolver(specifier, parentURL,
|
|
ModuleRequest.resolve);
|
|
|
|
if (typeof format !== 'string') {
|
|
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'format',
|
|
['esm', 'cjs', 'builtin', 'addon', 'json']);
|
|
}
|
|
if (typeof url !== 'string') {
|
|
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'url', 'string');
|
|
}
|
|
|
|
if (format === 'builtin') {
|
|
return { url: `node:${url}`, format };
|
|
}
|
|
|
|
if (format !== 'dynamic') {
|
|
if (!ModuleRequest.loaders.has(format)) {
|
|
throw new errors.Error('ERR_UNKNOWN_MODULE_FORMAT', format);
|
|
}
|
|
if (!url.startsWith('file:')) {
|
|
throw new errors.Error('ERR_INVALID_PROTOCOL', url, 'file:');
|
|
}
|
|
}
|
|
|
|
return { url, format };
|
|
}
|
|
|
|
async getModuleJob(specifier, parentURL = this.base) {
|
|
const { url, format } = await this.resolve(specifier, parentURL);
|
|
let job = this.moduleMap.get(url);
|
|
if (job === undefined) {
|
|
let loaderInstance;
|
|
if (format === 'dynamic') {
|
|
loaderInstance = async (url) => {
|
|
const { exports, execute } = await this.dynamicInstantiate(url);
|
|
return createDynamicModule(exports, url, (reflect) => {
|
|
debug(`Loading custom loader ${url}`);
|
|
execute(reflect.exports);
|
|
});
|
|
};
|
|
} else {
|
|
loaderInstance = ModuleRequest.loaders.get(format);
|
|
}
|
|
job = new ModuleJob(this, url, loaderInstance);
|
|
this.moduleMap.set(url, job);
|
|
}
|
|
return job;
|
|
}
|
|
|
|
async import(specifier, parentURL = this.base) {
|
|
const job = await this.getModuleJob(specifier, parentURL);
|
|
const module = await job.run();
|
|
return getNamespaceOfModuleWrap(module);
|
|
}
|
|
}
|
|
Object.setPrototypeOf(Loader.prototype, null);
|
|
module.exports = Loader;
|