diff --git a/packages/node-resolve/README.md b/packages/node-resolve/README.md
index 9f093239e..0f49f6d70 100755
--- a/packages/node-resolve/README.md
+++ b/packages/node-resolve/README.md
@@ -34,9 +34,9 @@ export default {
input: 'src/index.js',
output: {
dir: 'output',
- format: 'cjs'
+ format: 'cjs',
},
- plugins: [nodeResolve()]
+ plugins: [nodeResolve()],
};
```
@@ -44,6 +44,17 @@ Then call `rollup` either via the [CLI](https://www.rollupjs.org/guide/en/#comma
## Options
+### `exportConditions`
+
+Type: `Array[...String]`
+Default: `[]`
+
+Additional conditions of the package.json exports field to match when resolving modules. By default, this plugin looks for the `['default', 'module', 'import']` conditions when resolving imports.
+
+When using `@rollup/plugin-commonjs` v16 or higher, this plugin will use the `['default', 'module', 'require']` conditions when resolving require statements.
+
+Setting this option will add extra conditions on top of the default conditions. See https://nodejs.org/api/packages.html#packages_conditional_exports for more information.
+
### `browser`
Type: `Boolean`
@@ -164,9 +175,9 @@ export default {
output: {
file: 'bundle.js',
format: 'iife',
- name: 'MyModule'
+ name: 'MyModule',
},
- plugins: [resolve(), commonjs()]
+ plugins: [resolve(), commonjs()],
};
```
@@ -187,6 +198,19 @@ export default ({
})
```
+## 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.
+
+The node resolve plugin uses `import` by default, you can opt into using the `require` semantics by passing an extra option to the resolve function:
+
+```js
+this.resolve(importee, importer, {
+ skipSelf: true,
+ custom: { 'node-resolve': { isRequire: true } },
+});
+```
+
## Meta
[CONTRIBUTING](/.github/CONTRIBUTING.md)
diff --git a/packages/node-resolve/package.json b/packages/node-resolve/package.json
index 4f64b1e58..f4b67bff1 100644
--- a/packages/node-resolve/package.json
+++ b/packages/node-resolve/package.json
@@ -65,7 +65,7 @@
"@babel/core": "^7.10.5",
"@babel/plugin-transform-typescript": "^7.10.5",
"@rollup/plugin-babel": "^5.1.0",
- "@rollup/plugin-commonjs": "^14.0.0",
+ "@rollup/plugin-commonjs": "^16.0.0",
"@rollup/plugin-json": "^4.1.0",
"es5-ext": "^0.10.53",
"rollup": "^2.23.0",
diff --git a/packages/node-resolve/src/index.js b/packages/node-resolve/src/index.js
index 6081a0797..1d5d67fb9 100644
--- a/packages/node-resolve/src/index.js
+++ b/packages/node-resolve/src/index.js
@@ -7,13 +7,8 @@ import isModule from 'is-module';
import { isDirCached, isFileCached, readCachedFile } from './cache';
import { exists, readFile, realpath } from './fs';
-import {
- getMainFields,
- getPackageInfo,
- getPackageName,
- normalizeInput,
- resolveImportSpecifiers
-} from './util';
+import { resolveImportSpecifiers } from './resolveImportSpecifiers';
+import { getMainFields, getPackageInfo, getPackageName, normalizeInput } from './util';
const builtins = new Set(builtinList);
const ES6_BROWSER_EMPTY = '\0node-resolve:empty.js';
@@ -29,6 +24,10 @@ const deepFreeze = (object) => {
return object;
};
+
+const baseConditions = ['default', 'module'];
+const baseConditionsEsm = [...baseConditions, 'import'];
+const baseConditionsCjs = [...baseConditions, 'require'];
const defaults = {
customResolveOptions: {},
dedupe: [],
@@ -42,6 +41,8 @@ export const DEFAULTS = deepFreeze(deepMerge({}, defaults));
export function nodeResolve(opts = {}) {
const options = Object.assign({}, defaults, opts);
const { customResolveOptions, extensions, jail } = options;
+ const conditionsEsm = [...baseConditionsEsm, ...(options.exportConditions || [])];
+ const conditionsCjs = [...baseConditionsCjs, ...(options.exportConditions || [])];
const warnings = [];
const packageInfoCache = new Map();
const idToPackageInfo = new Map();
@@ -93,7 +94,7 @@ export function nodeResolve(opts = {}) {
isDirCached.clear();
},
- async resolveId(importee, importer) {
+ async resolveId(importee, importer, opts) {
if (importee === ES6_BROWSER_EMPTY) {
return importee;
}
@@ -222,7 +223,16 @@ export function nodeResolve(opts = {}) {
importSpecifierList.push(importee);
resolveOptions = Object.assign(resolveOptions, customResolveOptions);
- let resolved = await resolveImportSpecifiers(importSpecifierList, resolveOptions);
+ const warn = (...args) => this.warn(...args);
+ const isRequire =
+ opts && opts.custom && opts.custom['node-resolve'] && opts.custom['node-resolve'].isRequire;
+ const exportConditions = isRequire ? conditionsCjs : conditionsEsm;
+ let resolved = await resolveImportSpecifiers(
+ importSpecifierList,
+ resolveOptions,
+ exportConditions,
+ warn
+ );
if (!resolved) {
return null;
}
diff --git a/packages/node-resolve/src/resolveImportSpecifiers.js b/packages/node-resolve/src/resolveImportSpecifiers.js
new file mode 100644
index 000000000..5f780967e
--- /dev/null
+++ b/packages/node-resolve/src/resolveImportSpecifiers.js
@@ -0,0 +1,197 @@
+import fs from 'fs';
+import path from 'path';
+import { promisify } from 'util';
+
+import resolve from 'resolve';
+
+import { getPackageName } from './util';
+import { exists, realpath } from './fs';
+
+const resolveImportPath = promisify(resolve);
+const readFile = promisify(fs.readFile);
+
+const pathNotFoundError = (subPath, pkgPath) =>
+ new Error(`Package subpath '${subPath}' is not defined by "exports" in ${pkgPath}`);
+
+function findExportKeyMatch(exportMap, subPath) {
+ for (const key of Object.keys(exportMap)) {
+ if (key.endsWith('*')) {
+ // star match: "./foo/*": "./foo/*.js"
+ const keyWithoutStar = key.substring(0, key.length - 1);
+ if (subPath.startsWith(keyWithoutStar)) {
+ return key;
+ }
+ }
+
+ if (key.endsWith('/') && subPath.startsWith(key)) {
+ // directory match (deprecated by node): "./foo/": "./foo/.js"
+ return key;
+ }
+
+ if (key === subPath) {
+ // literal match
+ return key;
+ }
+ }
+ return null;
+}
+
+function mapSubPath(pkgJsonPath, subPath, key, value) {
+ if (typeof value === 'string') {
+ if (typeof key === 'string' && key.endsWith('*')) {
+ // star match: "./foo/*": "./foo/*.js"
+ const keyWithoutStar = key.substring(0, key.length - 1);
+ const subPathAfterKey = subPath.substring(keyWithoutStar.length);
+ return value.replace(/\*/g, subPathAfterKey);
+ }
+
+ if (value.endsWith('/')) {
+ // directory match (deprecated by node): "./foo/": "./foo/.js"
+ return `${value}${subPath.substring(key.length)}`;
+ }
+
+ // mapping is a string, for example { "./foo": "./dist/foo.js" }
+ return value;
+ }
+
+ if (Array.isArray(value)) {
+ // mapping is an array with fallbacks, for example { "./foo": ["foo:bar", "./dist/foo.js"] }
+ return value.find((v) => v.startsWith('./'));
+ }
+
+ throw pathNotFoundError(subPath, pkgJsonPath);
+}
+
+function findEntrypoint(pkgJsonPath, subPath, exportMap, conditions, key) {
+ if (typeof exportMap !== 'object') {
+ return mapSubPath(pkgJsonPath, subPath, key, exportMap);
+ }
+
+ // iterate conditions recursively, find the first that matches all conditions
+ for (const [condition, subExportMap] of Object.entries(exportMap)) {
+ if (conditions.includes(condition)) {
+ const mappedSubPath = findEntrypoint(pkgJsonPath, subPath, subExportMap, conditions, key);
+ if (mappedSubPath) {
+ return mappedSubPath;
+ }
+ }
+ }
+ throw pathNotFoundError(subPath, pkgJsonPath);
+}
+
+export function findEntrypointTopLevel(pkgJsonPath, subPath, exportMap, conditions) {
+ if (typeof exportMap !== 'object') {
+ // the export map shorthand, for example { exports: "./index.js" }
+ if (subPath !== '.') {
+ // shorthand only supports a main entrypoint
+ throw pathNotFoundError(subPath, pkgJsonPath);
+ }
+ return mapSubPath(pkgJsonPath, subPath, null, exportMap);
+ }
+
+ // export map is an object, the top level can be either conditions or sub path mappings
+ const keys = Object.keys(exportMap);
+ const isConditions = keys.every((k) => !k.startsWith('.'));
+ const isMappings = keys.every((k) => k.startsWith('.'));
+
+ if (!isConditions && !isMappings) {
+ throw new Error(
+ `Invalid package config ${pkgJsonPath}, "exports" cannot contain some keys starting with '.'` +
+ ' and some not. The exports object must either be an object of package subpath keys or an object of main entry' +
+ ' condition name keys only.'
+ );
+ }
+
+ let key = null;
+ let exportMapForSubPath;
+
+ if (isConditions) {
+ // top level is conditions, for example { "import": ..., "require": ..., "module": ... }
+ if (subPath !== '.') {
+ // package with top level conditions means it only supports a main entrypoint
+ throw pathNotFoundError(subPath, pkgJsonPath);
+ }
+ exportMapForSubPath = exportMap;
+ } else {
+ // top level is sub path mappings, for example { ".": ..., "./foo": ..., "./bar": ... }
+ key = findExportKeyMatch(exportMap, subPath);
+ if (!key) {
+ throw pathNotFoundError(subPath, pkgJsonPath);
+ }
+ exportMapForSubPath = exportMap[key];
+ }
+
+ return findEntrypoint(pkgJsonPath, subPath, exportMapForSubPath, conditions, key);
+}
+
+async function resolveId(importPath, options, exportConditions, warn) {
+ const pkgName = getPackageName(importPath);
+ if (pkgName) {
+ let pkgJsonPath;
+ let pkgJson;
+ try {
+ pkgJsonPath = await resolveImportPath(`${pkgName}/package.json`, options);
+ pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf-8'));
+ } catch (_) {
+ // if there is no package.json we defer to regular resolve behavior
+ }
+
+ if (pkgJsonPath && pkgJson && pkgJson.exports) {
+ try {
+ const packageSubPath =
+ pkgName === importPath ? '.' : `.${importPath.substring(pkgName.length)}`;
+ const mappedSubPath = findEntrypointTopLevel(
+ pkgJsonPath,
+ packageSubPath,
+ pkgJson.exports,
+ exportConditions
+ );
+ const pkgDir = path.dirname(pkgJsonPath);
+ return path.join(pkgDir, mappedSubPath);
+ } catch (error) {
+ warn(error);
+ return null;
+ }
+ }
+ }
+
+ return resolveImportPath(importPath, options);
+}
+
+// Resolve module specifiers in order. Promise resolves to the first module that resolves
+// successfully, or the error that resulted from the last attempted module resolution.
+export function resolveImportSpecifiers(
+ importSpecifierList,
+ resolveOptions,
+ exportConditions,
+ warn
+) {
+ let promise = Promise.resolve();
+
+ for (let i = 0; i < importSpecifierList.length; i++) {
+ // eslint-disable-next-line no-loop-func
+ promise = promise.then(async (value) => {
+ // if we've already resolved to something, just return it.
+ if (value) {
+ return value;
+ }
+
+ let result = await resolveId(importSpecifierList[i], resolveOptions, exportConditions, warn);
+ if (!resolveOptions.preserveSymlinks) {
+ if (await exists(result)) {
+ result = await realpath(result);
+ }
+ }
+ return result;
+ });
+
+ // swallow MODULE_NOT_FOUND errors
+ promise = promise.catch((error) => {
+ if (error.code !== 'MODULE_NOT_FOUND') {
+ throw error;
+ }
+ });
+ }
+
+ return promise;
+}
diff --git a/packages/node-resolve/src/util.js b/packages/node-resolve/src/util.js
index 1c2628afd..1af107a8d 100644
--- a/packages/node-resolve/src/util.js
+++ b/packages/node-resolve/src/util.js
@@ -1,13 +1,8 @@
import { dirname, extname, resolve } from 'path';
-import { promisify } from 'util';
import { createFilter } from '@rollup/pluginutils';
-import resolveModule from 'resolve';
-
-import { exists, realpath, realpathSync } from './fs';
-
-const resolveId = promisify(resolveModule);
+import { realpathSync } from './fs';
// returns the imported package name for bare module imports
export function getPackageName(id) {
@@ -158,36 +153,3 @@ export function normalizeInput(input) {
// otherwise it's a string
return [input];
}
-
-// Resolve module specifiers in order. Promise resolves to the first module that resolves
-// successfully, or the error that resulted from the last attempted module resolution.
-export function resolveImportSpecifiers(importSpecifierList, resolveOptions) {
- let promise = Promise.resolve();
-
- for (let i = 0; i < importSpecifierList.length; i++) {
- // eslint-disable-next-line no-loop-func
- promise = promise.then(async (value) => {
- // if we've already resolved to something, just return it.
- if (value) {
- return value;
- }
-
- let result = await resolveId(importSpecifierList[i], resolveOptions);
- if (!resolveOptions.preserveSymlinks) {
- if (await exists(result)) {
- result = await realpath(result);
- }
- }
- return result;
- });
-
- // swallow MODULE_NOT_FOUND errors
- promise = promise.catch((error) => {
- if (error.code !== 'MODULE_NOT_FOUND') {
- throw error;
- }
- });
- }
-
- return promise;
-}
diff --git a/packages/node-resolve/test/browser.js b/packages/node-resolve/test/browser.js
index 69a63dfe7..3719223ed 100644
--- a/packages/node-resolve/test/browser.js
+++ b/packages/node-resolve/test/browser.js
@@ -190,7 +190,7 @@ test('supports `false` in browser field', async (t) => {
await testBundle(t, bundle);
});
-test('pkg.browser with mapping to prevent bundle by specifying a value of false', async (t) => {
+test.only('pkg.browser with mapping to prevent bundle by specifying a value of false', async (t) => {
const bundle = await rollup({
input: 'browser-object-with-false.js',
plugins: [nodeResolve({ browser: true }), commonjs()]
diff --git a/packages/node-resolve/test/fixtures/browser-object-with-false.js b/packages/node-resolve/test/fixtures/browser-object-with-false.js
index 938b3416c..9183eff46 100755
--- a/packages/node-resolve/test/fixtures/browser-object-with-false.js
+++ b/packages/node-resolve/test/fixtures/browser-object-with-false.js
@@ -12,7 +12,7 @@ const clientHttp = new Client('http:');
t.is(clientWs.name, 'websocket-tracker');
t.is(clientHttp.name, 'NULL');
t.is(HTTPTracker, ES6_BROWSER_EMPTY);
-t.is(HTTPTrackerWithSubPath, ES6_BROWSER_EMPTY);
+t.deepEqual(HTTPTrackerWithSubPath, { default: {} });
// expose
export default 'ok';
diff --git a/packages/node-resolve/test/fixtures/exports-cjs.js b/packages/node-resolve/test/fixtures/exports-cjs.js
new file mode 100644
index 000000000..9e18e0188
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/exports-cjs.js
@@ -0,0 +1,3 @@
+const main = require('exports-cjs');
+
+module.exports = main;
diff --git a/packages/node-resolve/test/fixtures/exports-conditions-fallback.js b/packages/node-resolve/test/fixtures/exports-conditions-fallback.js
new file mode 100644
index 000000000..dcc23b1d5
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/exports-conditions-fallback.js
@@ -0,0 +1,3 @@
+import main from 'exports-conditions-fallback';
+
+export default main;
diff --git a/packages/node-resolve/test/fixtures/exports-directory.js b/packages/node-resolve/test/fixtures/exports-directory.js
new file mode 100644
index 000000000..3d4c351f3
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/exports-directory.js
@@ -0,0 +1,5 @@
+import a from 'exports-directory/foo/a.js';
+import b from 'exports-directory/foo/b.js';
+import c from 'exports-directory/foo/nested/c.js';
+
+export default { a, b, c };
diff --git a/packages/node-resolve/test/fixtures/exports-main-directory.js b/packages/node-resolve/test/fixtures/exports-main-directory.js
new file mode 100644
index 000000000..b1ff09727
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/exports-main-directory.js
@@ -0,0 +1,5 @@
+import a from 'exports-main-directory/a.js';
+import b from 'exports-main-directory/foo/b.js';
+import c from 'exports-main-directory/foo/nested/c.js';
+
+export default { a, b, c };
diff --git a/packages/node-resolve/test/fixtures/exports-mappings-and-conditions.js b/packages/node-resolve/test/fixtures/exports-mappings-and-conditions.js
new file mode 100644
index 000000000..8e67bda41
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/exports-mappings-and-conditions.js
@@ -0,0 +1,5 @@
+import main from 'exports-mappings-and-conditions';
+import foo from 'exports-mappings-and-conditions/foo';
+import bar from 'exports-mappings-and-conditions/bar';
+
+export default { main, foo, bar };
diff --git a/packages/node-resolve/test/fixtures/exports-nested-conditions.js b/packages/node-resolve/test/fixtures/exports-nested-conditions.js
new file mode 100644
index 000000000..b1a30f4fc
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/exports-nested-conditions.js
@@ -0,0 +1,3 @@
+import main from 'exports-nested-conditions';
+
+export default main;
diff --git a/packages/node-resolve/test/fixtures/exports-non-existing-subpath.js b/packages/node-resolve/test/fixtures/exports-non-existing-subpath.js
new file mode 100644
index 000000000..0a4fb26a4
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/exports-non-existing-subpath.js
@@ -0,0 +1,3 @@
+import bar from 'exports-non-existing-subpath/bar';
+
+export default bar;
diff --git a/packages/node-resolve/test/fixtures/exports-prevent-unspecified-subpath.js b/packages/node-resolve/test/fixtures/exports-prevent-unspecified-subpath.js
new file mode 100644
index 000000000..39e34e0e7
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/exports-prevent-unspecified-subpath.js
@@ -0,0 +1,3 @@
+import bar from 'exports-top-level-mappings/bar';
+
+export default bar;
diff --git a/packages/node-resolve/test/fixtures/exports-shorthand-fallback.js b/packages/node-resolve/test/fixtures/exports-shorthand-fallback.js
new file mode 100644
index 000000000..7405c6992
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/exports-shorthand-fallback.js
@@ -0,0 +1,3 @@
+import exportsMapEntry from 'exports-shorthand';
+
+export default exportsMapEntry;
diff --git a/packages/node-resolve/test/fixtures/exports-shorthand-subpath.js b/packages/node-resolve/test/fixtures/exports-shorthand-subpath.js
new file mode 100644
index 000000000..7273f5f73
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/exports-shorthand-subpath.js
@@ -0,0 +1,3 @@
+import exportsMapEntry from 'exports-shorthand/foo';
+
+export default exportsMapEntry;
diff --git a/packages/node-resolve/test/fixtures/exports-shorthand.js b/packages/node-resolve/test/fixtures/exports-shorthand.js
new file mode 100644
index 000000000..7405c6992
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/exports-shorthand.js
@@ -0,0 +1,3 @@
+import exportsMapEntry from 'exports-shorthand';
+
+export default exportsMapEntry;
diff --git a/packages/node-resolve/test/fixtures/exports-star.js b/packages/node-resolve/test/fixtures/exports-star.js
new file mode 100644
index 000000000..f71eeee4a
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/exports-star.js
@@ -0,0 +1,5 @@
+import a from 'exports-star/foo/a';
+import b from 'exports-star/foo/b';
+import c from 'exports-star/foo/bar/c';
+
+export default { a, b, c };
diff --git a/packages/node-resolve/test/fixtures/exports-top-level-conditions.js b/packages/node-resolve/test/fixtures/exports-top-level-conditions.js
new file mode 100644
index 000000000..1b2d8d621
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/exports-top-level-conditions.js
@@ -0,0 +1,3 @@
+import main from 'exports-top-level-conditions';
+
+export default main;
diff --git a/packages/node-resolve/test/fixtures/exports-top-level-mappings.js b/packages/node-resolve/test/fixtures/exports-top-level-mappings.js
new file mode 100644
index 000000000..63d50f1e4
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/exports-top-level-mappings.js
@@ -0,0 +1,4 @@
+import main from 'exports-top-level-mappings';
+import foo from 'exports-top-level-mappings/foo';
+
+export default { main, foo };
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-cjs/index-cjs.js b/packages/node-resolve/test/fixtures/node_modules/exports-cjs/index-cjs.js
new file mode 100644
index 000000000..1f9d31a5e
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-cjs/index-cjs.js
@@ -0,0 +1 @@
+module.exports = 'CJS';
\ No newline at end of file
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-cjs/index-esm.js b/packages/node-resolve/test/fixtures/node_modules/exports-cjs/index-esm.js
new file mode 100644
index 000000000..f6e979ebf
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-cjs/index-esm.js
@@ -0,0 +1 @@
+module.exports = 'ESM';
\ No newline at end of file
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-cjs/package.json b/packages/node-resolve/test/fixtures/node_modules/exports-cjs/package.json
new file mode 100644
index 000000000..65cee8751
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-cjs/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "exports-top-level-mappings",
+ "main": "index.js",
+ "exports": {
+ ".": {
+ "require": "./index-cjs.js",
+ "import": "./index-esm.js"
+ }
+ }
+}
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-conditions-fallback/index-mapped.js b/packages/node-resolve/test/fixtures/node_modules/exports-conditions-fallback/index-mapped.js
new file mode 100644
index 000000000..dc5589590
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-conditions-fallback/index-mapped.js
@@ -0,0 +1 @@
+export default 'MAIN MAPPED';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-conditions-fallback/index.js b/packages/node-resolve/test/fixtures/node_modules/exports-conditions-fallback/index.js
new file mode 100644
index 000000000..aaf1b3fe1
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-conditions-fallback/index.js
@@ -0,0 +1 @@
+export default 'MAIN';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-conditions-fallback/package.json b/packages/node-resolve/test/fixtures/node_modules/exports-conditions-fallback/package.json
new file mode 100644
index 000000000..aff5ec2a8
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-conditions-fallback/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "exports-conditions-fallback",
+ "main": "index.js",
+ "exports": {
+ "node": {
+ "require": "./index.js"
+ },
+ "default": "./index-mapped.js"
+ }
+}
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-directory/exported-foo/a.js b/packages/node-resolve/test/fixtures/node_modules/exports-directory/exported-foo/a.js
new file mode 100644
index 000000000..b7119a4f2
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-directory/exported-foo/a.js
@@ -0,0 +1 @@
+export default 'exported-foo a';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-directory/exported-foo/b.js b/packages/node-resolve/test/fixtures/node_modules/exports-directory/exported-foo/b.js
new file mode 100644
index 000000000..6d0e833a7
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-directory/exported-foo/b.js
@@ -0,0 +1 @@
+export default 'exported-foo b';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-directory/exported-foo/nested/c.js b/packages/node-resolve/test/fixtures/node_modules/exports-directory/exported-foo/nested/c.js
new file mode 100644
index 000000000..27265328a
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-directory/exported-foo/nested/c.js
@@ -0,0 +1 @@
+export default 'exported-foo c';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-directory/foo/a.js b/packages/node-resolve/test/fixtures/node_modules/exports-directory/foo/a.js
new file mode 100644
index 000000000..f3e609902
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-directory/foo/a.js
@@ -0,0 +1 @@
+export default 'foo a';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-directory/foo/b.js b/packages/node-resolve/test/fixtures/node_modules/exports-directory/foo/b.js
new file mode 100644
index 000000000..798313b69
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-directory/foo/b.js
@@ -0,0 +1 @@
+export default 'foo b';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-directory/foo/nested/c.js b/packages/node-resolve/test/fixtures/node_modules/exports-directory/foo/nested/c.js
new file mode 100644
index 000000000..041c1a3b9
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-directory/foo/nested/c.js
@@ -0,0 +1 @@
+export default 'foo c';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-directory/package.json b/packages/node-resolve/test/fixtures/node_modules/exports-directory/package.json
new file mode 100644
index 000000000..c68136251
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-directory/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "exports-directory",
+ "main": "index.js",
+ "exports": {
+ "./foo/": "./exported-foo/"
+ }
+}
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-main-directory/a.js b/packages/node-resolve/test/fixtures/node_modules/exports-main-directory/a.js
new file mode 100644
index 000000000..bcc62b225
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-main-directory/a.js
@@ -0,0 +1 @@
+export default 'exported a';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-main-directory/foo/b.js b/packages/node-resolve/test/fixtures/node_modules/exports-main-directory/foo/b.js
new file mode 100644
index 000000000..f9c84f907
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-main-directory/foo/b.js
@@ -0,0 +1 @@
+export default 'exported b';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-main-directory/foo/nested/c.js b/packages/node-resolve/test/fixtures/node_modules/exports-main-directory/foo/nested/c.js
new file mode 100644
index 000000000..b2e193530
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-main-directory/foo/nested/c.js
@@ -0,0 +1 @@
+export default 'exported c';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-main-directory/package.json b/packages/node-resolve/test/fixtures/node_modules/exports-main-directory/package.json
new file mode 100644
index 000000000..f46729a48
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-main-directory/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "exports-main-directory",
+ "main": "index.js",
+ "exports": {
+ "./": "./"
+ }
+}
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/bar-mapped.js b/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/bar-mapped.js
new file mode 100644
index 000000000..592f4b05b
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/bar-mapped.js
@@ -0,0 +1 @@
+export default 'BAR MAPPED';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/bar.js b/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/bar.js
new file mode 100644
index 000000000..d742342f6
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/bar.js
@@ -0,0 +1 @@
+export default 'BAR';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/foo-mapped.js b/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/foo-mapped.js
new file mode 100644
index 000000000..8b87f90ae
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/foo-mapped.js
@@ -0,0 +1 @@
+export default 'FOO MAPPED';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/foo.js b/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/foo.js
new file mode 100644
index 000000000..20fc97bf8
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/foo.js
@@ -0,0 +1 @@
+export default 'FOO';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/index-mapped.js b/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/index-mapped.js
new file mode 100644
index 000000000..dc5589590
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/index-mapped.js
@@ -0,0 +1 @@
+export default 'MAIN MAPPED';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/index.js b/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/index.js
new file mode 100644
index 000000000..aaf1b3fe1
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/index.js
@@ -0,0 +1 @@
+export default 'MAIN';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/package.json b/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/package.json
new file mode 100644
index 000000000..5a2e55562
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "exports-mappings-and-conditions",
+ "main": "index.js",
+ "exports": {
+ ".": {
+ "require": "./index.js'",
+ "module": "./index-mapped.js"
+ },
+ "./foo": {
+ "node": {
+ "require": "./foo.js",
+ "default": "./foo.js"
+ },
+ "module": {
+ "require": "./foo.js",
+ "default": "./foo-mapped.js"
+ }
+ },
+ "./bar": "./bar-mapped.js"
+ }
+}
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-nested-conditions/index-mapped.js b/packages/node-resolve/test/fixtures/node_modules/exports-nested-conditions/index-mapped.js
new file mode 100644
index 000000000..dc5589590
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-nested-conditions/index-mapped.js
@@ -0,0 +1 @@
+export default 'MAIN MAPPED';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-nested-conditions/index.js b/packages/node-resolve/test/fixtures/node_modules/exports-nested-conditions/index.js
new file mode 100644
index 000000000..aaf1b3fe1
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-nested-conditions/index.js
@@ -0,0 +1 @@
+export default 'MAIN';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-nested-conditions/package.json b/packages/node-resolve/test/fixtures/node_modules/exports-nested-conditions/package.json
new file mode 100644
index 000000000..729dae830
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-nested-conditions/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "exports-nested-conditions",
+ "main": "index.js",
+ "exports": {
+ "module": {
+ "node": "./index.js",
+ "browser": "./index.js",
+ "default": "./index-mapped.js"
+ }
+ }
+}
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/foo-mapped.js b/packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/foo-mapped.js
new file mode 100644
index 000000000..8b87f90ae
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/foo-mapped.js
@@ -0,0 +1 @@
+export default 'FOO MAPPED';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/foo.js b/packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/foo.js
new file mode 100644
index 000000000..20fc97bf8
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/foo.js
@@ -0,0 +1 @@
+export default 'FOO';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/index-mapped.js b/packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/index-mapped.js
new file mode 100644
index 000000000..dc5589590
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/index-mapped.js
@@ -0,0 +1 @@
+export default 'MAIN MAPPED';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/index.js b/packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/index.js
new file mode 100644
index 000000000..aaf1b3fe1
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/index.js
@@ -0,0 +1 @@
+export default 'MAIN';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/package.json b/packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/package.json
new file mode 100644
index 000000000..0f7855cd6
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "exports-non-existing-subpath",
+ "main": "index.js",
+ "exports": {
+ ".": "./index-mapped.js",
+ "./foo": "./foo-mapped.js"
+ }
+}
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/bar.js b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/bar.js
new file mode 100644
index 000000000..d742342f6
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/bar.js
@@ -0,0 +1 @@
+export default 'BAR';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/foo.js b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/foo.js
new file mode 100644
index 000000000..20fc97bf8
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/foo.js
@@ -0,0 +1 @@
+export default 'FOO';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/index-mapped.js b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/index-mapped.js
new file mode 100644
index 000000000..dc5589590
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/index-mapped.js
@@ -0,0 +1 @@
+export default 'MAIN MAPPED';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/index.js b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/index.js
new file mode 100644
index 000000000..aaf1b3fe1
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/index.js
@@ -0,0 +1 @@
+export default 'MAIN';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/package.json b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/package.json
new file mode 100644
index 000000000..23db68cb9
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "exports-prevent-direct-imports",
+ "main": "index.js",
+ "exports": {
+ ".": "./index.js",
+ "./foo.js": "./foo.js"
+ }
+}
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/foo-mapped.js b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/foo-mapped.js
new file mode 100644
index 000000000..8b87f90ae
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/foo-mapped.js
@@ -0,0 +1 @@
+export default 'FOO MAPPED';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/foo.js b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/foo.js
new file mode 100644
index 000000000..20fc97bf8
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/foo.js
@@ -0,0 +1 @@
+export default 'FOO';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/index-mapped.js b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/index-mapped.js
new file mode 100644
index 000000000..dc5589590
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/index-mapped.js
@@ -0,0 +1 @@
+export default 'MAIN MAPPED';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/index.js b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/index.js
new file mode 100644
index 000000000..aaf1b3fe1
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/index.js
@@ -0,0 +1 @@
+export default 'MAIN';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/package.json b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/package.json
new file mode 100644
index 000000000..2862a5771
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "exports-prevent-unspecified-subpath",
+ "main": "index.js",
+ "exports": {
+ ".": "./index-mapped.js",
+ "./foo": "./foo-mapped.js"
+ }
+}
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-shorthand-fallback/index-mapped.js b/packages/node-resolve/test/fixtures/node_modules/exports-shorthand-fallback/index-mapped.js
new file mode 100644
index 000000000..dc5589590
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-shorthand-fallback/index-mapped.js
@@ -0,0 +1 @@
+export default 'MAIN MAPPED';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-shorthand-fallback/index.js b/packages/node-resolve/test/fixtures/node_modules/exports-shorthand-fallback/index.js
new file mode 100644
index 000000000..aaf1b3fe1
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-shorthand-fallback/index.js
@@ -0,0 +1 @@
+export default 'MAIN';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-shorthand-fallback/package.json b/packages/node-resolve/test/fixtures/node_modules/exports-shorthand-fallback/package.json
new file mode 100644
index 000000000..de989359d
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-shorthand-fallback/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "exports-shorthand-fallback",
+ "main": "index.js",
+ "exports": ["./index-mapped.js", "./not-index-mapped.js"]
+}
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-shorthand/index-mapped.js b/packages/node-resolve/test/fixtures/node_modules/exports-shorthand/index-mapped.js
new file mode 100644
index 000000000..dc5589590
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-shorthand/index-mapped.js
@@ -0,0 +1 @@
+export default 'MAIN MAPPED';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-shorthand/index.js b/packages/node-resolve/test/fixtures/node_modules/exports-shorthand/index.js
new file mode 100644
index 000000000..aaf1b3fe1
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-shorthand/index.js
@@ -0,0 +1 @@
+export default 'MAIN';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-shorthand/package.json b/packages/node-resolve/test/fixtures/node_modules/exports-shorthand/package.json
new file mode 100644
index 000000000..97f25f41e
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-shorthand/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "exports-shorthand",
+ "main": "index.js",
+ "exports": "./index-mapped.js"
+}
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-star/foo/a.js b/packages/node-resolve/test/fixtures/node_modules/exports-star/foo/a.js
new file mode 100644
index 000000000..5a373d2fa
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-star/foo/a.js
@@ -0,0 +1 @@
+export default 'A';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-star/foo/b.js b/packages/node-resolve/test/fixtures/node_modules/exports-star/foo/b.js
new file mode 100644
index 000000000..43431cbc9
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-star/foo/b.js
@@ -0,0 +1 @@
+export default 'B';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-star/foo/bar/c.js b/packages/node-resolve/test/fixtures/node_modules/exports-star/foo/bar/c.js
new file mode 100644
index 000000000..d152c0607
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-star/foo/bar/c.js
@@ -0,0 +1 @@
+export default 'C';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-star/package.json b/packages/node-resolve/test/fixtures/node_modules/exports-star/package.json
new file mode 100644
index 000000000..eabd64006
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-star/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "exports-star",
+ "main": "index.js",
+ "exports": {
+ "./foo/*": "./foo/*.js"
+ }
+}
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-top-level-conditions/index-mapped.js b/packages/node-resolve/test/fixtures/node_modules/exports-top-level-conditions/index-mapped.js
new file mode 100644
index 000000000..dc5589590
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-top-level-conditions/index-mapped.js
@@ -0,0 +1 @@
+export default 'MAIN MAPPED';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-top-level-conditions/index.js b/packages/node-resolve/test/fixtures/node_modules/exports-top-level-conditions/index.js
new file mode 100644
index 000000000..aaf1b3fe1
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-top-level-conditions/index.js
@@ -0,0 +1 @@
+export default 'MAIN';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-top-level-conditions/package.json b/packages/node-resolve/test/fixtures/node_modules/exports-top-level-conditions/package.json
new file mode 100644
index 000000000..c753831d2
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-top-level-conditions/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "exports-top-level-conditions",
+ "main": "index.js",
+ "exports": {
+ "require": "./index.js",
+ "module": "./index-mapped.js"
+ }
+}
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/foo-mapped.js b/packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/foo-mapped.js
new file mode 100644
index 000000000..8b87f90ae
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/foo-mapped.js
@@ -0,0 +1 @@
+export default 'FOO MAPPED';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/foo.js b/packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/foo.js
new file mode 100644
index 000000000..20fc97bf8
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/foo.js
@@ -0,0 +1 @@
+export default 'FOO';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/index-mapped.js b/packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/index-mapped.js
new file mode 100644
index 000000000..dc5589590
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/index-mapped.js
@@ -0,0 +1 @@
+export default 'MAIN MAPPED';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/index.js b/packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/index.js
new file mode 100644
index 000000000..aaf1b3fe1
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/index.js
@@ -0,0 +1 @@
+export default 'MAIN';
diff --git a/packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/package.json b/packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/package.json
new file mode 100644
index 000000000..deffe5419
--- /dev/null
+++ b/packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "exports-top-level-mappings",
+ "main": "index.js",
+ "exports": {
+ ".": "./index-mapped.js",
+ "./foo": "./foo-mapped.js"
+ }
+}
diff --git a/packages/node-resolve/test/package-entry-points.js b/packages/node-resolve/test/package-entry-points.js
new file mode 100644
index 000000000..9311ecae4
--- /dev/null
+++ b/packages/node-resolve/test/package-entry-points.js
@@ -0,0 +1,200 @@
+const { join } = require('path');
+
+const test = require('ava');
+const { rollup } = require('rollup');
+const commonjs = require('@rollup/plugin-commonjs');
+
+const { testBundle } = require('../../../util/test');
+
+const { nodeResolve } = require('..');
+
+process.chdir(join(__dirname, 'fixtures'));
+
+test('handles export map shorthand', async (t) => {
+ const bundle = await rollup({
+ input: 'exports-shorthand.js',
+ onwarn: () => {
+ t.fail('No warnings were expected');
+ },
+ plugins: [nodeResolve()]
+ });
+ const { module } = await testBundle(t, bundle);
+
+ t.is(module.exports, 'MAIN MAPPED');
+});
+
+test('handles export map with fallback', async (t) => {
+ const bundle = await rollup({
+ input: 'exports-shorthand-fallback.js',
+ onwarn: () => {
+ t.fail('No warnings were expected');
+ },
+ plugins: [nodeResolve()]
+ });
+ const { module } = await testBundle(t, bundle);
+
+ t.is(module.exports, 'MAIN MAPPED');
+});
+
+test('handles export map with top level mappings', async (t) => {
+ const bundle = await rollup({
+ input: 'exports-top-level-mappings.js',
+ onwarn: () => {
+ t.fail('No warnings were expected');
+ },
+ plugins: [nodeResolve()]
+ });
+ const { module } = await testBundle(t, bundle);
+
+ t.is(module.exports.main, 'MAIN MAPPED');
+ t.is(module.exports.foo, 'FOO MAPPED');
+});
+
+test('handles export map with top level conditions', async (t) => {
+ const bundle = await rollup({
+ input: 'exports-top-level-conditions.js',
+ onwarn: () => {
+ t.fail('No warnings were expected');
+ },
+ plugins: [nodeResolve()]
+ });
+ const { module } = await testBundle(t, bundle);
+
+ t.is(module.exports, 'MAIN MAPPED');
+});
+
+test('handles export map with nested conditions', async (t) => {
+ const bundle = await rollup({
+ input: 'exports-nested-conditions.js',
+ onwarn: () => {
+ t.fail('No warnings were expected');
+ },
+ plugins: [nodeResolve()]
+ });
+ const { module } = await testBundle(t, bundle);
+
+ t.is(module.exports, 'MAIN MAPPED');
+});
+
+test('handles conditions with a fallback', async (t) => {
+ const bundle = await rollup({
+ input: 'exports-conditions-fallback.js',
+ onwarn: () => {
+ t.fail('No warnings were expected');
+ },
+ plugins: [nodeResolve()]
+ });
+ const { module } = await testBundle(t, bundle);
+
+ t.is(module.exports, 'MAIN MAPPED');
+});
+
+test('handles top level mappings with conditions', async (t) => {
+ const bundle = await rollup({
+ input: 'exports-mappings-and-conditions.js',
+ onwarn: () => {
+ t.fail('No warnings were expected');
+ },
+ plugins: [nodeResolve()]
+ });
+ const { module } = await testBundle(t, bundle);
+
+ t.is(module.exports.main, 'MAIN MAPPED');
+ t.is(module.exports.foo, 'FOO MAPPED');
+ t.is(module.exports.bar, 'BAR MAPPED');
+});
+
+test('handles directory exports', async (t) => {
+ const bundle = await rollup({
+ input: 'exports-directory.js',
+ onwarn: () => {
+ t.fail('No warnings were expected');
+ },
+ plugins: [nodeResolve()]
+ });
+ const { module } = await testBundle(t, bundle);
+
+ t.is(module.exports.a, 'exported-foo a');
+ t.is(module.exports.b, 'exported-foo b');
+ t.is(module.exports.c, 'exported-foo c');
+});
+
+test('handles main directory exports', async (t) => {
+ const bundle = await rollup({
+ input: 'exports-main-directory.js',
+ onwarn: () => {
+ t.fail('No warnings were expected');
+ },
+ plugins: [nodeResolve()]
+ });
+ const { module } = await testBundle(t, bundle);
+
+ t.is(module.exports.a, 'exported a');
+ t.is(module.exports.b, 'exported b');
+ t.is(module.exports.c, 'exported c');
+});
+
+test('logs a warning when using shorthand and importing a subpath', async (t) => {
+ t.plan(1);
+ const errors = [];
+ await rollup({
+ input: 'exports-shorthand-subpath.js',
+ onwarn: (error) => {
+ errors.push(error);
+ },
+ plugins: [nodeResolve()]
+ });
+ t.true(errors[0].message.includes('Package subpath \'./foo\' is not defined by "exports" in'));
+});
+
+test('logs a warning when a subpath cannot be found', async (t) => {
+ t.plan(1);
+ const errors = [];
+ await rollup({
+ input: 'exports-non-existing-subpath.js',
+ onwarn: (error) => {
+ errors.push(error);
+ },
+ plugins: [nodeResolve()]
+ });
+ t.true(errors[0].message.includes('Package subpath \'./bar\' is not defined by "exports" in'));
+});
+
+test('prevents importing files not specified in exports map', async (t) => {
+ t.plan(1);
+ const errors = [];
+ await rollup({
+ input: 'exports-prevent-unspecified-subpath.js',
+ onwarn: (error) => {
+ errors.push(error);
+ },
+ plugins: [nodeResolve()]
+ });
+ t.true(errors[0].message.includes('Package subpath \'./bar\' is not defined by "exports" in'));
+});
+
+test('uses "require" condition when a module is referenced with require', async (t) => {
+ const bundle = await rollup({
+ input: 'exports-cjs.js',
+ onwarn: () => {
+ t.fail('No warnings were expected');
+ },
+ plugins: [commonjs(), nodeResolve()]
+ });
+ const { module } = await testBundle(t, bundle);
+
+ t.is(module.exports, 'CJS');
+});
+
+test('can use star pattern in exports field', async (t) => {
+ const bundle = await rollup({
+ input: 'exports-star.js',
+ onwarn: () => {
+ t.fail('No warnings were expected');
+ },
+ plugins: [nodeResolve()]
+ });
+ const { module } = await testBundle(t, bundle);
+
+ t.deepEqual(module.exports, { a: 'A', b: 'B', c: 'C' });
+});
diff --git a/packages/node-resolve/types/index.d.ts b/packages/node-resolve/types/index.d.ts
index d2fed0f77..f58031979 100755
--- a/packages/node-resolve/types/index.d.ts
+++ b/packages/node-resolve/types/index.d.ts
@@ -9,6 +9,17 @@ export const DEFAULTS: {
};
export interface RollupNodeResolveOptions {
+ /**
+ * Additional conditions of the package.json exports field to match when resolving modules.
+ * By default, this plugin looks for the `'default', 'module', 'import']` conditions when resolving imports.
+ *
+ * When using `@rollup/plugin-commonjs` v16 or higher, this plugin will use the
+ * `['default', 'module', 'import']` conditions when resolving require statements.
+ *
+ * Setting this option will add extra conditions on top of the default conditions.
+ * See https://nodejs.org/api/packages.html#packages_conditional_exports for more information.
+ */
+ exportConditions?: string[];
/**
* If `true`, instructs the plugin to use the `"browser"` property in `package.json`
* files to specify alternative files to load for bundling. This is useful when
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5a9d401be..f36a816e9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -328,7 +328,7 @@ importers:
'@babel/core': 7.12.3
'@babel/plugin-transform-typescript': 7.12.1_@babel+core@7.12.3
'@rollup/plugin-babel': 5.2.1_@babel+core@7.12.3+rollup@2.32.1
- '@rollup/plugin-commonjs': 14.0.0_rollup@2.32.1
+ '@rollup/plugin-commonjs': 16.0.0_rollup@2.32.1
'@rollup/plugin-json': 4.1.0_rollup@2.32.1
es5-ext: 0.10.53
rollup: 2.32.1
@@ -338,7 +338,7 @@ importers:
'@babel/core': ^7.10.5
'@babel/plugin-transform-typescript': ^7.10.5
'@rollup/plugin-babel': ^5.1.0
- '@rollup/plugin-commonjs': ^14.0.0
+ '@rollup/plugin-commonjs': ^16.0.0
'@rollup/plugin-json': ^4.1.0
'@rollup/pluginutils': ^3.1.0
'@types/resolve': 1.17.1
@@ -506,7 +506,7 @@ importers:
rollup: ^2.23.0
source-map-support: ^0.5.19
tosource: ^1.0.0
-lockfileVersion: 5.1
+lockfileVersion: 5.2
packages:
/@ava/babel/1.0.1:
dependencies:
@@ -1659,6 +1659,23 @@ packages:
rollup: ^2.3.4
resolution:
integrity: sha512-+PSmD9ePwTAeU106i9FRdc+Zb3XUWyW26mo5Atr2mk82hor8+nPwkztEjFo8/B1fJKfaQDg9aM2bzQkjhi7zOw==
+ /@rollup/plugin-commonjs/16.0.0_rollup@2.32.1:
+ dependencies:
+ '@rollup/pluginutils': 3.1.0_rollup@2.32.1
+ commondir: 1.0.1
+ estree-walker: 2.0.1
+ glob: 7.1.6
+ is-reference: 1.2.1
+ magic-string: 0.25.7
+ resolve: 1.18.1
+ rollup: 2.32.1
+ dev: true
+ engines:
+ node: '>= 8.0.0'
+ peerDependencies:
+ rollup: ^2.30.0
+ resolution:
+ integrity: sha512-LuNyypCP3msCGVQJ7ki8PqYdpjfEkE/xtFa5DqlF+7IBD0JsfMZ87C58heSwIMint58sAUZbt3ITqOmdQv/dXw==
/@rollup/plugin-json/4.1.0_rollup@2.32.1:
dependencies:
'@rollup/pluginutils': 3.1.0_rollup@2.32.1
@@ -3794,7 +3811,6 @@ packages:
resolution:
integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
/estree-walker/2.0.1:
- dev: false
resolution:
integrity: sha512-tF0hv+Yi2Ot1cwj9eYHtxC0jB9bmjacjQs6ZBTj82H8JwUywFuc+7E83NWfNMwHXZc11mjfFcVXPe9gEP4B8dg==
/esutils/2.0.3:
@@ -3965,6 +3981,7 @@ packages:
resolution:
integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
/fsevents/2.1.3:
+ dev: true
engines:
node: ^8.16.0 || ^10.6.0 || >=11.0.0
optional: true
@@ -4141,6 +4158,7 @@ packages:
/graphql/14.7.0:
dependencies:
iterall: 1.3.0
+ dev: true
engines:
node: '>= 6.x'
resolution:
@@ -4746,6 +4764,7 @@ packages:
resolution:
integrity: sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==
/iterall/1.3.0:
+ dev: true
resolution:
integrity: sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==
/js-string-escape/1.0.1:
@@ -6600,6 +6619,7 @@ packages:
resolution:
integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==
/rollup/2.32.1:
+ dev: true
engines:
node: '>=10.0.0'
hasBin: true