From 21f76b75ca4e9d2cdc8fb2a25e49168801026b78 Mon Sep 17 00:00:00 2001 From: Lars den Bakker Date: Thu, 13 Aug 2020 18:02:33 +0200 Subject: [PATCH] feat(node-resolve): support package entry points --- .eslintignore | 2 +- packages/node-resolve/README.md | 15 +- packages/node-resolve/src/index.js | 13 +- packages/node-resolve/src/resolveId.js | 121 ++++++++++++++++ packages/node-resolve/src/util.js | 24 ++-- .../exports-mappings-and-conditions.js | 5 + .../fixtures/exports-nested-conditions.js | 3 + .../fixtures/exports-non-existing-subpath.js | 3 + .../exports-prevent-unspecified-subpath.js | 3 + .../fixtures/exports-shorthand-fallback.js | 3 + .../fixtures/exports-shorthand-subpath.js | 3 + .../test/fixtures/exports-shorthand.js | 3 + .../fixtures/exports-top-level-conditions.js | 3 + .../fixtures/exports-top-level-mappings.js | 4 + .../bar-mapped.js | 1 + .../exports-mappings-and-conditions/bar.js | 1 + .../foo-mapped.js | 1 + .../exports-mappings-and-conditions/foo.js | 1 + .../index-mapped.js | 1 + .../exports-mappings-and-conditions/index.js | 1 + .../package.json | 21 +++ .../exports-nested-conditions/index-mapped.js | 1 + .../exports-nested-conditions/index.js | 1 + .../exports-nested-conditions/package.json | 11 ++ .../foo-mapped.js | 1 + .../exports-non-existing-subpath/foo.js | 1 + .../index-mapped.js | 1 + .../exports-non-existing-subpath/index.js | 1 + .../exports-non-existing-subpath/package.json | 8 ++ .../exports-prevent-direct-imports/bar.js | 1 + .../exports-prevent-direct-imports/foo.js | 1 + .../index-mapped.js | 1 + .../exports-prevent-direct-imports/index.js | 1 + .../package.json | 8 ++ .../foo-mapped.js | 1 + .../foo.js | 1 + .../index-mapped.js | 1 + .../index.js | 1 + .../package.json | 8 ++ .../index-mapped.js | 1 + .../exports-shorthand-fallback/index.js | 1 + .../exports-shorthand-fallback/package.json | 5 + .../exports-shorthand/index-mapped.js | 1 + .../node_modules/exports-shorthand/index.js | 1 + .../exports-shorthand/package.json | 5 + .../index-mapped.js | 1 + .../exports-top-level-conditions/index.js | 1 + .../exports-top-level-conditions/package.json | 8 ++ .../exports-top-level-mappings/foo-mapped.js | 1 + .../exports-top-level-mappings/foo.js | 1 + .../index-mapped.js | 1 + .../exports-top-level-mappings/index.js | 1 + .../exports-top-level-mappings/package.json | 8 ++ .../node-resolve/test/package-entry-points.js | 129 ++++++++++++++++++ packages/node-resolve/types/index.d.ts | 7 + 55 files changed, 435 insertions(+), 18 deletions(-) create mode 100644 packages/node-resolve/src/resolveId.js create mode 100644 packages/node-resolve/test/fixtures/exports-mappings-and-conditions.js create mode 100644 packages/node-resolve/test/fixtures/exports-nested-conditions.js create mode 100644 packages/node-resolve/test/fixtures/exports-non-existing-subpath.js create mode 100644 packages/node-resolve/test/fixtures/exports-prevent-unspecified-subpath.js create mode 100644 packages/node-resolve/test/fixtures/exports-shorthand-fallback.js create mode 100644 packages/node-resolve/test/fixtures/exports-shorthand-subpath.js create mode 100644 packages/node-resolve/test/fixtures/exports-shorthand.js create mode 100644 packages/node-resolve/test/fixtures/exports-top-level-conditions.js create mode 100644 packages/node-resolve/test/fixtures/exports-top-level-mappings.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/bar-mapped.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/bar.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/foo-mapped.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/foo.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/index-mapped.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/index.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-mappings-and-conditions/package.json create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-nested-conditions/index-mapped.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-nested-conditions/index.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-nested-conditions/package.json create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/foo-mapped.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/foo.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/index-mapped.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/index.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-non-existing-subpath/package.json create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/bar.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/foo.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/index-mapped.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/index.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-prevent-direct-imports/package.json create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/foo-mapped.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/foo.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/index-mapped.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/index.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-prevent-unspecified-subpath/package.json create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-shorthand-fallback/index-mapped.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-shorthand-fallback/index.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-shorthand-fallback/package.json create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-shorthand/index-mapped.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-shorthand/index.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-shorthand/package.json create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-top-level-conditions/index-mapped.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-top-level-conditions/index.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-top-level-conditions/package.json create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/foo-mapped.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/foo.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/index-mapped.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/index.js create mode 100644 packages/node-resolve/test/fixtures/node_modules/exports-top-level-mappings/package.json create mode 100644 packages/node-resolve/test/package-entry-points.js diff --git a/.eslintignore b/.eslintignore index a9ba028ce..82a9a730d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1 @@ -.eslintrc.js +.eslintrc.js \ No newline at end of file diff --git a/packages/node-resolve/README.md b/packages/node-resolve/README.md index 9f093239e..6d0dd08ef 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,13 @@ Then call `rollup` either via the [CLI](https://www.rollupjs.org/guide/en/#comma ## Options +### `exportConditions` + +Type: `Array[...String]`
+Default: `['module', 'default']` + +Conditions used to select exports defined using node js [package exports](https://nodejs.org/api/esm.html#esm_packages). Conditions are evaluated left to right, returning the first match that is found. Setting this property overwrites the default values. + ### `browser` Type: `Boolean`
@@ -164,9 +171,9 @@ export default { output: { file: 'bundle.js', format: 'iife', - name: 'MyModule' + name: 'MyModule', }, - plugins: [resolve(), commonjs()] + plugins: [resolve(), commonjs()], }; ``` diff --git a/packages/node-resolve/src/index.js b/packages/node-resolve/src/index.js index 0c045b003..b37dbe341 100644 --- a/packages/node-resolve/src/index.js +++ b/packages/node-resolve/src/index.js @@ -18,7 +18,7 @@ import { const builtins = new Set(builtinList); const ES6_BROWSER_EMPTY = '\0node-resolve:empty.js'; const nullFn = () => null; -const deepFreeze = object => { +const deepFreeze = (object) => { Object.freeze(object); for (const value of Object.values(object)) { @@ -30,6 +30,7 @@ const deepFreeze = object => { return object; }; const defaults = { + exportConditions: ['module', 'default'], customResolveOptions: {}, dedupe: [], // It's important that .mjs is listed before .js so that Rollup will interpret npm modules @@ -41,7 +42,7 @@ export const DEFAULTS = deepFreeze(deepMerge({}, defaults)); export function nodeResolve(opts = {}) { const options = Object.assign({}, defaults, opts); - const { customResolveOptions, extensions, jail } = options; + const { exportConditions, customResolveOptions, extensions, jail } = options; const warnings = []; const packageInfoCache = new Map(); const idToPackageInfo = new Map(); @@ -220,7 +221,13 @@ export function nodeResolve(opts = {}) { resolveOptions = Object.assign(resolveOptions, customResolveOptions); try { - let resolved = await resolveImportSpecifiers(importSpecifierList, resolveOptions); + const warn = (...args) => this.warn(...args); + let resolved = await resolveImportSpecifiers( + importSpecifierList, + resolveOptions, + exportConditions, + warn + ); if (resolved && packageBrowserField) { if (Object.prototype.hasOwnProperty.call(packageBrowserField, resolved)) { diff --git a/packages/node-resolve/src/resolveId.js b/packages/node-resolve/src/resolveId.js new file mode 100644 index 000000000..156f65b8c --- /dev/null +++ b/packages/node-resolve/src/resolveId.js @@ -0,0 +1,121 @@ +import fs from 'fs'; +import path from 'path'; +import { promisify } from 'util'; + +import resolve from 'resolve'; + +import { getPackageName } from './util'; + +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 mapSubPath(pkgJsonPath, subPath, value) { + if (typeof value === 'string') { + // 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) { + if (typeof exportMap !== 'object') { + return mapSubPath(pkgJsonPath, subPath, 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); + 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, 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 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": ... } + if (!(subPath in exportMap)) { + throw pathNotFoundError(subPath, pkgJsonPath); + } + exportMapForSubPath = exportMap[subPath]; + } + + return findEntrypoint(pkgJsonPath, subPath, exportMapForSubPath, conditions); +} + +export default 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); +} diff --git a/packages/node-resolve/src/util.js b/packages/node-resolve/src/util.js index c4c8949c6..96d583fd8 100644 --- a/packages/node-resolve/src/util.js +++ b/packages/node-resolve/src/util.js @@ -1,14 +1,11 @@ import { dirname, extname, resolve } from 'path'; -import { promisify } from 'util'; import { createFilter } from '@rollup/pluginutils'; -import resolveModule from 'resolve'; +import resolveId from './resolveId'; import { realpathSync } from './fs'; -const resolveId = promisify(resolveModule); - // returns the imported package name for bare module imports export function getPackageName(id) { if (id.startsWith('.') || id.startsWith('/')) { @@ -161,7 +158,12 @@ export function normalizeInput(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) { +export function resolveImportSpecifiers( + importSpecifierList, + resolveOptions, + exportConditions, + warn +) { let promise = Promise.resolve(); for (let i = 0; i < importSpecifierList.length; i++) { @@ -171,12 +173,14 @@ export function resolveImportSpecifiers(importSpecifierList, resolveOptions) { return value; } - return resolveId(importSpecifierList[i], resolveOptions).then((result) => { - if (!resolveOptions.preserveSymlinks) { - result = realpathSync(result); + return resolveId(importSpecifierList[i], resolveOptions, exportConditions, warn).then( + (result) => { + if (!resolveOptions.preserveSymlinks) { + result = realpathSync(result); + } + return result; } - return result; - }); + ); }); if (i < importSpecifierList.length - 1) { 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-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-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-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..cbe5609a8 --- /dev/null +++ b/packages/node-resolve/test/package-entry-points.js @@ -0,0 +1,129 @@ +const { join } = require('path'); + +const test = require('ava'); +const { rollup } = require('rollup'); + +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 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('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.only('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')); +}); diff --git a/packages/node-resolve/types/index.d.ts b/packages/node-resolve/types/index.d.ts index d2fed0f77..cba4e5665 100755 --- a/packages/node-resolve/types/index.d.ts +++ b/packages/node-resolve/types/index.d.ts @@ -9,6 +9,13 @@ export const DEFAULTS: { }; export interface RollupNodeResolveOptions { + /** + * Conditions used to select exports defined using node js [package exports](https://nodejs.org/api/esm.html#esm_packages). + * Conditions are evaluated left to right, returning the first match that is found. + * Setting this property overwrites the default values. + */ + 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