@rollup/plugin-node-resolve
Advanced tools
Comparing version 11.0.1 to 11.1.0
# @rollup/plugin-node-resolve ChangeLog | ||
## v11.1.0 | ||
_2021-01-15_ | ||
### Features | ||
- feat: support pkg imports and export array (#693) | ||
## v11.0.1 | ||
@@ -4,0 +12,0 @@ |
@@ -11,2 +11,3 @@ 'use strict'; | ||
var util = require('util'); | ||
var url = require('url'); | ||
var resolve = require('resolve'); | ||
@@ -233,164 +234,328 @@ var pluginutils = require('@rollup/pluginutils'); | ||
const resolveImportPath = util.promisify(resolve__default['default']); | ||
const readFile$1 = util.promisify(fs__default['default'].readFile); | ||
/* eslint-disable no-await-in-loop */ | ||
const pathNotFoundError = (importPath, importer, subPath, pkgPath) => | ||
new Error( | ||
`Could not resolve import "${importPath}" in "${importer}".` + | ||
` Package subpath "${subPath}" is not defined by "exports" in ${pkgPath}` | ||
); | ||
const fileExists = util.promisify(fs__default['default'].exists); | ||
function findExportKeyMatch(exportMap, subPath) { | ||
if (subPath in exportMap) { | ||
return subPath; | ||
function isModuleDir(current, moduleDirs) { | ||
return moduleDirs.some((dir) => current.endsWith(dir)); | ||
} | ||
async function findPackageJson(base, moduleDirs) { | ||
const { root } = path__default['default'].parse(base); | ||
let current = base; | ||
while (current !== root && !isModuleDir(current, moduleDirs)) { | ||
const pkgJsonPath = path__default['default'].join(current, 'package.json'); | ||
if (await fileExists(pkgJsonPath)) { | ||
const pkgJsonString = fs__default['default'].readFileSync(pkgJsonPath, 'utf-8'); | ||
return { pkgJson: JSON.parse(pkgJsonString), pkgPath: current, pkgJsonPath }; | ||
} | ||
current = path__default['default'].resolve(current, '..'); | ||
} | ||
return null; | ||
} | ||
const matchKeys = Object.keys(exportMap) | ||
.filter((key) => key.endsWith('/') || key.endsWith('*')) | ||
.sort((a, b) => b.length - a.length); | ||
function isUrl(str) { | ||
try { | ||
return !!new URL(str); | ||
} catch (_) { | ||
return false; | ||
} | ||
} | ||
for (const key of matchKeys) { | ||
if (key.endsWith('*')) { | ||
// star match: "./foo/*": "./foo/*.js" | ||
const keyWithoutStar = key.substring(0, key.length - 1); | ||
if (subPath.startsWith(keyWithoutStar)) { | ||
return key; | ||
function isConditions(exports) { | ||
return typeof exports === 'object' && Object.keys(exports).every((k) => !k.startsWith('.')); | ||
} | ||
function isMappings(exports) { | ||
return typeof exports === 'object' && !isConditions(exports); | ||
} | ||
function isMixedExports(exports) { | ||
const keys = Object.keys(exports); | ||
return keys.some((k) => k.startsWith('.')) && keys.some((k) => !k.startsWith('.')); | ||
} | ||
function createBaseErrorMsg(importSpecifier, importer) { | ||
return `Could not resolve import "${importSpecifier}" in ${importer}`; | ||
} | ||
function createErrorMsg(context, reason, internal) { | ||
const { importSpecifier, importer, pkgJsonPath } = context; | ||
const base = createBaseErrorMsg(importSpecifier, importer); | ||
const field = internal ? 'imports' : 'exports'; | ||
return `${base} using ${field} defined in ${pkgJsonPath}.${reason ? ` ${reason}` : ''}`; | ||
} | ||
class ResolveError extends Error {} | ||
class InvalidConfigurationError extends ResolveError { | ||
constructor(context, reason) { | ||
super(createErrorMsg(context, `Invalid "exports" field. ${reason}`)); | ||
} | ||
} | ||
class InvalidModuleSpecifierError extends ResolveError { | ||
constructor(context, internal) { | ||
super(createErrorMsg(context, internal)); | ||
} | ||
} | ||
class InvalidPackageTargetError extends ResolveError { | ||
constructor(context, reason) { | ||
super(createErrorMsg(context, reason)); | ||
} | ||
} | ||
/* eslint-disable no-await-in-loop, no-undefined */ | ||
function includesInvalidSegments(pathSegments, moduleDirs) { | ||
return pathSegments | ||
.split('/') | ||
.slice(1) | ||
.some((t) => ['.', '..', ...moduleDirs].includes(t)); | ||
} | ||
async function resolvePackageTarget(context, { target, subpath, pattern, internal }) { | ||
if (typeof target === 'string') { | ||
if (!pattern && subpath.length > 0 && !target.endsWith('/')) { | ||
throw new InvalidModuleSpecifierError(context); | ||
} | ||
if (!target.startsWith('./')) { | ||
if (internal && !['/', '../'].some((p) => target.startsWith(p)) && !isUrl(target)) { | ||
// this is a bare package import, remap it and resolve it using regular node resolve | ||
if (pattern) { | ||
const result = await context.resolveId( | ||
target.replace(/\*/g, subpath), | ||
context.pkgURL.href | ||
); | ||
return result ? url.pathToFileURL(result.location) : null; | ||
} | ||
const result = await context.resolveId(`${target}${subpath}`, context.pkgURL.href); | ||
return result ? url.pathToFileURL(result.location) : null; | ||
} | ||
throw new InvalidPackageTargetError(context, `Invalid mapping: "${target}".`); | ||
} | ||
if (key.endsWith('/') && subPath.startsWith(key)) { | ||
// directory match (deprecated by node): "./foo/": "./foo/.js" | ||
return key; | ||
if (includesInvalidSegments(target, context.moduleDirs)) { | ||
throw new InvalidPackageTargetError(context, `Invalid mapping: "${target}".`); | ||
} | ||
if (key === subPath) { | ||
// literal match | ||
return key; | ||
const resolvedTarget = new URL(target, context.pkgURL); | ||
if (!resolvedTarget.href.startsWith(context.pkgURL.href)) { | ||
throw new InvalidPackageTargetError( | ||
context, | ||
`Resolved to ${resolvedTarget.href} which is outside package ${context.pkgURL.href}` | ||
); | ||
} | ||
if (includesInvalidSegments(subpath, context.moduleDirs)) { | ||
throw new InvalidModuleSpecifierError(context); | ||
} | ||
if (pattern) { | ||
return resolvedTarget.href.replace(/\*/g, subpath); | ||
} | ||
return new URL(subpath, resolvedTarget).href; | ||
} | ||
return null; | ||
} | ||
function mapSubPath({ importPath, importer, 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 (Array.isArray(target)) { | ||
let lastError; | ||
for (const item of target) { | ||
try { | ||
const resolved = await resolvePackageTarget(context, { | ||
target: item, | ||
subpath, | ||
pattern, | ||
internal | ||
}); | ||
// return if defined or null, but not undefined | ||
if (resolved !== undefined) { | ||
return resolved; | ||
} | ||
} catch (error) { | ||
if (!(error instanceof InvalidPackageTargetError)) { | ||
throw error; | ||
} else { | ||
lastError = error; | ||
} | ||
} | ||
} | ||
if (value.endsWith('/')) { | ||
// directory match (deprecated by node): "./foo/": "./foo/.js" | ||
return `${value}${subPath.substring(key.length)}`; | ||
if (lastError) { | ||
throw lastError; | ||
} | ||
return null; | ||
} | ||
// mapping is a string, for example { "./foo": "./dist/foo.js" } | ||
return value; | ||
if (target && typeof target === 'object') { | ||
for (const [key, value] of Object.entries(target)) { | ||
if (key === 'default' || context.conditions.includes(key)) { | ||
const resolved = await resolvePackageTarget(context, { | ||
target: value, | ||
subpath, | ||
pattern, | ||
internal | ||
}); | ||
// return if defined or null, but not undefined | ||
if (resolved !== undefined) { | ||
return resolved; | ||
} | ||
} | ||
} | ||
return undefined; | ||
} | ||
if (Array.isArray(value)) { | ||
// mapping is an array with fallbacks, for example { "./foo": ["foo:bar", "./dist/foo.js"] } | ||
return value.find((v) => v.startsWith('./')); | ||
if (target === null) { | ||
return null; | ||
} | ||
throw pathNotFoundError(importPath, importer, subPath, pkgJsonPath); | ||
throw new InvalidPackageTargetError(context, `Invalid exports field.`); | ||
} | ||
function findEntrypoint({ | ||
importPath, | ||
importer, | ||
pkgJsonPath, | ||
subPath, | ||
exportMap, | ||
conditions, | ||
key | ||
}) { | ||
if (typeof exportMap !== 'object') { | ||
return mapSubPath({ importPath, importer, pkgJsonPath, subPath, key, value: exportMap }); | ||
/* eslint-disable no-await-in-loop */ | ||
async function resolvePackageImportsExports(context, { matchKey, matchObj, internal }) { | ||
if (!matchKey.endsWith('*') && matchKey in matchObj) { | ||
const target = matchObj[matchKey]; | ||
const resolved = await resolvePackageTarget(context, { target, subpath: '', internal }); | ||
return resolved; | ||
} | ||
// 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({ | ||
importPath, | ||
importer, | ||
pkgJsonPath, | ||
subPath, | ||
exportMap: subExportMap, | ||
conditions, | ||
key | ||
const expansionKeys = Object.keys(matchObj) | ||
.filter((k) => k.endsWith('/') || k.endsWith('*')) | ||
.sort((a, b) => b.length - a.length); | ||
for (const expansionKey of expansionKeys) { | ||
const prefix = expansionKey.substring(0, expansionKey.length - 1); | ||
if (expansionKey.endsWith('*') && matchKey.startsWith(prefix)) { | ||
const target = matchObj[expansionKey]; | ||
const subpath = matchKey.substring(expansionKey.length - 1); | ||
const resolved = await resolvePackageTarget(context, { | ||
target, | ||
subpath, | ||
pattern: true, | ||
internal | ||
}); | ||
if (mappedSubPath) { | ||
return mappedSubPath; | ||
} | ||
return resolved; | ||
} | ||
} | ||
throw pathNotFoundError(importer, subPath, pkgJsonPath); | ||
} | ||
function findEntrypointTopLevel({ | ||
importPath, | ||
importer, | ||
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(importPath, importer, subPath, pkgJsonPath); | ||
if (matchKey.startsWith(expansionKey)) { | ||
const target = matchObj[expansionKey]; | ||
const subpath = matchKey.substring(expansionKey.length); | ||
const resolved = await resolvePackageTarget(context, { target, subpath, internal }); | ||
return resolved; | ||
} | ||
return mapSubPath({ importPath, importer, pkgJsonPath, subPath, key: null, value: 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('.')); | ||
throw new InvalidModuleSpecifierError(context, internal); | ||
} | ||
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.' | ||
async function resolvePackageExports(context, subpath, exports) { | ||
if (isMixedExports(exports)) { | ||
throw new InvalidConfigurationError( | ||
context, | ||
'All keys must either start with ./, or without one.' | ||
); | ||
} | ||
let key = null; | ||
let exportMapForSubPath; | ||
if (subpath === '.') { | ||
let mainExport; | ||
// If exports is a String or Array, or an Object containing no keys starting with ".", then | ||
if (typeof exports === 'string' || Array.isArray(exports) || isConditions(exports)) { | ||
mainExport = exports; | ||
} else if (isMappings(exports)) { | ||
mainExport = exports['.']; | ||
} | ||
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(importPath, importer, subPath, pkgJsonPath); | ||
if (mainExport) { | ||
const resolved = await resolvePackageTarget(context, { target: mainExport, subpath: '' }); | ||
if (resolved) { | ||
return resolved; | ||
} | ||
} | ||
exportMapForSubPath = exportMap; | ||
} else { | ||
// top level is sub path mappings, for example { ".": ..., "./foo": ..., "./bar": ... } | ||
key = findExportKeyMatch(exportMap, subPath); | ||
if (!key) { | ||
throw pathNotFoundError(importPath, importer, subPath, pkgJsonPath); | ||
} else if (isMappings(exports)) { | ||
const resolvedMatch = await resolvePackageImportsExports(context, { | ||
matchKey: subpath, | ||
matchObj: exports | ||
}); | ||
if (resolvedMatch) { | ||
return resolvedMatch; | ||
} | ||
exportMapForSubPath = exportMap[key]; | ||
} | ||
return findEntrypoint({ | ||
importPath, | ||
throw new InvalidModuleSpecifierError(context); | ||
} | ||
async function resolvePackageImports({ | ||
importSpecifier, | ||
importer, | ||
moduleDirs, | ||
conditions, | ||
resolveId | ||
}) { | ||
const result = await findPackageJson(importer, moduleDirs); | ||
if (!result) { | ||
throw new Error(createBaseErrorMsg('. Could not find a parent package.json.')); | ||
} | ||
const { pkgPath, pkgJsonPath, pkgJson } = result; | ||
const pkgURL = url.pathToFileURL(`${pkgPath}/`); | ||
const context = { | ||
importer, | ||
importSpecifier, | ||
moduleDirs, | ||
pkgURL, | ||
pkgJsonPath, | ||
subPath, | ||
exportMap: exportMapForSubPath, | ||
conditions, | ||
key | ||
resolveId | ||
}; | ||
const { imports } = pkgJson; | ||
if (!imports) { | ||
throw new InvalidModuleSpecifierError(context, true); | ||
} | ||
if (importSpecifier === '#' || importSpecifier.startsWith('#/')) { | ||
throw new InvalidModuleSpecifierError(context, 'Invalid import specifier.'); | ||
} | ||
return resolvePackageImportsExports(context, { | ||
matchKey: importSpecifier, | ||
matchObj: imports, | ||
internal: true | ||
}); | ||
} | ||
const resolveImportPath = util.promisify(resolve__default['default']); | ||
const readFile$1 = util.promisify(fs__default['default'].readFile); | ||
async function getPackageJson(importer, pkgName, resolveOptions, moduleDirectories) { | ||
if (importer) { | ||
const selfPackageJsonResult = await findPackageJson(importer, moduleDirectories); | ||
if (selfPackageJsonResult && selfPackageJsonResult.pkgJson.name === pkgName) { | ||
// the referenced package name is the current package | ||
return selfPackageJsonResult; | ||
} | ||
} | ||
try { | ||
const pkgJsonPath = await resolveImportPath(`${pkgName}/package.json`, resolveOptions); | ||
const pkgJson = JSON.parse(await readFile$1(pkgJsonPath, 'utf-8')); | ||
return { pkgJsonPath, pkgJson }; | ||
} catch (_) { | ||
return null; | ||
} | ||
} | ||
async function resolveId({ | ||
importer, | ||
importPath, | ||
importSpecifier, | ||
exportConditions, | ||
@@ -441,28 +606,57 @@ warn, | ||
const pkgName = getPackageName(importPath); | ||
if (pkgName) { | ||
let pkgJsonPath; | ||
let pkgJson; | ||
try { | ||
pkgJsonPath = await resolveImportPath(`${pkgName}/package.json`, resolveOptions); | ||
pkgJson = JSON.parse(await readFile$1(pkgJsonPath, 'utf-8')); | ||
} catch (_) { | ||
// if there is no package.json we defer to regular resolve behavior | ||
} | ||
const pkgName = getPackageName(importSpecifier); | ||
if (importSpecifier.startsWith('#')) { | ||
// this is a package internal import, resolve using package imports field | ||
const resolveResult = await resolvePackageImports({ | ||
importSpecifier, | ||
importer, | ||
moduleDirs: moduleDirectories, | ||
conditions: exportConditions, | ||
resolveId(id, parent) { | ||
return resolveId({ | ||
importSpecifier: id, | ||
importer: parent, | ||
exportConditions, | ||
warn, | ||
packageInfoCache, | ||
extensions, | ||
mainFields, | ||
preserveSymlinks, | ||
useBrowserOverrides, | ||
baseDir, | ||
moduleDirectories | ||
}); | ||
} | ||
}); | ||
location = url.fileURLToPath(resolveResult); | ||
} else if (pkgName) { | ||
// it's a bare import, find the package.json and resolve using package exports if available | ||
const result = await getPackageJson(importer, pkgName, resolveOptions, moduleDirectories); | ||
if (pkgJsonPath && pkgJson && pkgJson.exports) { | ||
if (result && result.pkgJson.exports) { | ||
const { pkgJson, pkgJsonPath } = result; | ||
try { | ||
const packageSubPath = | ||
pkgName === importPath ? '.' : `.${importPath.substring(pkgName.length)}`; | ||
const mappedSubPath = findEntrypointTopLevel({ | ||
const subpath = | ||
pkgName === importSpecifier ? '.' : `.${importSpecifier.substring(pkgName.length)}`; | ||
const pkgDr = pkgJsonPath.replace('package.json', ''); | ||
const pkgURL = url.pathToFileURL(pkgDr); | ||
const context = { | ||
importer, | ||
importPath, | ||
importSpecifier, | ||
moduleDirs: moduleDirectories, | ||
pkgURL, | ||
pkgJsonPath, | ||
subPath: packageSubPath, | ||
exportMap: pkgJson.exports, | ||
conditions: exportConditions | ||
}); | ||
const pkgDir = path__default['default'].dirname(pkgJsonPath); | ||
location = path__default['default'].join(pkgDir, mappedSubPath); | ||
}; | ||
const resolvedPackageExport = await resolvePackageExports( | ||
context, | ||
subpath, | ||
pkgJson.exports | ||
); | ||
location = url.fileURLToPath(resolvedPackageExport); | ||
} catch (error) { | ||
if (!(error instanceof ResolveError)) { | ||
throw error; | ||
} | ||
warn(error); | ||
@@ -475,4 +669,5 @@ return null; | ||
if (!location) { | ||
// package has no imports or exports, use classic node resolve | ||
try { | ||
location = await resolveImportPath(importPath, resolveOptions); | ||
location = await resolveImportPath(importSpecifier, resolveOptions); | ||
} catch (error) { | ||
@@ -520,3 +715,3 @@ if (error.code !== 'MODULE_NOT_FOUND') { | ||
importer, | ||
importPath: importSpecifierList[i], | ||
importSpecifier: importSpecifierList[i], | ||
exportConditions, | ||
@@ -523,0 +718,0 @@ warn, |
@@ -7,2 +7,3 @@ import path, { dirname, resolve, extname, normalize, sep } from 'path'; | ||
import { promisify } from 'util'; | ||
import { pathToFileURL, fileURLToPath } from 'url'; | ||
import resolve$1 from 'resolve'; | ||
@@ -220,164 +221,328 @@ import { createFilter } from '@rollup/pluginutils'; | ||
const resolveImportPath = promisify(resolve$1); | ||
const readFile$1 = promisify(fs.readFile); | ||
/* eslint-disable no-await-in-loop */ | ||
const pathNotFoundError = (importPath, importer, subPath, pkgPath) => | ||
new Error( | ||
`Could not resolve import "${importPath}" in "${importer}".` + | ||
` Package subpath "${subPath}" is not defined by "exports" in ${pkgPath}` | ||
); | ||
const fileExists = promisify(fs.exists); | ||
function findExportKeyMatch(exportMap, subPath) { | ||
if (subPath in exportMap) { | ||
return subPath; | ||
function isModuleDir(current, moduleDirs) { | ||
return moduleDirs.some((dir) => current.endsWith(dir)); | ||
} | ||
async function findPackageJson(base, moduleDirs) { | ||
const { root } = path.parse(base); | ||
let current = base; | ||
while (current !== root && !isModuleDir(current, moduleDirs)) { | ||
const pkgJsonPath = path.join(current, 'package.json'); | ||
if (await fileExists(pkgJsonPath)) { | ||
const pkgJsonString = fs.readFileSync(pkgJsonPath, 'utf-8'); | ||
return { pkgJson: JSON.parse(pkgJsonString), pkgPath: current, pkgJsonPath }; | ||
} | ||
current = path.resolve(current, '..'); | ||
} | ||
return null; | ||
} | ||
const matchKeys = Object.keys(exportMap) | ||
.filter((key) => key.endsWith('/') || key.endsWith('*')) | ||
.sort((a, b) => b.length - a.length); | ||
function isUrl(str) { | ||
try { | ||
return !!new URL(str); | ||
} catch (_) { | ||
return false; | ||
} | ||
} | ||
for (const key of matchKeys) { | ||
if (key.endsWith('*')) { | ||
// star match: "./foo/*": "./foo/*.js" | ||
const keyWithoutStar = key.substring(0, key.length - 1); | ||
if (subPath.startsWith(keyWithoutStar)) { | ||
return key; | ||
function isConditions(exports) { | ||
return typeof exports === 'object' && Object.keys(exports).every((k) => !k.startsWith('.')); | ||
} | ||
function isMappings(exports) { | ||
return typeof exports === 'object' && !isConditions(exports); | ||
} | ||
function isMixedExports(exports) { | ||
const keys = Object.keys(exports); | ||
return keys.some((k) => k.startsWith('.')) && keys.some((k) => !k.startsWith('.')); | ||
} | ||
function createBaseErrorMsg(importSpecifier, importer) { | ||
return `Could not resolve import "${importSpecifier}" in ${importer}`; | ||
} | ||
function createErrorMsg(context, reason, internal) { | ||
const { importSpecifier, importer, pkgJsonPath } = context; | ||
const base = createBaseErrorMsg(importSpecifier, importer); | ||
const field = internal ? 'imports' : 'exports'; | ||
return `${base} using ${field} defined in ${pkgJsonPath}.${reason ? ` ${reason}` : ''}`; | ||
} | ||
class ResolveError extends Error {} | ||
class InvalidConfigurationError extends ResolveError { | ||
constructor(context, reason) { | ||
super(createErrorMsg(context, `Invalid "exports" field. ${reason}`)); | ||
} | ||
} | ||
class InvalidModuleSpecifierError extends ResolveError { | ||
constructor(context, internal) { | ||
super(createErrorMsg(context, internal)); | ||
} | ||
} | ||
class InvalidPackageTargetError extends ResolveError { | ||
constructor(context, reason) { | ||
super(createErrorMsg(context, reason)); | ||
} | ||
} | ||
/* eslint-disable no-await-in-loop, no-undefined */ | ||
function includesInvalidSegments(pathSegments, moduleDirs) { | ||
return pathSegments | ||
.split('/') | ||
.slice(1) | ||
.some((t) => ['.', '..', ...moduleDirs].includes(t)); | ||
} | ||
async function resolvePackageTarget(context, { target, subpath, pattern, internal }) { | ||
if (typeof target === 'string') { | ||
if (!pattern && subpath.length > 0 && !target.endsWith('/')) { | ||
throw new InvalidModuleSpecifierError(context); | ||
} | ||
if (!target.startsWith('./')) { | ||
if (internal && !['/', '../'].some((p) => target.startsWith(p)) && !isUrl(target)) { | ||
// this is a bare package import, remap it and resolve it using regular node resolve | ||
if (pattern) { | ||
const result = await context.resolveId( | ||
target.replace(/\*/g, subpath), | ||
context.pkgURL.href | ||
); | ||
return result ? pathToFileURL(result.location) : null; | ||
} | ||
const result = await context.resolveId(`${target}${subpath}`, context.pkgURL.href); | ||
return result ? pathToFileURL(result.location) : null; | ||
} | ||
throw new InvalidPackageTargetError(context, `Invalid mapping: "${target}".`); | ||
} | ||
if (key.endsWith('/') && subPath.startsWith(key)) { | ||
// directory match (deprecated by node): "./foo/": "./foo/.js" | ||
return key; | ||
if (includesInvalidSegments(target, context.moduleDirs)) { | ||
throw new InvalidPackageTargetError(context, `Invalid mapping: "${target}".`); | ||
} | ||
if (key === subPath) { | ||
// literal match | ||
return key; | ||
const resolvedTarget = new URL(target, context.pkgURL); | ||
if (!resolvedTarget.href.startsWith(context.pkgURL.href)) { | ||
throw new InvalidPackageTargetError( | ||
context, | ||
`Resolved to ${resolvedTarget.href} which is outside package ${context.pkgURL.href}` | ||
); | ||
} | ||
if (includesInvalidSegments(subpath, context.moduleDirs)) { | ||
throw new InvalidModuleSpecifierError(context); | ||
} | ||
if (pattern) { | ||
return resolvedTarget.href.replace(/\*/g, subpath); | ||
} | ||
return new URL(subpath, resolvedTarget).href; | ||
} | ||
return null; | ||
} | ||
function mapSubPath({ importPath, importer, 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 (Array.isArray(target)) { | ||
let lastError; | ||
for (const item of target) { | ||
try { | ||
const resolved = await resolvePackageTarget(context, { | ||
target: item, | ||
subpath, | ||
pattern, | ||
internal | ||
}); | ||
// return if defined or null, but not undefined | ||
if (resolved !== undefined) { | ||
return resolved; | ||
} | ||
} catch (error) { | ||
if (!(error instanceof InvalidPackageTargetError)) { | ||
throw error; | ||
} else { | ||
lastError = error; | ||
} | ||
} | ||
} | ||
if (value.endsWith('/')) { | ||
// directory match (deprecated by node): "./foo/": "./foo/.js" | ||
return `${value}${subPath.substring(key.length)}`; | ||
if (lastError) { | ||
throw lastError; | ||
} | ||
return null; | ||
} | ||
// mapping is a string, for example { "./foo": "./dist/foo.js" } | ||
return value; | ||
if (target && typeof target === 'object') { | ||
for (const [key, value] of Object.entries(target)) { | ||
if (key === 'default' || context.conditions.includes(key)) { | ||
const resolved = await resolvePackageTarget(context, { | ||
target: value, | ||
subpath, | ||
pattern, | ||
internal | ||
}); | ||
// return if defined or null, but not undefined | ||
if (resolved !== undefined) { | ||
return resolved; | ||
} | ||
} | ||
} | ||
return undefined; | ||
} | ||
if (Array.isArray(value)) { | ||
// mapping is an array with fallbacks, for example { "./foo": ["foo:bar", "./dist/foo.js"] } | ||
return value.find((v) => v.startsWith('./')); | ||
if (target === null) { | ||
return null; | ||
} | ||
throw pathNotFoundError(importPath, importer, subPath, pkgJsonPath); | ||
throw new InvalidPackageTargetError(context, `Invalid exports field.`); | ||
} | ||
function findEntrypoint({ | ||
importPath, | ||
importer, | ||
pkgJsonPath, | ||
subPath, | ||
exportMap, | ||
conditions, | ||
key | ||
}) { | ||
if (typeof exportMap !== 'object') { | ||
return mapSubPath({ importPath, importer, pkgJsonPath, subPath, key, value: exportMap }); | ||
/* eslint-disable no-await-in-loop */ | ||
async function resolvePackageImportsExports(context, { matchKey, matchObj, internal }) { | ||
if (!matchKey.endsWith('*') && matchKey in matchObj) { | ||
const target = matchObj[matchKey]; | ||
const resolved = await resolvePackageTarget(context, { target, subpath: '', internal }); | ||
return resolved; | ||
} | ||
// 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({ | ||
importPath, | ||
importer, | ||
pkgJsonPath, | ||
subPath, | ||
exportMap: subExportMap, | ||
conditions, | ||
key | ||
const expansionKeys = Object.keys(matchObj) | ||
.filter((k) => k.endsWith('/') || k.endsWith('*')) | ||
.sort((a, b) => b.length - a.length); | ||
for (const expansionKey of expansionKeys) { | ||
const prefix = expansionKey.substring(0, expansionKey.length - 1); | ||
if (expansionKey.endsWith('*') && matchKey.startsWith(prefix)) { | ||
const target = matchObj[expansionKey]; | ||
const subpath = matchKey.substring(expansionKey.length - 1); | ||
const resolved = await resolvePackageTarget(context, { | ||
target, | ||
subpath, | ||
pattern: true, | ||
internal | ||
}); | ||
if (mappedSubPath) { | ||
return mappedSubPath; | ||
} | ||
return resolved; | ||
} | ||
} | ||
throw pathNotFoundError(importer, subPath, pkgJsonPath); | ||
} | ||
function findEntrypointTopLevel({ | ||
importPath, | ||
importer, | ||
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(importPath, importer, subPath, pkgJsonPath); | ||
if (matchKey.startsWith(expansionKey)) { | ||
const target = matchObj[expansionKey]; | ||
const subpath = matchKey.substring(expansionKey.length); | ||
const resolved = await resolvePackageTarget(context, { target, subpath, internal }); | ||
return resolved; | ||
} | ||
return mapSubPath({ importPath, importer, pkgJsonPath, subPath, key: null, value: 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('.')); | ||
throw new InvalidModuleSpecifierError(context, internal); | ||
} | ||
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.' | ||
async function resolvePackageExports(context, subpath, exports) { | ||
if (isMixedExports(exports)) { | ||
throw new InvalidConfigurationError( | ||
context, | ||
'All keys must either start with ./, or without one.' | ||
); | ||
} | ||
let key = null; | ||
let exportMapForSubPath; | ||
if (subpath === '.') { | ||
let mainExport; | ||
// If exports is a String or Array, or an Object containing no keys starting with ".", then | ||
if (typeof exports === 'string' || Array.isArray(exports) || isConditions(exports)) { | ||
mainExport = exports; | ||
} else if (isMappings(exports)) { | ||
mainExport = exports['.']; | ||
} | ||
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(importPath, importer, subPath, pkgJsonPath); | ||
if (mainExport) { | ||
const resolved = await resolvePackageTarget(context, { target: mainExport, subpath: '' }); | ||
if (resolved) { | ||
return resolved; | ||
} | ||
} | ||
exportMapForSubPath = exportMap; | ||
} else { | ||
// top level is sub path mappings, for example { ".": ..., "./foo": ..., "./bar": ... } | ||
key = findExportKeyMatch(exportMap, subPath); | ||
if (!key) { | ||
throw pathNotFoundError(importPath, importer, subPath, pkgJsonPath); | ||
} else if (isMappings(exports)) { | ||
const resolvedMatch = await resolvePackageImportsExports(context, { | ||
matchKey: subpath, | ||
matchObj: exports | ||
}); | ||
if (resolvedMatch) { | ||
return resolvedMatch; | ||
} | ||
exportMapForSubPath = exportMap[key]; | ||
} | ||
return findEntrypoint({ | ||
importPath, | ||
throw new InvalidModuleSpecifierError(context); | ||
} | ||
async function resolvePackageImports({ | ||
importSpecifier, | ||
importer, | ||
moduleDirs, | ||
conditions, | ||
resolveId | ||
}) { | ||
const result = await findPackageJson(importer, moduleDirs); | ||
if (!result) { | ||
throw new Error(createBaseErrorMsg('. Could not find a parent package.json.')); | ||
} | ||
const { pkgPath, pkgJsonPath, pkgJson } = result; | ||
const pkgURL = pathToFileURL(`${pkgPath}/`); | ||
const context = { | ||
importer, | ||
importSpecifier, | ||
moduleDirs, | ||
pkgURL, | ||
pkgJsonPath, | ||
subPath, | ||
exportMap: exportMapForSubPath, | ||
conditions, | ||
key | ||
resolveId | ||
}; | ||
const { imports } = pkgJson; | ||
if (!imports) { | ||
throw new InvalidModuleSpecifierError(context, true); | ||
} | ||
if (importSpecifier === '#' || importSpecifier.startsWith('#/')) { | ||
throw new InvalidModuleSpecifierError(context, 'Invalid import specifier.'); | ||
} | ||
return resolvePackageImportsExports(context, { | ||
matchKey: importSpecifier, | ||
matchObj: imports, | ||
internal: true | ||
}); | ||
} | ||
const resolveImportPath = promisify(resolve$1); | ||
const readFile$1 = promisify(fs.readFile); | ||
async function getPackageJson(importer, pkgName, resolveOptions, moduleDirectories) { | ||
if (importer) { | ||
const selfPackageJsonResult = await findPackageJson(importer, moduleDirectories); | ||
if (selfPackageJsonResult && selfPackageJsonResult.pkgJson.name === pkgName) { | ||
// the referenced package name is the current package | ||
return selfPackageJsonResult; | ||
} | ||
} | ||
try { | ||
const pkgJsonPath = await resolveImportPath(`${pkgName}/package.json`, resolveOptions); | ||
const pkgJson = JSON.parse(await readFile$1(pkgJsonPath, 'utf-8')); | ||
return { pkgJsonPath, pkgJson }; | ||
} catch (_) { | ||
return null; | ||
} | ||
} | ||
async function resolveId({ | ||
importer, | ||
importPath, | ||
importSpecifier, | ||
exportConditions, | ||
@@ -428,28 +593,57 @@ warn, | ||
const pkgName = getPackageName(importPath); | ||
if (pkgName) { | ||
let pkgJsonPath; | ||
let pkgJson; | ||
try { | ||
pkgJsonPath = await resolveImportPath(`${pkgName}/package.json`, resolveOptions); | ||
pkgJson = JSON.parse(await readFile$1(pkgJsonPath, 'utf-8')); | ||
} catch (_) { | ||
// if there is no package.json we defer to regular resolve behavior | ||
} | ||
const pkgName = getPackageName(importSpecifier); | ||
if (importSpecifier.startsWith('#')) { | ||
// this is a package internal import, resolve using package imports field | ||
const resolveResult = await resolvePackageImports({ | ||
importSpecifier, | ||
importer, | ||
moduleDirs: moduleDirectories, | ||
conditions: exportConditions, | ||
resolveId(id, parent) { | ||
return resolveId({ | ||
importSpecifier: id, | ||
importer: parent, | ||
exportConditions, | ||
warn, | ||
packageInfoCache, | ||
extensions, | ||
mainFields, | ||
preserveSymlinks, | ||
useBrowserOverrides, | ||
baseDir, | ||
moduleDirectories | ||
}); | ||
} | ||
}); | ||
location = fileURLToPath(resolveResult); | ||
} else if (pkgName) { | ||
// it's a bare import, find the package.json and resolve using package exports if available | ||
const result = await getPackageJson(importer, pkgName, resolveOptions, moduleDirectories); | ||
if (pkgJsonPath && pkgJson && pkgJson.exports) { | ||
if (result && result.pkgJson.exports) { | ||
const { pkgJson, pkgJsonPath } = result; | ||
try { | ||
const packageSubPath = | ||
pkgName === importPath ? '.' : `.${importPath.substring(pkgName.length)}`; | ||
const mappedSubPath = findEntrypointTopLevel({ | ||
const subpath = | ||
pkgName === importSpecifier ? '.' : `.${importSpecifier.substring(pkgName.length)}`; | ||
const pkgDr = pkgJsonPath.replace('package.json', ''); | ||
const pkgURL = pathToFileURL(pkgDr); | ||
const context = { | ||
importer, | ||
importPath, | ||
importSpecifier, | ||
moduleDirs: moduleDirectories, | ||
pkgURL, | ||
pkgJsonPath, | ||
subPath: packageSubPath, | ||
exportMap: pkgJson.exports, | ||
conditions: exportConditions | ||
}); | ||
const pkgDir = path.dirname(pkgJsonPath); | ||
location = path.join(pkgDir, mappedSubPath); | ||
}; | ||
const resolvedPackageExport = await resolvePackageExports( | ||
context, | ||
subpath, | ||
pkgJson.exports | ||
); | ||
location = fileURLToPath(resolvedPackageExport); | ||
} catch (error) { | ||
if (!(error instanceof ResolveError)) { | ||
throw error; | ||
} | ||
warn(error); | ||
@@ -462,4 +656,5 @@ return null; | ||
if (!location) { | ||
// package has no imports or exports, use classic node resolve | ||
try { | ||
location = await resolveImportPath(importPath, resolveOptions); | ||
location = await resolveImportPath(importSpecifier, resolveOptions); | ||
} catch (error) { | ||
@@ -507,3 +702,3 @@ if (error.code !== 'MODULE_NOT_FOUND') { | ||
importer, | ||
importPath: importSpecifierList[i], | ||
importSpecifier: importSpecifierList[i], | ||
exportConditions, | ||
@@ -510,0 +705,0 @@ warn, |
{ | ||
"name": "@rollup/plugin-node-resolve", | ||
"version": "11.0.1", | ||
"version": "11.1.0", | ||
"publishConfig": { | ||
@@ -5,0 +5,0 @@ "access": "public" |
@@ -45,2 +45,6 @@ [npm]: https://img.shields.io/npm/v/@rollup/plugin-node-resolve | ||
## Package entrypoints | ||
This plugin supports the package entrypoints feature from node js, specified in the `exports` or `imports` field of a package. Check the [official documentation](https://nodejs.org/api/packages.html#packages_package_entry_points) for more information on how this works. | ||
## Options | ||
@@ -66,2 +70,4 @@ | ||
> This option does not work when a package is using [package entrypoints](https://nodejs.org/api/packages.html#packages_package_entry_points) | ||
### `moduleDirectories` | ||
@@ -68,0 +74,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
84980
1883
221
1