diff --git a/packages/node-resolve/src/resolveId.js b/packages/node-resolve/src/resolveId.js new file mode 100644 index 000000000..ee17a4b31 --- /dev/null +++ b/packages/node-resolve/src/resolveId.js @@ -0,0 +1,130 @@ +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 findExportKeyMatch(keys, subPath) { + return keys.find((key) => (key.endsWith('/') ? subPath.startsWith(key) : key === subPath)); +} + +function mapSubPath(pkgJsonPath, subPath, key, value) { + if (typeof value === 'string') { + if (value.endsWith('/')) { + 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(keys, subPath); + if (!key) { + throw pathNotFoundError(subPath, pkgJsonPath); + } + exportMapForSubPath = exportMap[key]; + } + + return findEntrypoint(pkgJsonPath, subPath, exportMapForSubPath, conditions, key); +} + +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/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-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-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-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..dcfbed8ff --- /dev/null +++ b/packages/node-resolve/test/package-entry-points.js @@ -0,0 +1,173 @@ +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 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')); +});