diff --git a/lib/async.js b/lib/async.js index 7f6517a8..a0169dcd 100644 --- a/lib/async.js +++ b/lib/async.js @@ -3,6 +3,7 @@ var fs = require('fs'); var path = require('path'); var caller = require('./caller.js'); var nodeModulesPaths = require('./node-modules-paths.js'); +var useProcessResolution = require('./use-process-resolution.js'); var defaultIsFile = function isFile(file, cb) { fs.stat(file, function (err, stat) { @@ -38,6 +39,10 @@ module.exports = function resolve(x, options, callback) { }); } + if (opts.useProcessResolution) { + useProcessResolution(opts); + } + var isFile = opts.isFile || defaultIsFile; var isDirectory = opts.isDirectory || defaultIsDir; var readFile = opts.readFile || fs.readFile; @@ -225,6 +230,7 @@ module.exports = function resolve(x, options, callback) { } } function loadNodeModules(x, start, cb) { + opts.request = x; processDirs(cb, nodeModulesPaths(start, opts)); } }; diff --git a/lib/node-modules-paths.js b/lib/node-modules-paths.js index 658ea137..8af65aa4 100644 --- a/lib/node-modules-paths.js +++ b/lib/node-modules-paths.js @@ -20,25 +20,37 @@ module.exports = function nodeModulesPaths(start, opts) { } } - var prefix = '/'; - if ((/^([A-Za-z]:)/).test(absoluteStart)) { - prefix = ''; - } else if ((/^\\\\/).test(absoluteStart)) { - prefix = '\\\\'; - } + var dirs = []; + + if (!opts || !opts.skipNodeModules) { + var prefix = '/'; + if ((/^([A-Za-z]:)/).test(absoluteStart)) { + prefix = ''; + } else if ((/^\\\\/).test(absoluteStart)) { + prefix = '\\\\'; + } - var paths = [absoluteStart]; - var parsed = parse(absoluteStart); - while (parsed.dir !== paths[paths.length - 1]) { - paths.push(parsed.dir); - parsed = parse(parsed.dir); + var paths = [absoluteStart]; + var parsed = parse(absoluteStart); + while (parsed.dir !== paths[paths.length - 1]) { + paths.push(parsed.dir); + parsed = parse(parsed.dir); + } + + dirs = paths.reduce(function (dirs, aPath) { + return dirs.concat(modules.map(function (moduleDir) { + return path.join(prefix, aPath, moduleDir); + })); + }, []); } - var dirs = paths.reduce(function (dirs, aPath) { - return dirs.concat(modules.map(function (moduleDir) { - return path.join(prefix, aPath, moduleDir); - })); - }, []); + if (opts && opts.paths) { + if (typeof opts.paths === 'function') { + dirs = dirs.concat(opts.paths(absoluteStart, opts)); + } else { + dirs = dirs.concat(opts.paths); + } + } - return opts && opts.paths ? dirs.concat(opts.paths) : dirs; + return dirs; }; diff --git a/lib/sync.js b/lib/sync.js index d9737e2b..29055040 100644 --- a/lib/sync.js +++ b/lib/sync.js @@ -3,6 +3,7 @@ var fs = require('fs'); var path = require('path'); var caller = require('./caller.js'); var nodeModulesPaths = require('./node-modules-paths.js'); +var useProcessResolution = require('./use-process-resolution.js'); var defaultIsFile = function isFile(file) { try { @@ -29,6 +30,11 @@ module.exports = function (x, options) { throw new TypeError('Path must be a string.'); } var opts = options || {}; + + if (opts.useProcessResolution) { + useProcessResolution(opts); + } + var isFile = opts.isFile || defaultIsFile; var isDirectory = opts.isDirectory || defaultIsDir; var readFileSync = opts.readFileSync || fs.readFileSync; @@ -137,6 +143,7 @@ module.exports = function (x, options) { } function loadNodeModulesSync(x, start) { + opts.request = x; var dirs = nodeModulesPaths(start, opts); for (var i = 0; i < dirs.length; i++) { var dir = dirs[i]; diff --git a/lib/use-process-resolution.js b/lib/use-process-resolution.js new file mode 100644 index 00000000..bc2fcdc7 --- /dev/null +++ b/lib/use-process-resolution.js @@ -0,0 +1,31 @@ +var path = require('path'); + +module.exports = function processResolution(opts) { + if (process.versions.pnp) { + var pnp = require('pnpapi'); + + opts.preserveSymlinks = true; + opts.skipNodeModules = true; + opts.paths = function (basedir, opts) { + if (!opts || !opts.request) { + throw new Error('Missing request option'); + } + + // Extract the name of the package being requested (1=full name, 2=scope name, 3=local name) + var parts = opts.request.match(/^((?:(@[^/]+)\/)?([^/]+))/); + + // This is guaranteed to return the path to the "package.json" file from the given package + var manifestPath = pnp.resolveToUnqualified(parts[1] + '/package.json', basedir); + + // The first dirname strips the package.json, the second strips the local named folder + var nodeModules = path.dirname(path.dirname(manifestPath)); + + // Strips the scope named folder if needed + if (parts[2]) { + nodeModules = path.dirname(nodeModules); + } + + return [nodeModules]; + }; + } +}; diff --git a/readme.markdown b/readme.markdown index 85d731fa..dc20b914 100644 --- a/readme.markdown +++ b/readme.markdown @@ -73,6 +73,14 @@ options are: * returns - a relative path that will be joined from the package.json location * opts.paths - require.paths array to use if nothing is found on the normal `node_modules` recursive walk (probably don't use this) +For advanced users, `paths` can also be a `opts.paths(start, opts)` function + * start - lookup path + * opts - the resolution options + * opts.request - the import specifier being resolved + +* opts.useProcessResolution - instructs `resolve` to use the same resolution algorithm than the one used by the current process + +* opts.skipNodeModules - instructs `resolve` to ignore any `node_modules` directory when doing the resolution * opts.moduleDirectory - directory (or directories) in which to recursively look for modules. default: `"node_modules"` diff --git a/test/node-modules-paths.js b/test/node-modules-paths.js index a917f063..f5e08ed3 100644 --- a/test/node-modules-paths.js +++ b/test/node-modules-paths.js @@ -7,6 +7,11 @@ var nodeModulesPaths = require('../lib/node-modules-paths'); var verifyDirs = function verifyDirs(t, start, dirs, moduleDirectories, paths) { var moduleDirs = [].concat(moduleDirectories || 'node_modules'); + if (paths) { + for (var k = 0; k < paths.length; ++k) { + moduleDirs.push(path.basename(paths[k])); + } + } var foundModuleDirs = {}; var uniqueDirs = {}; @@ -20,7 +25,7 @@ var verifyDirs = function verifyDirs(t, start, dirs, moduleDirectories, paths) { } t.equal(keys(parsedDirs).length >= start.split(path.sep).length, true, 'there are >= dirs than "start" has'); var foundModuleDirNames = keys(foundModuleDirs); - t.deepEqual(foundModuleDirNames, moduleDirs.concat(paths || []), 'all desired module dirs were found'); + t.deepEqual(foundModuleDirNames, moduleDirs, 'all desired module dirs were found'); t.equal(keys(uniqueDirs).length, dirs.length, 'all dirs provided were unique'); var counts = {}; @@ -49,7 +54,16 @@ test('node-modules-paths', function (t) { t.end(); }); - t.test('with paths option', function (t) { + t.test('with skipNodeModules=true option', function (t) { + var start = path.join(__dirname, 'resolver'); + var dirs = nodeModulesPaths(start, { skipNodeModules: true }); + + t.deepEqual(dirs, [], 'no node_modules was computed'); + + t.end(); + }); + + t.test('with paths=array option', function (t) { var start = path.join(__dirname, 'resolver'); var paths = ['a', 'b']; var dirs = nodeModulesPaths(start, { paths: paths }); @@ -59,6 +73,19 @@ test('node-modules-paths', function (t) { t.end(); }); + t.test('with paths=function option', function (t) { + var paths = function paths(absoluteStart, opts) { + return [path.join(absoluteStart, 'not node modules', opts.request)]; + }; + + var start = path.join(__dirname, 'resolver'); + var dirs = nodeModulesPaths(start, { paths: paths, request: 'pkg' }); + + verifyDirs(t, start, dirs, null, [path.join(start, 'not node modules', 'pkg')]); + + t.end(); + }); + t.test('with moduleDirectory option', function (t) { var start = path.join(__dirname, 'resolver'); var moduleDirectory = 'not node modules';