Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for the exports package.json attribute #224

Open
wants to merge 4 commits into
base: 1.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions .editorconfig
Expand Up @@ -17,7 +17,7 @@ block_comment_end = */
[*.yml]
indent_size = 1

[package.json]
[{package.json,.gitmodules}]
indent_style = tab

[CHANGELOG.md]
Expand All @@ -27,7 +27,7 @@ indent_size = 2
[{*.json,Makefile}]
max_line_length = off

[test/{dotdot,resolver,module_dir,multirepo,node_path,pathfilter,precedence}/**/*]
[test/{dotdot,exports,list-exports,resolver,module_dir,multirepo,node_path,pathfilter,precedence}/**/*]
indent_style = off
indent_size = off
max_line_length = off
Expand Down
1 change: 1 addition & 0 deletions .eslintignore
@@ -1 +1,2 @@
node_modules
test/list-exports/
1 change: 1 addition & 0 deletions .eslintrc
Expand Up @@ -27,6 +27,7 @@
"object-curly-newline": 0,
"operator-linebreak": [2, "before"],
"sort-keys": 0,
"eqeqeq": [2, "always", {"null": "ignore"}]
},
"overrides": [
{
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -9,3 +9,6 @@ yarn.lock

# symlinked file used in tests
test/resolver/symlinked/_/node_modules/package

# submodule
test/list-exports
4 changes: 4 additions & 0 deletions .gitmodules
@@ -0,0 +1,4 @@
[submodule "test/list-exports"]
path = test/list-exports
url = https://github.com/ljharb/list-exports.git
branch = main
117 changes: 101 additions & 16 deletions lib/async.js
@@ -1,9 +1,11 @@
/* eslint-disable max-lines */
var fs = require('fs');
var path = require('path');
var caller = require('./caller');
var nodeModulesPaths = require('./node-modules-paths');
var normalizeOptions = require('./normalize-options');
var isCore = require('is-core-module');
var resolveExports = require('./resolve-imports-exports');

var realpathFS = fs.realpath && typeof fs.realpath.native === 'function' ? fs.realpath.native : fs.realpath;

Expand Down Expand Up @@ -77,6 +79,12 @@ module.exports = function resolve(x, options, callback) {
var basedir = opts.basedir || path.dirname(caller());
var parent = opts.filename || basedir;

if (opts.exportsField == null) {
opts.exportsField = { level: 'ignore' };
} else if (typeof opts.exportsField === 'string') {
opts.exportsField = { level: opts.exportsField };
}

opts.paths = opts.paths || [];

// ensure that `basedir` is an absolute path at this point, resolving against the process' current working directory
Expand Down Expand Up @@ -265,35 +273,112 @@ module.exports = function resolve(x, options, callback) {
});
}

function processDirs(cb, dirs) {
function loadManifestInDir(dir, cb) {
maybeRealpath(realpath, dir, opts, function (err, pkgdir) {
if (err) return cb(null);

var pkgfile = path.join(pkgdir, 'package.json');
isFile(pkgfile, function (err, ex) {
// on err, ex is false
if (!ex) return cb(null);

readFile(pkgfile, function (err, body) {
if (err) cb(err);
try { var pkg = JSON.parse(body); } catch (jsonErr) {}

if (pkg && opts.packageFilter) {
pkg = opts.packageFilter(pkg, pkgfile, dir);
}
cb(pkg);
});
});
});
}

function processDirs(cb, dirs, subpath) {
if (dirs.length === 0) return cb(null, undefined);
var dir = dirs[0];

isDirectory(path.dirname(dir), isdir);

function isdir(err, isdir) {
if (err) return cb(err);
if (!isdir) return processDirs(cb, dirs.slice(1));
loadAsFile(dir, opts.package, onfile);
if (opts.exportsField.level !== 'ignore' && endsWithSubpath(dir, subpath)) {
var pkgDir = dir.slice(0, dir.length - subpath.length);
loadManifestInDir(pkgDir, onmanifestWithExports);
} else {
onmanifest(false);
}

function onfile(err, m, pkg) {
if (err) return cb(err);
if (m) return cb(null, m, pkg);
loadAsDirectory(dir, opts.package, ondir);
function onmanifestWithExports(pkg) {
if (!pkg || pkg.exports == null) {
return onmanifest(false);
}

var resolvedExport;
try {
resolvedExport = resolveExports(opts.exportsField, pkgDir, parent, subpath, pkg.exports);
} catch (resolveErr) {
return cb(resolveErr);
}

if (resolvedExport.exact) {
isFile(resolvedExport.resolved, function (err, ex) {
if (ex) {
cb(null, resolvedExport.resolved, pkg);
} else {
cb(null, undefined);
}
});
} else {
dir = resolvedExport.resolved;
onmanifest(true);
}
}

function ondir(err, n, pkg) {
if (err) return cb(err);
if (n) return cb(null, n, pkg);
processDirs(cb, dirs.slice(1));
function onmanifest(stop) {
isDirectory(path.dirname(dir), isdir);

function isdir(err, isdir) {
if (err) return cb(err);
if (!isdir) return next();
loadAsFile(dir, opts.package, onfile);
}

function onfile(err, m, pkg) {
if (err) return cb(err);
if (m) return cb(null, m, pkg);
loadAsDirectory(dir, opts.package, ondir);
}

function ondir(err, n, pkg) {
if (err) return cb(err);
if (n) return cb(null, n, pkg);
next();
}

function next() {
if (stop) {
cb(null, undefined);
} else {
processDirs(cb, dirs.slice(1), subpath);
}
}
}
}

function loadNodeModules(x, start, cb) {
var subpathIndex = x.charAt(0) === '@' ? x.indexOf('/', x.indexOf('/') + 1) : x.indexOf('/');
var subpath = subpathIndex === -1 ? '' : x.slice(subpathIndex);

var thunk = function () { return getPackageCandidates(x, start, opts); };

processDirs(
cb,
packageIterator ? packageIterator(x, start, thunk, opts) : thunk()
packageIterator ? packageIterator(x, start, thunk, opts) : thunk(),
subpath
);
}

function endsWithSubpath(dir, subpath) {
var endOfDir = dir.slice(dir.length - subpath.length);

return endOfDir === subpath || endOfDir.replace(/\\/g, '/') === subpath;
}
};