@rollup/plugin-node-resolve
Advanced tools
Comparing version 15.2.0 to 15.2.1
@@ -15,3 +15,3 @@ 'use strict'; | ||
var version = "15.2.0"; | ||
var version = "15.2.1"; | ||
var peerDependencies = { | ||
@@ -25,14 +25,13 @@ rollup: "^2.78.0||^3.0.0" | ||
const stat = util.promisify(fs.stat); | ||
async function fileExists(filePath) { | ||
try { | ||
const res = await stat(filePath); | ||
return res.isFile(); | ||
} catch { | ||
return false; | ||
} | ||
try { | ||
const res = await stat(filePath); | ||
return res.isFile(); | ||
} | ||
catch { | ||
return false; | ||
} | ||
} | ||
async function resolveSymlink(path) { | ||
return (await fileExists(path)) ? realpath(path) : path; | ||
return (await fileExists(path)) ? realpath(path) : path; | ||
} | ||
@@ -313,301 +312,370 @@ | ||
/* eslint-disable no-await-in-loop */ | ||
function isModuleDir(current, moduleDirs) { | ||
return moduleDirs.some((dir) => current.endsWith(dir)); | ||
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 }; | ||
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, '..'); | ||
} | ||
current = path.resolve(current, '..'); | ||
} | ||
return null; | ||
return null; | ||
} | ||
function isUrl(str) { | ||
try { | ||
return !!new URL(str); | ||
} catch (_) { | ||
return false; | ||
} | ||
try { | ||
return !!new URL(str); | ||
} | ||
catch (_) { | ||
return false; | ||
} | ||
} | ||
/** | ||
* Conditions is an export object where all keys are conditions like 'node' (aka do not with '.') | ||
*/ | ||
function isConditions(exports) { | ||
return typeof exports === 'object' && Object.keys(exports).every((k) => !k.startsWith('.')); | ||
return typeof exports === 'object' && Object.keys(exports).every((k) => !k.startsWith('.')); | ||
} | ||
/** | ||
* Mappings is an export object where all keys start with '. | ||
*/ | ||
function isMappings(exports) { | ||
return typeof exports === 'object' && !isConditions(exports); | ||
return typeof exports === 'object' && !isConditions(exports); | ||
} | ||
/** | ||
* Check for mixed exports, which are exports where some keys start with '.' and some do not | ||
*/ | ||
function isMixedExports(exports) { | ||
const keys = Object.keys(exports); | ||
return keys.some((k) => k.startsWith('.')) && keys.some((k) => !k.startsWith('.')); | ||
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}`; | ||
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}` : ''}`; | ||
function createErrorMsg(context, reason, isImports) { | ||
const { importSpecifier, importer, pkgJsonPath } = context; | ||
const base = createBaseErrorMsg(importSpecifier, importer); | ||
const field = isImports ? 'imports' : 'exports'; | ||
return `${base} using ${field} defined in ${pkgJsonPath}.${reason ? ` ${reason}` : ''}`; | ||
} | ||
class ResolveError extends Error {} | ||
class ResolveError extends Error { | ||
} | ||
class InvalidConfigurationError extends ResolveError { | ||
constructor(context, reason) { | ||
super(createErrorMsg(context, `Invalid "exports" field. ${reason}`)); | ||
} | ||
constructor(context, reason) { | ||
super(createErrorMsg(context, `Invalid "exports" field. ${reason}`)); | ||
} | ||
} | ||
class InvalidModuleSpecifierError extends ResolveError { | ||
constructor(context, internal, reason) { | ||
super(createErrorMsg(context, reason, internal)); | ||
} | ||
constructor(context, isImports, reason) { | ||
super(createErrorMsg(context, reason, isImports)); | ||
} | ||
} | ||
class InvalidPackageTargetError extends ResolveError { | ||
constructor(context, reason) { | ||
super(createErrorMsg(context, reason)); | ||
} | ||
constructor(context, reason) { | ||
super(createErrorMsg(context, reason)); | ||
} | ||
} | ||
/* eslint-disable no-await-in-loop, no-undefined */ | ||
/** | ||
* Check for invalid path segments | ||
*/ | ||
function includesInvalidSegments(pathSegments, moduleDirs) { | ||
return pathSegments | ||
.split('/') | ||
.slice(1) | ||
.some((t) => ['.', '..', ...moduleDirs].includes(t)); | ||
const invalidSegments = ['', '.', '..', ...moduleDirs]; | ||
// contains any "", ".", "..", or "node_modules" segments, including percent encoded variants | ||
return pathSegments.some((v) => invalidSegments.includes(v) || invalidSegments.includes(decodeURI(v))); | ||
} | ||
async function resolvePackageTarget(context, { target, subpath, pattern, internal }) { | ||
if (typeof target === 'string') { | ||
if (!pattern && subpath.length > 0 && !target.endsWith('/')) { | ||
throw new InvalidModuleSpecifierError(context); | ||
async function resolvePackageTarget(context, { target, patternMatch, isImports }) { | ||
// If target is a String, then | ||
if (typeof target === 'string') { | ||
// If target does not start with "./", then | ||
if (!target.startsWith('./')) { | ||
// If isImports is false, or if target starts with "../" or "/", or if target is a valid URL, then | ||
if (!isImports || ['/', '../'].some((p) => target.startsWith(p)) || isUrl(target)) { | ||
// Throw an Invalid Package Target error. | ||
throw new InvalidPackageTargetError(context, `Invalid mapping: "${target}".`); | ||
} | ||
// If patternMatch is a String, then | ||
if (typeof patternMatch === 'string') { | ||
// Return PACKAGE_RESOLVE(target with every instance of "*" replaced by patternMatch, packageURL + "/") | ||
const result = await context.resolveId(target.replace(/\*/g, patternMatch), context.pkgURL.href); | ||
return result ? url.pathToFileURL(result.location).href : null; | ||
} | ||
// Return PACKAGE_RESOLVE(target, packageURL + "/"). | ||
const result = await context.resolveId(target, context.pkgURL.href); | ||
return result ? url.pathToFileURL(result.location).href : null; | ||
} | ||
// TODO: Drop if we do not support Node <= 16 anymore | ||
// This behavior was removed in Node 17 (deprecated in Node 14), see DEP0148 | ||
if (context.allowExportsFolderMapping) { | ||
target = target.replace(/\/$/, '/*'); | ||
} | ||
// If target split on "/" or "\" | ||
{ | ||
const pathSegments = target.split(/\/|\\/); | ||
// after the first "." segment | ||
const firstDot = pathSegments.indexOf('.'); | ||
firstDot !== -1 && pathSegments.slice(firstDot); | ||
if (firstDot !== -1 && | ||
firstDot < pathSegments.length - 1 && | ||
includesInvalidSegments(pathSegments.slice(firstDot + 1), context.moduleDirs)) { | ||
throw new InvalidPackageTargetError(context, `Invalid mapping: "${target}".`); | ||
} | ||
} | ||
// Let resolvedTarget be the URL resolution of the concatenation of packageURL and target. | ||
const resolvedTarget = new URL(target, context.pkgURL); | ||
// Assert: resolvedTarget is contained in packageURL. | ||
if (!resolvedTarget.href.startsWith(context.pkgURL.href)) { | ||
throw new InvalidPackageTargetError(context, `Resolved to ${resolvedTarget.href} which is outside package ${context.pkgURL.href}`); | ||
} | ||
// If patternMatch is null, then | ||
if (!patternMatch) { | ||
// Return resolvedTarget. | ||
return resolvedTarget; | ||
} | ||
// If patternMatch split on "/" or "\" contains invalid segments | ||
if (includesInvalidSegments(patternMatch.split(/\/|\\/), context.moduleDirs)) { | ||
// throw an Invalid Module Specifier error. | ||
throw new InvalidModuleSpecifierError(context); | ||
} | ||
// Return the URL resolution of resolvedTarget with every instance of "*" replaced with patternMatch. | ||
return resolvedTarget.href.replace(/\*/g, patternMatch); | ||
} | ||
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).href : null; | ||
// Otherwise, if target is an Array, then | ||
if (Array.isArray(target)) { | ||
// If _target.length is zero, return null. | ||
if (target.length === 0) { | ||
return null; | ||
} | ||
const result = await context.resolveId(`${target}${subpath}`, context.pkgURL.href); | ||
return result ? url.pathToFileURL(result.location).href : null; | ||
} | ||
throw new InvalidPackageTargetError(context, `Invalid mapping: "${target}".`); | ||
let lastError = null; | ||
// For each item in target, do | ||
for (const item of target) { | ||
// Let resolved be the result of PACKAGE_TARGET_RESOLVE of the item | ||
// continuing the loop on any Invalid Package Target error. | ||
try { | ||
const resolved = await resolvePackageTarget(context, { | ||
target: item, | ||
patternMatch, | ||
isImports | ||
}); | ||
// If resolved is undefined, continue the loop. | ||
// Else Return resolved. | ||
if (resolved !== undefined) { | ||
return resolved; | ||
} | ||
} | ||
catch (error) { | ||
if (!(error instanceof InvalidPackageTargetError)) { | ||
throw error; | ||
} | ||
else { | ||
lastError = error; | ||
} | ||
} | ||
} | ||
// Return or throw the last fallback resolution null return or error | ||
if (lastError) { | ||
throw lastError; | ||
} | ||
return null; | ||
} | ||
if (includesInvalidSegments(target, context.moduleDirs)) { | ||
throw new InvalidPackageTargetError(context, `Invalid mapping: "${target}".`); | ||
// Otherwise, if target is a non-null Object, then | ||
if (target && typeof target === 'object') { | ||
// For each property of target | ||
for (const [key, value] of Object.entries(target)) { | ||
// If exports contains any index property keys, as defined in ECMA-262 6.1.7 Array Index, throw an Invalid Package Configuration error. | ||
// TODO: We do not check if the key is a number here... | ||
// If key equals "default" or conditions contains an entry for the key, then | ||
if (key === 'default' || context.conditions.includes(key)) { | ||
// Let targetValue be the value of the property in target. | ||
// Let resolved be the result of PACKAGE_TARGET_RESOLVE of the targetValue | ||
const resolved = await resolvePackageTarget(context, { | ||
target: value, | ||
patternMatch, | ||
isImports | ||
}); | ||
// If resolved is equal to undefined, continue the loop. | ||
// Return resolved. | ||
if (resolved !== undefined) { | ||
return resolved; | ||
} | ||
} | ||
} | ||
// Return undefined. | ||
return undefined; | ||
} | ||
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}` | ||
); | ||
// Otherwise, if target is null, return null. | ||
if (target === null) { | ||
return null; | ||
} | ||
// Otherwise throw an Invalid Package Target error. | ||
throw new InvalidPackageTargetError(context, `Invalid exports field.`); | ||
} | ||
if (includesInvalidSegments(subpath, context.moduleDirs)) { | ||
throw new InvalidModuleSpecifierError(context); | ||
/* eslint-disable no-await-in-loop */ | ||
/** | ||
* Implementation of Node's `PATTERN_KEY_COMPARE` function | ||
*/ | ||
function nodePatternKeyCompare(keyA, keyB) { | ||
// Let baseLengthA be the index of "*" in keyA plus one, if keyA contains "*", or the length of keyA otherwise. | ||
const baseLengthA = keyA.includes('*') ? keyA.indexOf('*') + 1 : keyA.length; | ||
// Let baseLengthB be the index of "*" in keyB plus one, if keyB contains "*", or the length of keyB otherwise. | ||
const baseLengthB = keyB.includes('*') ? keyB.indexOf('*') + 1 : keyB.length; | ||
// if baseLengthA is greater, return -1, if lower 1 | ||
const rval = baseLengthB - baseLengthA; | ||
if (rval !== 0) | ||
return rval; | ||
// If keyA does not contain "*", return 1. | ||
if (!keyA.includes('*')) | ||
return 1; | ||
// If keyB does not contain "*", return -1. | ||
if (!keyB.includes('*')) | ||
return -1; | ||
// If the length of keyA is greater than the length of keyB, return -1. | ||
// If the length of keyB is greater than the length of keyA, return 1. | ||
// Else Return 0. | ||
return keyB.length - keyA.length; | ||
} | ||
async function resolvePackageImportsExports(context, { matchKey, matchObj, isImports }) { | ||
// If matchKey is a key of matchObj and does not contain "*", then | ||
if (!matchKey.includes('*') && matchKey in matchObj) { | ||
// Let target be the value of matchObj[matchKey]. | ||
const target = matchObj[matchKey]; | ||
// Return the result of PACKAGE_TARGET_RESOLVE(packageURL, target, null, isImports, conditions). | ||
const resolved = await resolvePackageTarget(context, { target, patternMatch: '', isImports }); | ||
return resolved; | ||
} | ||
// Let expansionKeys be the list of keys of matchObj containing only a single "*" | ||
const expansionKeys = Object.keys(matchObj) | ||
// Assert: ends with "/" or contains only a single "*". | ||
.filter((k) => k.endsWith('/') || k.includes('*')) | ||
// sorted by the sorting function PATTERN_KEY_COMPARE which orders in descending order of specificity. | ||
.sort(nodePatternKeyCompare); | ||
// For each key expansionKey in expansionKeys, do | ||
for (const expansionKey of expansionKeys) { | ||
const indexOfAsterisk = expansionKey.indexOf('*'); | ||
// Let patternBase be the substring of expansionKey up to but excluding the first "*" character. | ||
const patternBase = indexOfAsterisk === -1 ? expansionKey : expansionKey.substring(0, indexOfAsterisk); | ||
// If matchKey starts with but is not equal to patternBase, then | ||
if (matchKey.startsWith(patternBase) && matchKey !== patternBase) { | ||
// Let patternTrailer be the substring of expansionKey from the index after the first "*" character. | ||
const patternTrailer = indexOfAsterisk !== -1 ? expansionKey.substring(indexOfAsterisk + 1) : ''; | ||
// If patternTrailer has zero length, | ||
if (patternTrailer.length === 0 || | ||
// or if matchKey ends with patternTrailer and the length of matchKey is greater than or equal to the length of expansionKey, then | ||
(matchKey.endsWith(patternTrailer) && matchKey.length >= expansionKey.length)) { | ||
// Let target be the value of matchObj[expansionKey]. | ||
const target = matchObj[expansionKey]; | ||
// Let patternMatch be the substring of matchKey starting at the index of the length of patternBase up to the length | ||
// of matchKey minus the length of patternTrailer. | ||
const patternMatch = matchKey.substring(patternBase.length, matchKey.length - patternTrailer.length); | ||
// Return the result of PACKAGE_TARGET_RESOLVE | ||
const resolved = await resolvePackageTarget(context, { | ||
target, | ||
patternMatch, | ||
isImports | ||
}); | ||
return resolved; | ||
} | ||
} | ||
} | ||
throw new InvalidModuleSpecifierError(context, isImports); | ||
} | ||
if (pattern) { | ||
return resolvedTarget.href.replace(/\*/g, subpath); | ||
/** | ||
* Implementation of PACKAGE_EXPORTS_RESOLVE | ||
*/ | ||
async function resolvePackageExports(context, subpath, exports) { | ||
// If exports is an Object with both a key starting with "." and a key not starting with "." | ||
if (isMixedExports(exports)) { | ||
// throw an Invalid Package Configuration error. | ||
throw new InvalidConfigurationError(context, 'All keys must either start with ./, or without one.'); | ||
} | ||
return new URL(subpath, resolvedTarget).href; | ||
} | ||
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; | ||
// If subpath is equal to ".", then | ||
if (subpath === '.') { | ||
// Let mainExport be undefined. | ||
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)) { | ||
// Set mainExport to exports | ||
mainExport = exports; | ||
// Otherwise if exports is an Object containing a "." property, then | ||
} | ||
} catch (error) { | ||
if (!(error instanceof InvalidPackageTargetError)) { | ||
throw error; | ||
} else { | ||
lastError = error; | ||
else if (isMappings(exports)) { | ||
// Set mainExport to exports["."] | ||
mainExport = exports['.']; | ||
} | ||
} | ||
// If mainExport is not undefined, then | ||
if (mainExport) { | ||
// Let resolved be the result of PACKAGE_TARGET_RESOLVE with target = mainExport | ||
const resolved = await resolvePackageTarget(context, { | ||
target: mainExport, | ||
patternMatch: '', | ||
isImports: false | ||
}); | ||
// If resolved is not null or undefined, return resolved. | ||
if (resolved) { | ||
return resolved; | ||
} | ||
} | ||
// Otherwise, if exports is an Object and all keys of exports start with ".", then | ||
} | ||
if (lastError) { | ||
throw lastError; | ||
} | ||
return null; | ||
} | ||
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 | ||
else if (isMappings(exports)) { | ||
// Let resolved be the result of PACKAGE_IMPORTS_EXPORTS_RESOLVE | ||
const resolvedMatch = await resolvePackageImportsExports(context, { | ||
matchKey: subpath, | ||
matchObj: exports, | ||
isImports: false | ||
}); | ||
// return if defined or null, but not undefined | ||
if (resolved !== undefined) { | ||
return resolved; | ||
// If resolved is not null or undefined, return resolved. | ||
if (resolvedMatch) { | ||
return resolvedMatch; | ||
} | ||
} | ||
} | ||
return undefined; | ||
} | ||
if (target === null) { | ||
return null; | ||
} | ||
throw new InvalidPackageTargetError(context, `Invalid exports field.`); | ||
// Throw a Package Path Not Exported error. | ||
throw new InvalidModuleSpecifierError(context); | ||
} | ||
/* 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; | ||
} | ||
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 | ||
}); | ||
return resolved; | ||
async function resolvePackageImports({ importSpecifier, importer, moduleDirs, conditions, resolveId }) { | ||
const result = await findPackageJson(importer, moduleDirs); | ||
if (!result) { | ||
throw new Error(`${createBaseErrorMsg(importSpecifier, importer)}. Could not find a parent package.json.`); | ||
} | ||
if (matchKey.startsWith(expansionKey)) { | ||
const target = matchObj[expansionKey]; | ||
const subpath = matchKey.substring(expansionKey.length); | ||
const resolved = await resolvePackageTarget(context, { target, subpath, internal }); | ||
return resolved; | ||
const { pkgPath, pkgJsonPath, pkgJson } = result; | ||
const pkgURL = url.pathToFileURL(`${pkgPath}/`); | ||
const context = { | ||
importer, | ||
importSpecifier, | ||
moduleDirs, | ||
pkgURL, | ||
pkgJsonPath, | ||
conditions, | ||
resolveId | ||
}; | ||
// Assert: specifier begins with "#". | ||
if (!importSpecifier.startsWith('#')) { | ||
throw new InvalidModuleSpecifierError(context, true, 'Invalid import specifier.'); | ||
} | ||
} | ||
throw new InvalidModuleSpecifierError(context, internal); | ||
} | ||
async function resolvePackageExports(context, subpath, exports) { | ||
if (isMixedExports(exports)) { | ||
throw new InvalidConfigurationError( | ||
context, | ||
'All keys must either start with ./, or without one.' | ||
); | ||
} | ||
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 specifier is exactly equal to "#" or starts with "#/", then | ||
if (importSpecifier === '#' || importSpecifier.startsWith('#/')) { | ||
// Throw an Invalid Module Specifier error. | ||
throw new InvalidModuleSpecifierError(context, true, 'Invalid import specifier.'); | ||
} | ||
if (mainExport) { | ||
const resolved = await resolvePackageTarget(context, { target: mainExport, subpath: '' }); | ||
if (resolved) { | ||
return resolved; | ||
} | ||
const { imports } = pkgJson; | ||
if (!imports) { | ||
throw new InvalidModuleSpecifierError(context, true); | ||
} | ||
} else if (isMappings(exports)) { | ||
const resolvedMatch = await resolvePackageImportsExports(context, { | ||
matchKey: subpath, | ||
matchObj: exports | ||
// Let packageURL be the result of LOOKUP_PACKAGE_SCOPE(parentURL). | ||
// If packageURL is not null, then | ||
return resolvePackageImportsExports(context, { | ||
matchKey: importSpecifier, | ||
matchObj: imports, | ||
isImports: true | ||
}); | ||
if (resolvedMatch) { | ||
return resolvedMatch; | ||
} | ||
} | ||
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, | ||
conditions, | ||
resolveId | ||
}; | ||
const { imports } = pkgJson; | ||
if (!imports) { | ||
throw new InvalidModuleSpecifierError(context, true); | ||
} | ||
if (importSpecifier === '#' || importSpecifier.startsWith('#/')) { | ||
throw new InvalidModuleSpecifierError(context, true, 'Invalid import specifier.'); | ||
} | ||
return resolvePackageImportsExports(context, { | ||
matchKey: importSpecifier, | ||
matchObj: imports, | ||
internal: true | ||
}); | ||
} | ||
const resolveImportPath = util.promisify(resolve); | ||
@@ -715,3 +783,4 @@ const readFile = util.promisify(fs.readFile); | ||
rootDir, | ||
ignoreSideEffectsForRoot | ||
ignoreSideEffectsForRoot, | ||
allowExportsFolderMapping | ||
}) { | ||
@@ -805,2 +874,3 @@ if (importSpecifier.startsWith('#')) { | ||
pkgJsonPath, | ||
allowExportsFolderMapping, | ||
conditions: exportConditions | ||
@@ -886,3 +956,4 @@ }; | ||
rootDir, | ||
ignoreSideEffectsForRoot | ||
ignoreSideEffectsForRoot, | ||
allowExportsFolderMapping | ||
}) { | ||
@@ -903,3 +974,4 @@ try { | ||
rootDir, | ||
ignoreSideEffectsForRoot | ||
ignoreSideEffectsForRoot, | ||
allowExportsFolderMapping | ||
}); | ||
@@ -991,3 +1063,5 @@ if (exportMapRes) return exportMapRes; | ||
moduleDirectories: ['node_modules'], | ||
ignoreSideEffectsForRoot: false | ||
ignoreSideEffectsForRoot: false, | ||
// TODO: set to false in next major release or remove | ||
allowExportsFolderMapping: true | ||
}; | ||
@@ -1138,3 +1212,4 @@ const DEFAULTS = deepFreeze(deepMerge({}, defaults)); | ||
rootDir, | ||
ignoreSideEffectsForRoot | ||
ignoreSideEffectsForRoot, | ||
allowExportsFolderMapping: options.allowExportsFolderMapping | ||
}); | ||
@@ -1141,0 +1216,0 @@ |
@@ -11,3 +11,3 @@ import path, { dirname, resolve, extname, normalize, sep } from 'path'; | ||
var version = "15.2.0"; | ||
var version = "15.2.1"; | ||
var peerDependencies = { | ||
@@ -21,14 +21,13 @@ rollup: "^2.78.0||^3.0.0" | ||
const stat = promisify(fs.stat); | ||
async function fileExists(filePath) { | ||
try { | ||
const res = await stat(filePath); | ||
return res.isFile(); | ||
} catch { | ||
return false; | ||
} | ||
try { | ||
const res = await stat(filePath); | ||
return res.isFile(); | ||
} | ||
catch { | ||
return false; | ||
} | ||
} | ||
async function resolveSymlink(path) { | ||
return (await fileExists(path)) ? realpath(path) : path; | ||
return (await fileExists(path)) ? realpath(path) : path; | ||
} | ||
@@ -309,301 +308,370 @@ | ||
/* eslint-disable no-await-in-loop */ | ||
function isModuleDir(current, moduleDirs) { | ||
return moduleDirs.some((dir) => current.endsWith(dir)); | ||
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 }; | ||
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, '..'); | ||
} | ||
current = path.resolve(current, '..'); | ||
} | ||
return null; | ||
return null; | ||
} | ||
function isUrl(str) { | ||
try { | ||
return !!new URL(str); | ||
} catch (_) { | ||
return false; | ||
} | ||
try { | ||
return !!new URL(str); | ||
} | ||
catch (_) { | ||
return false; | ||
} | ||
} | ||
/** | ||
* Conditions is an export object where all keys are conditions like 'node' (aka do not with '.') | ||
*/ | ||
function isConditions(exports) { | ||
return typeof exports === 'object' && Object.keys(exports).every((k) => !k.startsWith('.')); | ||
return typeof exports === 'object' && Object.keys(exports).every((k) => !k.startsWith('.')); | ||
} | ||
/** | ||
* Mappings is an export object where all keys start with '. | ||
*/ | ||
function isMappings(exports) { | ||
return typeof exports === 'object' && !isConditions(exports); | ||
return typeof exports === 'object' && !isConditions(exports); | ||
} | ||
/** | ||
* Check for mixed exports, which are exports where some keys start with '.' and some do not | ||
*/ | ||
function isMixedExports(exports) { | ||
const keys = Object.keys(exports); | ||
return keys.some((k) => k.startsWith('.')) && keys.some((k) => !k.startsWith('.')); | ||
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}`; | ||
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}` : ''}`; | ||
function createErrorMsg(context, reason, isImports) { | ||
const { importSpecifier, importer, pkgJsonPath } = context; | ||
const base = createBaseErrorMsg(importSpecifier, importer); | ||
const field = isImports ? 'imports' : 'exports'; | ||
return `${base} using ${field} defined in ${pkgJsonPath}.${reason ? ` ${reason}` : ''}`; | ||
} | ||
class ResolveError extends Error {} | ||
class ResolveError extends Error { | ||
} | ||
class InvalidConfigurationError extends ResolveError { | ||
constructor(context, reason) { | ||
super(createErrorMsg(context, `Invalid "exports" field. ${reason}`)); | ||
} | ||
constructor(context, reason) { | ||
super(createErrorMsg(context, `Invalid "exports" field. ${reason}`)); | ||
} | ||
} | ||
class InvalidModuleSpecifierError extends ResolveError { | ||
constructor(context, internal, reason) { | ||
super(createErrorMsg(context, reason, internal)); | ||
} | ||
constructor(context, isImports, reason) { | ||
super(createErrorMsg(context, reason, isImports)); | ||
} | ||
} | ||
class InvalidPackageTargetError extends ResolveError { | ||
constructor(context, reason) { | ||
super(createErrorMsg(context, reason)); | ||
} | ||
constructor(context, reason) { | ||
super(createErrorMsg(context, reason)); | ||
} | ||
} | ||
/* eslint-disable no-await-in-loop, no-undefined */ | ||
/** | ||
* Check for invalid path segments | ||
*/ | ||
function includesInvalidSegments(pathSegments, moduleDirs) { | ||
return pathSegments | ||
.split('/') | ||
.slice(1) | ||
.some((t) => ['.', '..', ...moduleDirs].includes(t)); | ||
const invalidSegments = ['', '.', '..', ...moduleDirs]; | ||
// contains any "", ".", "..", or "node_modules" segments, including percent encoded variants | ||
return pathSegments.some((v) => invalidSegments.includes(v) || invalidSegments.includes(decodeURI(v))); | ||
} | ||
async function resolvePackageTarget(context, { target, subpath, pattern, internal }) { | ||
if (typeof target === 'string') { | ||
if (!pattern && subpath.length > 0 && !target.endsWith('/')) { | ||
throw new InvalidModuleSpecifierError(context); | ||
async function resolvePackageTarget(context, { target, patternMatch, isImports }) { | ||
// If target is a String, then | ||
if (typeof target === 'string') { | ||
// If target does not start with "./", then | ||
if (!target.startsWith('./')) { | ||
// If isImports is false, or if target starts with "../" or "/", or if target is a valid URL, then | ||
if (!isImports || ['/', '../'].some((p) => target.startsWith(p)) || isUrl(target)) { | ||
// Throw an Invalid Package Target error. | ||
throw new InvalidPackageTargetError(context, `Invalid mapping: "${target}".`); | ||
} | ||
// If patternMatch is a String, then | ||
if (typeof patternMatch === 'string') { | ||
// Return PACKAGE_RESOLVE(target with every instance of "*" replaced by patternMatch, packageURL + "/") | ||
const result = await context.resolveId(target.replace(/\*/g, patternMatch), context.pkgURL.href); | ||
return result ? pathToFileURL(result.location).href : null; | ||
} | ||
// Return PACKAGE_RESOLVE(target, packageURL + "/"). | ||
const result = await context.resolveId(target, context.pkgURL.href); | ||
return result ? pathToFileURL(result.location).href : null; | ||
} | ||
// TODO: Drop if we do not support Node <= 16 anymore | ||
// This behavior was removed in Node 17 (deprecated in Node 14), see DEP0148 | ||
if (context.allowExportsFolderMapping) { | ||
target = target.replace(/\/$/, '/*'); | ||
} | ||
// If target split on "/" or "\" | ||
{ | ||
const pathSegments = target.split(/\/|\\/); | ||
// after the first "." segment | ||
const firstDot = pathSegments.indexOf('.'); | ||
firstDot !== -1 && pathSegments.slice(firstDot); | ||
if (firstDot !== -1 && | ||
firstDot < pathSegments.length - 1 && | ||
includesInvalidSegments(pathSegments.slice(firstDot + 1), context.moduleDirs)) { | ||
throw new InvalidPackageTargetError(context, `Invalid mapping: "${target}".`); | ||
} | ||
} | ||
// Let resolvedTarget be the URL resolution of the concatenation of packageURL and target. | ||
const resolvedTarget = new URL(target, context.pkgURL); | ||
// Assert: resolvedTarget is contained in packageURL. | ||
if (!resolvedTarget.href.startsWith(context.pkgURL.href)) { | ||
throw new InvalidPackageTargetError(context, `Resolved to ${resolvedTarget.href} which is outside package ${context.pkgURL.href}`); | ||
} | ||
// If patternMatch is null, then | ||
if (!patternMatch) { | ||
// Return resolvedTarget. | ||
return resolvedTarget; | ||
} | ||
// If patternMatch split on "/" or "\" contains invalid segments | ||
if (includesInvalidSegments(patternMatch.split(/\/|\\/), context.moduleDirs)) { | ||
// throw an Invalid Module Specifier error. | ||
throw new InvalidModuleSpecifierError(context); | ||
} | ||
// Return the URL resolution of resolvedTarget with every instance of "*" replaced with patternMatch. | ||
return resolvedTarget.href.replace(/\*/g, patternMatch); | ||
} | ||
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).href : null; | ||
// Otherwise, if target is an Array, then | ||
if (Array.isArray(target)) { | ||
// If _target.length is zero, return null. | ||
if (target.length === 0) { | ||
return null; | ||
} | ||
const result = await context.resolveId(`${target}${subpath}`, context.pkgURL.href); | ||
return result ? pathToFileURL(result.location).href : null; | ||
} | ||
throw new InvalidPackageTargetError(context, `Invalid mapping: "${target}".`); | ||
let lastError = null; | ||
// For each item in target, do | ||
for (const item of target) { | ||
// Let resolved be the result of PACKAGE_TARGET_RESOLVE of the item | ||
// continuing the loop on any Invalid Package Target error. | ||
try { | ||
const resolved = await resolvePackageTarget(context, { | ||
target: item, | ||
patternMatch, | ||
isImports | ||
}); | ||
// If resolved is undefined, continue the loop. | ||
// Else Return resolved. | ||
if (resolved !== undefined) { | ||
return resolved; | ||
} | ||
} | ||
catch (error) { | ||
if (!(error instanceof InvalidPackageTargetError)) { | ||
throw error; | ||
} | ||
else { | ||
lastError = error; | ||
} | ||
} | ||
} | ||
// Return or throw the last fallback resolution null return or error | ||
if (lastError) { | ||
throw lastError; | ||
} | ||
return null; | ||
} | ||
if (includesInvalidSegments(target, context.moduleDirs)) { | ||
throw new InvalidPackageTargetError(context, `Invalid mapping: "${target}".`); | ||
// Otherwise, if target is a non-null Object, then | ||
if (target && typeof target === 'object') { | ||
// For each property of target | ||
for (const [key, value] of Object.entries(target)) { | ||
// If exports contains any index property keys, as defined in ECMA-262 6.1.7 Array Index, throw an Invalid Package Configuration error. | ||
// TODO: We do not check if the key is a number here... | ||
// If key equals "default" or conditions contains an entry for the key, then | ||
if (key === 'default' || context.conditions.includes(key)) { | ||
// Let targetValue be the value of the property in target. | ||
// Let resolved be the result of PACKAGE_TARGET_RESOLVE of the targetValue | ||
const resolved = await resolvePackageTarget(context, { | ||
target: value, | ||
patternMatch, | ||
isImports | ||
}); | ||
// If resolved is equal to undefined, continue the loop. | ||
// Return resolved. | ||
if (resolved !== undefined) { | ||
return resolved; | ||
} | ||
} | ||
} | ||
// Return undefined. | ||
return undefined; | ||
} | ||
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}` | ||
); | ||
// Otherwise, if target is null, return null. | ||
if (target === null) { | ||
return null; | ||
} | ||
// Otherwise throw an Invalid Package Target error. | ||
throw new InvalidPackageTargetError(context, `Invalid exports field.`); | ||
} | ||
if (includesInvalidSegments(subpath, context.moduleDirs)) { | ||
throw new InvalidModuleSpecifierError(context); | ||
/* eslint-disable no-await-in-loop */ | ||
/** | ||
* Implementation of Node's `PATTERN_KEY_COMPARE` function | ||
*/ | ||
function nodePatternKeyCompare(keyA, keyB) { | ||
// Let baseLengthA be the index of "*" in keyA plus one, if keyA contains "*", or the length of keyA otherwise. | ||
const baseLengthA = keyA.includes('*') ? keyA.indexOf('*') + 1 : keyA.length; | ||
// Let baseLengthB be the index of "*" in keyB plus one, if keyB contains "*", or the length of keyB otherwise. | ||
const baseLengthB = keyB.includes('*') ? keyB.indexOf('*') + 1 : keyB.length; | ||
// if baseLengthA is greater, return -1, if lower 1 | ||
const rval = baseLengthB - baseLengthA; | ||
if (rval !== 0) | ||
return rval; | ||
// If keyA does not contain "*", return 1. | ||
if (!keyA.includes('*')) | ||
return 1; | ||
// If keyB does not contain "*", return -1. | ||
if (!keyB.includes('*')) | ||
return -1; | ||
// If the length of keyA is greater than the length of keyB, return -1. | ||
// If the length of keyB is greater than the length of keyA, return 1. | ||
// Else Return 0. | ||
return keyB.length - keyA.length; | ||
} | ||
async function resolvePackageImportsExports(context, { matchKey, matchObj, isImports }) { | ||
// If matchKey is a key of matchObj and does not contain "*", then | ||
if (!matchKey.includes('*') && matchKey in matchObj) { | ||
// Let target be the value of matchObj[matchKey]. | ||
const target = matchObj[matchKey]; | ||
// Return the result of PACKAGE_TARGET_RESOLVE(packageURL, target, null, isImports, conditions). | ||
const resolved = await resolvePackageTarget(context, { target, patternMatch: '', isImports }); | ||
return resolved; | ||
} | ||
// Let expansionKeys be the list of keys of matchObj containing only a single "*" | ||
const expansionKeys = Object.keys(matchObj) | ||
// Assert: ends with "/" or contains only a single "*". | ||
.filter((k) => k.endsWith('/') || k.includes('*')) | ||
// sorted by the sorting function PATTERN_KEY_COMPARE which orders in descending order of specificity. | ||
.sort(nodePatternKeyCompare); | ||
// For each key expansionKey in expansionKeys, do | ||
for (const expansionKey of expansionKeys) { | ||
const indexOfAsterisk = expansionKey.indexOf('*'); | ||
// Let patternBase be the substring of expansionKey up to but excluding the first "*" character. | ||
const patternBase = indexOfAsterisk === -1 ? expansionKey : expansionKey.substring(0, indexOfAsterisk); | ||
// If matchKey starts with but is not equal to patternBase, then | ||
if (matchKey.startsWith(patternBase) && matchKey !== patternBase) { | ||
// Let patternTrailer be the substring of expansionKey from the index after the first "*" character. | ||
const patternTrailer = indexOfAsterisk !== -1 ? expansionKey.substring(indexOfAsterisk + 1) : ''; | ||
// If patternTrailer has zero length, | ||
if (patternTrailer.length === 0 || | ||
// or if matchKey ends with patternTrailer and the length of matchKey is greater than or equal to the length of expansionKey, then | ||
(matchKey.endsWith(patternTrailer) && matchKey.length >= expansionKey.length)) { | ||
// Let target be the value of matchObj[expansionKey]. | ||
const target = matchObj[expansionKey]; | ||
// Let patternMatch be the substring of matchKey starting at the index of the length of patternBase up to the length | ||
// of matchKey minus the length of patternTrailer. | ||
const patternMatch = matchKey.substring(patternBase.length, matchKey.length - patternTrailer.length); | ||
// Return the result of PACKAGE_TARGET_RESOLVE | ||
const resolved = await resolvePackageTarget(context, { | ||
target, | ||
patternMatch, | ||
isImports | ||
}); | ||
return resolved; | ||
} | ||
} | ||
} | ||
throw new InvalidModuleSpecifierError(context, isImports); | ||
} | ||
if (pattern) { | ||
return resolvedTarget.href.replace(/\*/g, subpath); | ||
/** | ||
* Implementation of PACKAGE_EXPORTS_RESOLVE | ||
*/ | ||
async function resolvePackageExports(context, subpath, exports) { | ||
// If exports is an Object with both a key starting with "." and a key not starting with "." | ||
if (isMixedExports(exports)) { | ||
// throw an Invalid Package Configuration error. | ||
throw new InvalidConfigurationError(context, 'All keys must either start with ./, or without one.'); | ||
} | ||
return new URL(subpath, resolvedTarget).href; | ||
} | ||
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; | ||
// If subpath is equal to ".", then | ||
if (subpath === '.') { | ||
// Let mainExport be undefined. | ||
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)) { | ||
// Set mainExport to exports | ||
mainExport = exports; | ||
// Otherwise if exports is an Object containing a "." property, then | ||
} | ||
} catch (error) { | ||
if (!(error instanceof InvalidPackageTargetError)) { | ||
throw error; | ||
} else { | ||
lastError = error; | ||
else if (isMappings(exports)) { | ||
// Set mainExport to exports["."] | ||
mainExport = exports['.']; | ||
} | ||
} | ||
// If mainExport is not undefined, then | ||
if (mainExport) { | ||
// Let resolved be the result of PACKAGE_TARGET_RESOLVE with target = mainExport | ||
const resolved = await resolvePackageTarget(context, { | ||
target: mainExport, | ||
patternMatch: '', | ||
isImports: false | ||
}); | ||
// If resolved is not null or undefined, return resolved. | ||
if (resolved) { | ||
return resolved; | ||
} | ||
} | ||
// Otherwise, if exports is an Object and all keys of exports start with ".", then | ||
} | ||
if (lastError) { | ||
throw lastError; | ||
} | ||
return null; | ||
} | ||
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 | ||
else if (isMappings(exports)) { | ||
// Let resolved be the result of PACKAGE_IMPORTS_EXPORTS_RESOLVE | ||
const resolvedMatch = await resolvePackageImportsExports(context, { | ||
matchKey: subpath, | ||
matchObj: exports, | ||
isImports: false | ||
}); | ||
// return if defined or null, but not undefined | ||
if (resolved !== undefined) { | ||
return resolved; | ||
// If resolved is not null or undefined, return resolved. | ||
if (resolvedMatch) { | ||
return resolvedMatch; | ||
} | ||
} | ||
} | ||
return undefined; | ||
} | ||
if (target === null) { | ||
return null; | ||
} | ||
throw new InvalidPackageTargetError(context, `Invalid exports field.`); | ||
// Throw a Package Path Not Exported error. | ||
throw new InvalidModuleSpecifierError(context); | ||
} | ||
/* 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; | ||
} | ||
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 | ||
}); | ||
return resolved; | ||
async function resolvePackageImports({ importSpecifier, importer, moduleDirs, conditions, resolveId }) { | ||
const result = await findPackageJson(importer, moduleDirs); | ||
if (!result) { | ||
throw new Error(`${createBaseErrorMsg(importSpecifier, importer)}. Could not find a parent package.json.`); | ||
} | ||
if (matchKey.startsWith(expansionKey)) { | ||
const target = matchObj[expansionKey]; | ||
const subpath = matchKey.substring(expansionKey.length); | ||
const resolved = await resolvePackageTarget(context, { target, subpath, internal }); | ||
return resolved; | ||
const { pkgPath, pkgJsonPath, pkgJson } = result; | ||
const pkgURL = pathToFileURL(`${pkgPath}/`); | ||
const context = { | ||
importer, | ||
importSpecifier, | ||
moduleDirs, | ||
pkgURL, | ||
pkgJsonPath, | ||
conditions, | ||
resolveId | ||
}; | ||
// Assert: specifier begins with "#". | ||
if (!importSpecifier.startsWith('#')) { | ||
throw new InvalidModuleSpecifierError(context, true, 'Invalid import specifier.'); | ||
} | ||
} | ||
throw new InvalidModuleSpecifierError(context, internal); | ||
} | ||
async function resolvePackageExports(context, subpath, exports) { | ||
if (isMixedExports(exports)) { | ||
throw new InvalidConfigurationError( | ||
context, | ||
'All keys must either start with ./, or without one.' | ||
); | ||
} | ||
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 specifier is exactly equal to "#" or starts with "#/", then | ||
if (importSpecifier === '#' || importSpecifier.startsWith('#/')) { | ||
// Throw an Invalid Module Specifier error. | ||
throw new InvalidModuleSpecifierError(context, true, 'Invalid import specifier.'); | ||
} | ||
if (mainExport) { | ||
const resolved = await resolvePackageTarget(context, { target: mainExport, subpath: '' }); | ||
if (resolved) { | ||
return resolved; | ||
} | ||
const { imports } = pkgJson; | ||
if (!imports) { | ||
throw new InvalidModuleSpecifierError(context, true); | ||
} | ||
} else if (isMappings(exports)) { | ||
const resolvedMatch = await resolvePackageImportsExports(context, { | ||
matchKey: subpath, | ||
matchObj: exports | ||
// Let packageURL be the result of LOOKUP_PACKAGE_SCOPE(parentURL). | ||
// If packageURL is not null, then | ||
return resolvePackageImportsExports(context, { | ||
matchKey: importSpecifier, | ||
matchObj: imports, | ||
isImports: true | ||
}); | ||
if (resolvedMatch) { | ||
return resolvedMatch; | ||
} | ||
} | ||
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, | ||
conditions, | ||
resolveId | ||
}; | ||
const { imports } = pkgJson; | ||
if (!imports) { | ||
throw new InvalidModuleSpecifierError(context, true); | ||
} | ||
if (importSpecifier === '#' || importSpecifier.startsWith('#/')) { | ||
throw new InvalidModuleSpecifierError(context, true, 'Invalid import specifier.'); | ||
} | ||
return resolvePackageImportsExports(context, { | ||
matchKey: importSpecifier, | ||
matchObj: imports, | ||
internal: true | ||
}); | ||
} | ||
const resolveImportPath = promisify(resolve$1); | ||
@@ -711,3 +779,4 @@ const readFile = promisify(fs.readFile); | ||
rootDir, | ||
ignoreSideEffectsForRoot | ||
ignoreSideEffectsForRoot, | ||
allowExportsFolderMapping | ||
}) { | ||
@@ -801,2 +870,3 @@ if (importSpecifier.startsWith('#')) { | ||
pkgJsonPath, | ||
allowExportsFolderMapping, | ||
conditions: exportConditions | ||
@@ -882,3 +952,4 @@ }; | ||
rootDir, | ||
ignoreSideEffectsForRoot | ||
ignoreSideEffectsForRoot, | ||
allowExportsFolderMapping | ||
}) { | ||
@@ -899,3 +970,4 @@ try { | ||
rootDir, | ||
ignoreSideEffectsForRoot | ||
ignoreSideEffectsForRoot, | ||
allowExportsFolderMapping | ||
}); | ||
@@ -987,3 +1059,5 @@ if (exportMapRes) return exportMapRes; | ||
moduleDirectories: ['node_modules'], | ||
ignoreSideEffectsForRoot: false | ||
ignoreSideEffectsForRoot: false, | ||
// TODO: set to false in next major release or remove | ||
allowExportsFolderMapping: true | ||
}; | ||
@@ -1134,3 +1208,4 @@ const DEFAULTS = deepFreeze(deepMerge({}, defaults)); | ||
rootDir, | ||
ignoreSideEffectsForRoot | ||
ignoreSideEffectsForRoot, | ||
allowExportsFolderMapping: options.allowExportsFolderMapping | ||
}); | ||
@@ -1137,0 +1212,0 @@ |
{ | ||
"name": "@rollup/plugin-node-resolve", | ||
"version": "15.2.0", | ||
"version": "15.2.1", | ||
"publishConfig": { | ||
@@ -5,0 +5,0 @@ "access": "public" |
@@ -178,2 +178,27 @@ [npm]: https://img.shields.io/npm/v/@rollup/plugin-node-resolve | ||
### `allowExportsFolderMapping` | ||
Older Node versions supported exports mappings of folders like | ||
```json | ||
{ | ||
"exports": { | ||
"./foo/": "./dist/foo/" | ||
} | ||
} | ||
``` | ||
This was deprecated with Node 14 and removed in Node 17, instead it is recommended to use exports patterns like | ||
```json | ||
{ | ||
"exports": { | ||
"./foo/*": "./dist/foo/*" | ||
} | ||
} | ||
``` | ||
But for backwards compatibility this behavior is still supported by enabling the `allowExportsFolderMapping` (defaults to `true`). | ||
The default value might change in a futur major release. | ||
## Preserving symlinks | ||
@@ -180,0 +205,0 @@ |
@@ -99,2 +99,10 @@ import type { Plugin } from 'rollup'; | ||
rootDir?: string; | ||
/** | ||
* Allow folder mappings in package exports (trailing /) | ||
* This was deprecated in Node 14 and removed with Node 17, see DEP0148. | ||
* So this option might be changed to default to `false` in a future release. | ||
* @default true | ||
*/ | ||
allowExportsFolderMapping?: boolean; | ||
} | ||
@@ -101,0 +109,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
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
107511
2581
286