Files
node/lib/internal/loader/Loader.js
T
guybedford d21a11dc23 module: resolve and instantiate loader pipeline hooks
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>
2017-10-11 08:47:23 -05:00

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;