node/lib/internal/loader/ModuleRequest.js
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

123 lines
3.7 KiB
JavaScript

'use strict';
const fs = require('fs');
const internalCJSModule = require('internal/module');
const internalURLModule = require('internal/url');
const internalFS = require('internal/fs');
const NativeModule = require('native_module');
const { extname, _makeLong } = require('path');
const { URL } = require('url');
const { realpathSync } = require('fs');
const preserveSymlinks = !!process.binding('config').preserveSymlinks;
const {
ModuleWrap,
createDynamicModule
} = require('internal/loader/ModuleWrap');
const errors = require('internal/errors');
const search = require('internal/loader/search');
const asyncReadFile = require('util').promisify(require('fs').readFile);
const debug = require('util').debuglog('esm');
const realpathCache = new Map();
const loaders = new Map();
exports.loaders = loaders;
// Strategy for loading a standard JavaScript module
loaders.set('esm', async (url) => {
const source = `${await asyncReadFile(new URL(url))}`;
debug(`Loading StandardModule ${url}`);
return {
module: new ModuleWrap(internalCJSModule.stripShebang(source), url),
reflect: undefined
};
});
// Strategy for loading a node-style CommonJS module
loaders.set('cjs', async (url) => {
return createDynamicModule(['default'], url, (reflect) => {
debug(`Loading CJSModule ${url}`);
const CJSModule = require('module');
const pathname = internalURLModule.getPathFromURL(new URL(url));
CJSModule._load(pathname);
});
});
// Strategy for loading a node builtin CommonJS module that isn't
// through normal resolution
loaders.set('builtin', async (url) => {
return createDynamicModule(['default'], url, (reflect) => {
debug(`Loading BuiltinModule ${url}`);
const exports = NativeModule.require(url.substr(5));
reflect.exports.default.set(exports);
});
});
loaders.set('addon', async (url) => {
const ctx = createDynamicModule(['default'], url, (reflect) => {
debug(`Loading NativeModule ${url}`);
const module = { exports: {} };
const pathname = internalURLModule.getPathFromURL(new URL(url));
process.dlopen(module, _makeLong(pathname));
reflect.exports.default.set(module.exports);
});
return ctx;
});
loaders.set('json', async (url) => {
return createDynamicModule(['default'], url, (reflect) => {
debug(`Loading JSONModule ${url}`);
const pathname = internalURLModule.getPathFromURL(new URL(url));
const content = fs.readFileSync(pathname, 'utf8');
try {
const exports = JSON.parse(internalCJSModule.stripBOM(content));
reflect.exports.default.set(exports);
} catch (err) {
err.message = pathname + ': ' + err.message;
throw err;
}
});
});
exports.resolve = (specifier, parentURL) => {
if (NativeModule.nonInternalExists(specifier)) {
return {
url: specifier,
format: 'builtin'
};
}
let url = search(specifier, parentURL);
if (url.protocol !== 'file:') {
throw new errors.Error('ERR_INVALID_PROTOCOL',
url.protocol, 'file:');
}
if (!preserveSymlinks) {
const real = realpathSync(internalURLModule.getPathFromURL(url), {
[internalFS.realpathCacheKey]: realpathCache
});
const old = url;
url = internalURLModule.getURLFromFilePath(real);
url.search = old.search;
url.hash = old.hash;
}
const ext = extname(url.pathname);
switch (ext) {
case '.mjs':
return { url: `${url}`, format: 'esm' };
case '.json':
return { url: `${url}`, format: 'json' };
case '.node':
return { url: `${url}`, format: 'addon' };
case '.js':
return { url: `${url}`, format: 'cjs' };
default:
throw new errors.Error('ERR_UNKNOWN_FILE_EXTENSION',
internalURLModule.getPathFromURL(url));
}
};