mirror of
https://github.com/zebrajr/node.git
synced 2025-12-07 00:20:38 +01:00
This simplifies the top-level load when ES modules are enabled as we can entirely delegate the module resolver, which will hand over to CommonJS where appropriate. All not found errors are made consistent to throw during resolve and have the MODULE_NOT_FOUND code. Includes the test case from https://github.com/nodejs/node/pull/15736. Fixes: https://github.com/nodejs/node/issues/15732 PR-URL: https://github.com/nodejs/node/pull/16147 Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
108 lines
3.2 KiB
JavaScript
108 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;
|