summaryrefslogtreecommitdiff
path: root/lib/internal
diff options
context:
space:
mode:
authorJan Krems <jan.krems@gmail.com>2019-08-19 20:59:25 -0700
committerJan Krems <jan.krems@gmail.com>2019-10-24 15:14:38 -0700
commit71bcd05232b4fc21db20e5acf019f97780050568 (patch)
treeff58c1a5c27e3e28755395e1b07b0394583d96e1 /lib/internal
parentd53dd8b0a00d3e00e97f46ae4ae67afa31c10526 (diff)
downloadandroid-node-v8-71bcd05232b4fc21db20e5acf019f97780050568.tar.gz
android-node-v8-71bcd05232b4fc21db20e5acf019f97780050568.tar.bz2
android-node-v8-71bcd05232b4fc21db20e5acf019f97780050568.zip
module: resolve self-references
Adds the ability to `import` or `require` a package from within its own source code. This allows tests and examples to be written using the package name, making them easier to reuse by consumers of the package. Assuming the `name` field in `package.json` is set to `my-pkg`, its test could use `require('my-pkg')` or `import 'my-pkg'` even if there's no `node_modules/my-pkg` while testing the package itself. An important difference between this and relative specifiers like `require('../')` is that self-references use the public interface of the package as defined in the `exports` field while relative specifiers don't. This behavior is guarded by a new experimental flag (`--experimental-resolve-self`). PR-URL: https://github.com/nodejs/node/pull/29327 Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Michaƫl Zasso <targos@protonmail.com>
Diffstat (limited to 'lib/internal')
-rw-r--r--lib/internal/modules/cjs/loader.js166
1 files changed, 129 insertions, 37 deletions
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
index a28383690a..7df91ce4fd 100644
--- a/lib/internal/modules/cjs/loader.js
+++ b/lib/internal/modules/cjs/loader.js
@@ -58,6 +58,7 @@ const enableSourceMaps = getOptionValue('--enable-source-maps');
const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const experimentalModules = getOptionValue('--experimental-modules');
+const experimentalSelf = getOptionValue('--experimental-resolve-self');
const manifest = getOptionValue('--experimental-policy') ?
require('internal/process/policy').manifest :
null;
@@ -241,6 +242,7 @@ function readPackage(requestPath) {
try {
const parsed = JSON.parse(json);
const filtered = {
+ name: parsed.name,
main: parsed.main,
exports: parsed.exports,
type: parsed.type
@@ -370,6 +372,125 @@ function findLongestRegisteredExtension(filename) {
return '.js';
}
+function resolveBasePath(basePath, exts, isMain, trailingSlash, request) {
+ let filename;
+
+ const rc = stat(basePath);
+ if (!trailingSlash) {
+ if (rc === 0) { // File.
+ if (!isMain) {
+ if (preserveSymlinks) {
+ filename = path.resolve(basePath);
+ } else {
+ filename = toRealPath(basePath);
+ }
+ } else if (preserveSymlinksMain) {
+ // For the main module, we use the preserveSymlinksMain flag instead
+ // mainly for backward compatibility, as the preserveSymlinks flag
+ // historically has not applied to the main module. Most likely this
+ // was intended to keep .bin/ binaries working, as following those
+ // symlinks is usually required for the imports in the corresponding
+ // files to resolve; that said, in some use cases following symlinks
+ // causes bigger problems which is why the preserveSymlinksMain option
+ // is needed.
+ filename = path.resolve(basePath);
+ } else {
+ filename = toRealPath(basePath);
+ }
+ }
+
+ if (!filename) {
+ // Try it with each of the extensions
+ if (exts === undefined)
+ exts = Object.keys(Module._extensions);
+ filename = tryExtensions(basePath, exts, isMain);
+ }
+ }
+
+ if (!filename && rc === 1) { // Directory.
+ // try it with each of the extensions at "index"
+ if (exts === undefined)
+ exts = Object.keys(Module._extensions);
+ filename = tryPackage(basePath, exts, isMain, request);
+ }
+
+ return filename;
+}
+
+function trySelf(paths, exts, isMain, trailingSlash, request) {
+ if (!experimentalSelf) {
+ return false;
+ }
+
+ const { data: pkg, path: basePath } = readPackageScope(paths[0]);
+ if (!pkg) return false;
+ if (typeof pkg.name !== 'string') return false;
+
+ let expansion;
+ if (request === pkg.name) {
+ expansion = '';
+ } else if (StringPrototype.startsWith(request, `${pkg.name}/`)) {
+ expansion = StringPrototype.slice(request, pkg.name.length);
+ } else {
+ return false;
+ }
+
+ if (exts === undefined)
+ exts = Object.keys(Module._extensions);
+
+ if (expansion) {
+ // Use exports
+ const fromExports = applyExports(basePath, expansion);
+ if (!fromExports) return false;
+ return resolveBasePath(fromExports, exts, isMain, trailingSlash, request);
+ } else {
+ // Use main field
+ return tryPackage(basePath, exts, isMain, request);
+ }
+}
+
+function applyExports(basePath, expansion) {
+ const pkgExports = readPackageExports(basePath);
+ const mappingKey = `.${expansion}`;
+
+ if (typeof pkgExports === 'object' && pkgExports !== null) {
+ if (ObjectPrototype.hasOwnProperty(pkgExports, mappingKey)) {
+ const mapping = pkgExports[mappingKey];
+ return resolveExportsTarget(pathToFileURL(basePath + '/'), mapping, '',
+ basePath, mappingKey);
+ }
+
+ let dirMatch = '';
+ for (const candidateKey of Object.keys(pkgExports)) {
+ if (candidateKey[candidateKey.length - 1] !== '/') continue;
+ if (candidateKey.length > dirMatch.length &&
+ StringPrototype.startsWith(mappingKey, candidateKey)) {
+ dirMatch = candidateKey;
+ }
+ }
+
+ if (dirMatch !== '') {
+ const mapping = pkgExports[dirMatch];
+ const subpath = StringPrototype.slice(mappingKey, dirMatch.length);
+ return resolveExportsTarget(pathToFileURL(basePath + '/'), mapping,
+ subpath, basePath, mappingKey);
+ }
+ }
+ if (mappingKey === '.' && typeof pkgExports === 'string') {
+ return resolveExportsTarget(pathToFileURL(basePath + '/'), pkgExports,
+ '', basePath, mappingKey);
+ }
+ if (pkgExports != null) {
+ // eslint-disable-next-line no-restricted-syntax
+ const e = new Error(`Package exports for '${basePath}' do not define ` +
+ `a '${mappingKey}' subpath`);
+ e.code = 'MODULE_NOT_FOUND';
+ throw e;
+ }
+
+ return path.resolve(basePath, mappingKey);
+}
+
// This only applies to requests of a specific form:
// 1. name/.*
// 2. @scope/name/.*
@@ -384,43 +505,7 @@ function resolveExports(nmPath, request, absoluteRequest) {
}
const basePath = path.resolve(nmPath, name);
- const pkgExports = readPackageExports(basePath);
- const mappingKey = `.${expansion}`;
-
- if (typeof pkgExports === 'object' && pkgExports !== null) {
- if (ObjectPrototype.hasOwnProperty(pkgExports, mappingKey)) {
- const mapping = pkgExports[mappingKey];
- return resolveExportsTarget(pathToFileURL(basePath + '/'), mapping, '',
- basePath, mappingKey);
- }
-
- let dirMatch = '';
- for (const candidateKey of Object.keys(pkgExports)) {
- if (candidateKey[candidateKey.length - 1] !== '/') continue;
- if (candidateKey.length > dirMatch.length &&
- StringPrototype.startsWith(mappingKey, candidateKey)) {
- dirMatch = candidateKey;
- }
- }
-
- if (dirMatch !== '') {
- const mapping = pkgExports[dirMatch];
- const subpath = StringPrototype.slice(mappingKey, dirMatch.length);
- return resolveExportsTarget(pathToFileURL(basePath + '/'), mapping,
- subpath, basePath, mappingKey);
- }
- }
- if (mappingKey === '.' && typeof pkgExports === 'string') {
- return resolveExportsTarget(pathToFileURL(basePath + '/'), pkgExports,
- '', basePath, mappingKey);
- }
- if (pkgExports != null) {
- // eslint-disable-next-line no-restricted-syntax
- const e = new Error(`Package exports for '${basePath}' do not define ` +
- `a '${mappingKey}' subpath`);
- e.code = 'MODULE_NOT_FOUND';
- throw e;
- }
+ return applyExports(basePath, expansion);
}
return path.resolve(nmPath, request);
@@ -536,6 +621,13 @@ Module._findPath = function(request, paths, isMain) {
return filename;
}
}
+
+ const selfFilename = trySelf(paths, exts, isMain, trailingSlash, request);
+ if (selfFilename) {
+ Module._pathCache[cacheKey] = selfFilename;
+ return selfFilename;
+ }
+
return false;
};