From 12d87a4379b6953ce2dac1491924e5cc4a6bd2fa Mon Sep 17 00:00:00 2001 From: Andrew Hyndman Date: Mon, 12 Sep 2022 06:33:54 -0700 Subject: [PATCH] feat(node-resolve): add new option, modulePaths (#1104) * [node-resolve] Implement new modulePaths option * Add unit tests distinguishing modulePaths from moduleDirectories * Document new option * Update packages/node-resolve/test/test.js Co-authored-by: Tom Jenkinson * Update packages/node-resolve/README.md Co-authored-by: Tom Jenkinson * fixup! Update packages/node-resolve/test/test.js * Reject moduleDirectories containing a slash * chore: arbitrary edit to kick github's actions ui Co-authored-by: Andrew Hyndman Co-authored-by: Tom Jenkinson Co-authored-by: Andrew Powell --- packages/node-resolve/README.md | 11 +++++-- packages/node-resolve/src/index.js | 9 +++++- .../src/resolveImportSpecifiers.js | 12 +++++++- .../test/fixtures/custom-module-path/main.js | 3 ++ .../package-with-dependency/main.js | 3 ++ .../node_modules/dependency/main.js | 1 + .../node_modules/dependency/package.json | 3 ++ .../package-with-dependency/package.json | 3 ++ packages/node-resolve/test/test.js | 29 ++++++++++++++++++- packages/node-resolve/types/index.d.ts | 9 +++++- 10 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 packages/node-resolve/test/fixtures/custom-module-path/main.js create mode 100644 packages/node-resolve/test/fixtures/custom-module-path/node_modules/package-with-dependency/main.js create mode 100644 packages/node-resolve/test/fixtures/custom-module-path/node_modules/package-with-dependency/node_modules/dependency/main.js create mode 100644 packages/node-resolve/test/fixtures/custom-module-path/node_modules/package-with-dependency/node_modules/dependency/package.json create mode 100644 packages/node-resolve/test/fixtures/custom-module-path/node_modules/package-with-dependency/package.json diff --git a/packages/node-resolve/README.md b/packages/node-resolve/README.md index 2c8c4120e..210925662 100755 --- a/packages/node-resolve/README.md +++ b/packages/node-resolve/README.md @@ -75,7 +75,14 @@ If `true`, instructs the plugin to use the browser module resolutions in `packag Type: `Array[...String]`
Default: `['node_modules']` -One or more directories in which to recursively look for modules. +A list of directory names in which to recursively look for modules. + +### `modulePaths` + +Type: `Array[...String]`
+Default: `[]` + +A list of absolute paths to additional locations to search for modules. [This is analogous to setting the `NODE_PATH` environment variable for node](https://nodejs.org/api/modules.html#loading-from-the-global-folders). ### `dedupe` @@ -214,7 +221,7 @@ export default ({ }) ``` -## Resolving require statements +## Resolving Require Statements According to [NodeJS module resolution](https://nodejs.org/api/packages.html#packages_package_entry_points) `require` statements should resolve using the `require` condition in the package exports field, while es modules should use the `import` condition. diff --git a/packages/node-resolve/src/index.js b/packages/node-resolve/src/index.js index 7e717a018..0da8f54f2 100644 --- a/packages/node-resolve/src/index.js +++ b/packages/node-resolve/src/index.js @@ -44,7 +44,7 @@ export function nodeResolve(opts = {}) { const { warnings } = handleDeprecatedOptions(opts); const options = { ...defaults, ...opts }; - const { extensions, jail, moduleDirectories, ignoreSideEffectsForRoot } = options; + const { extensions, jail, moduleDirectories, modulePaths, ignoreSideEffectsForRoot } = options; const conditionsEsm = [...baseConditionsEsm, ...(options.exportConditions || [])]; const conditionsCjs = [...baseConditionsCjs, ...(options.exportConditions || [])]; const packageInfoCache = new Map(); @@ -57,6 +57,12 @@ export function nodeResolve(opts = {}) { let { dedupe } = options; let rollupOptions; + if (moduleDirectories.some((name) => name.includes('/'))) { + throw new Error( + '`moduleDirectories` option must only contain directory names. If you want to load modules from somewhere not supported by the default module resolution algorithm, see `modulePaths`.' + ); + } + if (typeof dedupe !== 'function') { dedupe = (importee) => options.dedupe.includes(importee) || options.dedupe.includes(getPackageName(importee)); @@ -167,6 +173,7 @@ export function nodeResolve(opts = {}) { useBrowserOverrides, baseDir, moduleDirectories, + modulePaths, rootDir, ignoreSideEffectsForRoot }); diff --git a/packages/node-resolve/src/resolveImportSpecifiers.js b/packages/node-resolve/src/resolveImportSpecifiers.js index e0a5b0466..39563ad0b 100644 --- a/packages/node-resolve/src/resolveImportSpecifiers.js +++ b/packages/node-resolve/src/resolveImportSpecifiers.js @@ -43,6 +43,7 @@ async function resolveIdClassic({ useBrowserOverrides, baseDir, moduleDirectories, + modulePaths, rootDir, ignoreSideEffectsForRoot }) { @@ -77,6 +78,7 @@ async function resolveIdClassic({ extensions, includeCoreModules: false, moduleDirectory: moduleDirectories, + paths: modulePaths, preserveSymlinks, packageFilter: filter }; @@ -111,6 +113,7 @@ async function resolveWithExportMap({ useBrowserOverrides, baseDir, moduleDirectories, + modulePaths, rootDir, ignoreSideEffectsForRoot }) { @@ -130,7 +133,8 @@ async function resolveWithExportMap({ preserveSymlinks, useBrowserOverrides, baseDir, - moduleDirectories + moduleDirectories, + modulePaths }); } }); @@ -180,6 +184,7 @@ async function resolveWithExportMap({ extensions, includeCoreModules: false, moduleDirectory: moduleDirectories, + paths: modulePaths, preserveSymlinks, packageFilter: filter }; @@ -230,6 +235,7 @@ async function resolveWithClassic({ useBrowserOverrides, baseDir, moduleDirectories, + modulePaths, rootDir, ignoreSideEffectsForRoot }) { @@ -247,6 +253,7 @@ async function resolveWithClassic({ useBrowserOverrides, baseDir, moduleDirectories, + modulePaths, rootDir, ignoreSideEffectsForRoot }); @@ -275,6 +282,7 @@ export default async function resolveImportSpecifiers({ useBrowserOverrides, baseDir, moduleDirectories, + modulePaths, rootDir, ignoreSideEffectsForRoot }) { @@ -290,6 +298,7 @@ export default async function resolveImportSpecifiers({ useBrowserOverrides, baseDir, moduleDirectories, + modulePaths, rootDir, ignoreSideEffectsForRoot }); @@ -315,6 +324,7 @@ export default async function resolveImportSpecifiers({ useBrowserOverrides, baseDir, moduleDirectories, + modulePaths, rootDir, ignoreSideEffectsForRoot }); diff --git a/packages/node-resolve/test/fixtures/custom-module-path/main.js b/packages/node-resolve/test/fixtures/custom-module-path/main.js new file mode 100644 index 000000000..0bf020d0b --- /dev/null +++ b/packages/node-resolve/test/fixtures/custom-module-path/main.js @@ -0,0 +1,3 @@ +import PACKAGE, {dependency} from 'package-with-dependency'; + +export { PACKAGE, dependency }; diff --git a/packages/node-resolve/test/fixtures/custom-module-path/node_modules/package-with-dependency/main.js b/packages/node-resolve/test/fixtures/custom-module-path/node_modules/package-with-dependency/main.js new file mode 100644 index 000000000..3b8eea697 --- /dev/null +++ b/packages/node-resolve/test/fixtures/custom-module-path/node_modules/package-with-dependency/main.js @@ -0,0 +1,3 @@ +export { dependency } from 'dependency'; + +export default 'PACKAGE_WITH_DEPENDENCY'; diff --git a/packages/node-resolve/test/fixtures/custom-module-path/node_modules/package-with-dependency/node_modules/dependency/main.js b/packages/node-resolve/test/fixtures/custom-module-path/node_modules/package-with-dependency/node_modules/dependency/main.js new file mode 100644 index 000000000..65a4dacab --- /dev/null +++ b/packages/node-resolve/test/fixtures/custom-module-path/node_modules/package-with-dependency/node_modules/dependency/main.js @@ -0,0 +1 @@ +export const dependency = 'DEPENDENCY'; diff --git a/packages/node-resolve/test/fixtures/custom-module-path/node_modules/package-with-dependency/node_modules/dependency/package.json b/packages/node-resolve/test/fixtures/custom-module-path/node_modules/package-with-dependency/node_modules/dependency/package.json new file mode 100644 index 000000000..daef9fcad --- /dev/null +++ b/packages/node-resolve/test/fixtures/custom-module-path/node_modules/package-with-dependency/node_modules/dependency/package.json @@ -0,0 +1,3 @@ +{ + "main": "main.js" +} diff --git a/packages/node-resolve/test/fixtures/custom-module-path/node_modules/package-with-dependency/package.json b/packages/node-resolve/test/fixtures/custom-module-path/node_modules/package-with-dependency/package.json new file mode 100644 index 000000000..daef9fcad --- /dev/null +++ b/packages/node-resolve/test/fixtures/custom-module-path/node_modules/package-with-dependency/package.json @@ -0,0 +1,3 @@ +{ + "main": "main.js" +} diff --git a/packages/node-resolve/test/test.js b/packages/node-resolve/test/test.js index 5ab2843d5..a9faff3c7 100755 --- a/packages/node-resolve/test/test.js +++ b/packages/node-resolve/test/test.js @@ -8,7 +8,7 @@ import { rollup } from 'rollup'; import { nodeResolve } from '..'; -import { getCode, getImports, testBundle } from '../../../util/test'; +import { evaluateBundle, getCode, getImports, testBundle } from '../../../util/test'; process.chdir(join(__dirname, 'fixtures')); @@ -273,6 +273,33 @@ test('allows custom moduleDirectories with legacy customResolveOptions.moduleDir t.snapshot(warnings); }); +test('moduleDirectories option rejects paths that contain a slash', async (t) => { + t.throws( + () => + nodeResolve({ + moduleDirectories: ['some/path'] + }), + { + message: /must only contain directory names/ + } + ); +}); + +test('allows custom modulePaths', async (t) => { + const bundle = await rollup({ + input: 'custom-module-path/main.js', + onwarn: failOnWarn(t), + plugins: [ + nodeResolve({ + modulePaths: [join(process.cwd(), 'custom-module-path/node_modules')] + }) + ] + }); + + const { dependency } = await evaluateBundle(bundle); + t.is(dependency, 'DEPENDENCY'); +}); + test('ignores deep-import non-modules', async (t) => { const warnings = []; const bundle = await rollup({ diff --git a/packages/node-resolve/types/index.d.ts b/packages/node-resolve/types/index.d.ts index 1b2228a37..11d5bfc18 100755 --- a/packages/node-resolve/types/index.d.ts +++ b/packages/node-resolve/types/index.d.ts @@ -31,11 +31,18 @@ export interface RollupNodeResolveOptions { browser?: boolean; /** - * One or more directories in which to recursively look for modules. + * A list of directory names in which to recursively look for modules. * @default ['node_modules'] */ moduleDirectories?: string[]; + /** + * A list of absolute paths to additional locations to search for modules. + * This is analogous to setting the `NODE_PATH` environment variable for node. + * @default [] + */ + modulePaths?: string[]; + /** * An `Array` of modules names, which instructs the plugin to force resolving for the * specified modules to the root `node_modules`. Helps to prevent bundling the same