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