From ef9e9dae6ccced153292eead6dfbc040ae680a95 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 25 Sep 2023 23:50:06 -0700 Subject: [PATCH] WIP: add support for package.json exports --- lib/async.js | 6 ++++++ lib/resolve-exports.js | 40 ++++++++++++++++++++++++++++++++++++++++ lib/sync.js | 7 +++++++ 3 files changed, 53 insertions(+) create mode 100644 lib/resolve-exports.js diff --git a/lib/async.js b/lib/async.js index 07f18a6..08dbaff 100644 --- a/lib/async.js +++ b/lib/async.js @@ -309,6 +309,12 @@ module.exports = function resolve(x, options, callback) { return; } + if (pkg && pkg.exports) { + var res = resolveExports(pkg.exports, ['require', 'node'], '.'); + var exportPath = path.resolve(x, res); + return loadAsFile(exportPath, pkg, cb); + } + loadAsFile(path.join(x, '/index'), pkg, cb); }); }); diff --git a/lib/resolve-exports.js b/lib/resolve-exports.js new file mode 100644 index 0000000..fc8ef02 --- /dev/null +++ b/lib/resolve-exports.js @@ -0,0 +1,40 @@ +// Just walks the exports object synchronously looking for a match. +// Does not validate that the module it finds actually exists. + +// Returns undefined if no match was found, null if a match was explicitly +// forbidden by setting the value to null in the exports object. Either +// null or undefined at the caller value have the same effective meaning, +// no match is available. +var resolveExports = function resolveExports (exp, conditions, sub) { + if (!exp) return exp; + if (typeof exp === 'string' && (sub === '.' || sub === null)) return exp; + // TODO: check if this should throw? + if (typeof exp !== 'object') return null; + if (Array.isArray(exp) && (sub === '.' || sub === null)) { + for (var i = 0; i < exp.length; i++) { + var resolved = resolveExports(exp[i], conditions); + if (resolved || resolved === null) return resolved; + } + } + if (sub !== null) { + if (Object.prototype.hasOwnProperty.call(exp, sub)) { + return resolveExports(exp[sub], conditions, null); + } else if (sub !== '.') { + // sub=./x, exports={require:'./y'}, not a match + return undefined; + } + } + var keys = Object.keys(exp); + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + if (key === 'default') return resolveExports(exp[key], conditions, null); + var k = conditions.indexOf(key); + if (k !== -1) { + const resolved = resolveExports(exp[key], conditions, null); + if (resolved || resolved === null) return resolved; + } + } + return undefined; +} + +module.exports = resolveExports; diff --git a/lib/sync.js b/lib/sync.js index ddc468a..4b093e3 100644 --- a/lib/sync.js +++ b/lib/sync.js @@ -5,6 +5,7 @@ var getHomedir = require('./homedir'); var caller = require('./caller'); var nodeModulesPaths = require('./node-modules-paths'); var normalizeOptions = require('./normalize-options'); +var resolveExports = require('./resolve-exports'); var realpathFS = process.platform !== 'win32' && fs.realpathSync && typeof fs.realpathSync.native === 'function' ? fs.realpathSync.native : fs.realpathSync; @@ -199,6 +200,12 @@ module.exports = function resolveSync(x, options) { incorrectMainError.code = 'INCORRECT_PACKAGE_MAIN'; throw incorrectMainError; } + + if (pkg && pkg.exports) { + var res = resolveExports(pkg.exports, ['require', 'node'], '.'); + var exportPath = path.resolve(x, res); + return loadAsFileSync(exportPath); + } } return loadAsFileSync(path.join(x, '/index'));