-
-
Notifications
You must be signed in to change notification settings - Fork 186
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[New] add support for the exports package.json attribute
- Loading branch information
Showing
23 changed files
with
759 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
var path = require('path'); | ||
var startsWith = require('string.prototype.startswith'); | ||
|
||
function makeError(code, message) { | ||
var error = new Error(message); | ||
error.code = code; | ||
return error; | ||
} | ||
|
||
function validateExports(exports, basePath) { | ||
var isConditional = true; | ||
|
||
if (typeof exports === 'object' && !Array.isArray(exports)) { | ||
var exportKeys = Object.keys(exports); | ||
|
||
for (var i = 0; i < exportKeys.length; i++) { | ||
var isKeyConditional = exportKeys[i][0] !== '.'; | ||
if (i === 0) { | ||
isConditional = isKeyConditional; | ||
} else if (isKeyConditional !== isConditional) { | ||
throw makeError('ERR_INVALID_PACKAGE_CONFIG', 'Invalid package config ' + basePath + path.sep + 'package.json, ' | ||
+ '"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.'); | ||
} | ||
} | ||
} | ||
|
||
if (isConditional) { | ||
return { '.': exports }; | ||
} else { | ||
return exports; | ||
} | ||
} | ||
|
||
function validateConditions(names) { | ||
// TODO If exports contains any index property keys, as defined in ECMA-262 6.1.7 Array Index, throw an Invalid Package Configuration error. | ||
return names; | ||
} | ||
|
||
// eslint-disable-next-line max-lines-per-function | ||
function resolvePackageTarget(packagePath, parent, key, target, subpath, internal, conditions) { | ||
if (typeof target === 'string') { | ||
var resolvedTarget = path.resolve(packagePath, target); | ||
var invalidTarget = false; | ||
|
||
if (!startsWith(target, './')) { | ||
if (!internal) { | ||
invalidTarget = true; | ||
} else if (!startsWith(target, '../') && !startsWith(target, '/')) { | ||
invalidTarget = true; | ||
} else { | ||
// TODO: imports need call package_resolve here | ||
} | ||
} | ||
|
||
var targetParts = target.split(/[\\/]/).slice(1); // slice to strip the leading '.' | ||
if (invalidTarget || targetParts.indexOf('node_modules') !== -1 || targetParts.indexOf('.') !== -1 || targetParts.indexOf('..') !== -1) { | ||
throw makeError('ERR_INVALID_PACKAGE_TARGET', 'Invalid "exports" target ' + JSON.stringify(target) | ||
+ ' defined for ' + key + ' in ' + packagePath + path.sep + 'package.json imported from ' + parent + '.'); | ||
} | ||
|
||
if (subpath !== '' && target[target.length - 1] !== '/') { | ||
throw makeError('ERR_INVALID_MODULE_SPECIFIER', 'Package subpath "' + subpath + '" is not a valid module request for ' | ||
+ 'the "exports" resolution of ' + packagePath + path.sep + 'package.json imported from ' + parent + '.'); | ||
} | ||
|
||
var resolved = path.normalize(resolvedTarget + subpath); | ||
var subpathParts = subpath.split(/[\\/]/); | ||
if (!startsWith(resolved, resolvedTarget) || subpathParts.indexOf('node_modules') !== -1 || subpathParts.indexOf('.') !== -1 || subpathParts.indexOf('..') !== -1) { | ||
throw makeError('ERR_INVALID_MODULE_SPECIFIER', 'Package subpath "' + subpath + '" is not a valid module request for ' | ||
+ 'the "exports" resolution of ' + packagePath + path.sep + 'package.json imported from ' + parent + '.'); | ||
} | ||
|
||
return resolved; | ||
} | ||
|
||
if (Array.isArray(target)) { | ||
if (target.length === 0) { | ||
throw makeError('ERR_PACKAGE_PATH_NOT_EXPORTED', key === '.' | ||
? 'No "exports" main resolved in ' + packagePath + path.sep + 'package.json.' | ||
: 'Package subpath ' + key + ' is not defined by "exports" in ' + packagePath + path.sep + 'package.json imported from ' + parent + '.'); | ||
} | ||
|
||
var lastError; | ||
for (var i = 0; i < target.length; i++) { | ||
try { | ||
return resolvePackageTarget(packagePath, parent, key, target[i], subpath, internal, conditions); | ||
} catch (e) { | ||
if (e && (e.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED' || e.code === 'ERR_INVALID_PACKAGE_TARGET')) { | ||
lastError = e; | ||
} else { | ||
throw e; | ||
} | ||
} | ||
} | ||
throw lastError; | ||
} | ||
|
||
if (target === null) { | ||
throw makeError('ERR_PACKAGE_PATH_NOT_EXPORTED', key === '.' | ||
? 'No "exports" main resolved in ' + packagePath + path.sep + 'package.json.' | ||
: 'Package subpath ' + key + ' is not defined by "exports" in ' + packagePath + path.sep + 'package.json imported from ' + parent + '.'); | ||
} | ||
|
||
if (typeof target !== 'object') { | ||
throw makeError('ERR_INVALID_PACKAGE_TARGET', 'Invalid "exports" target ' + JSON.stringify(target) | ||
+ ' defined for ' + key + ' in ' + packagePath + path.sep + 'package.json imported from ' + parent + '.'); | ||
} | ||
|
||
var exportedConditions = validateConditions(Object.keys(target)); | ||
|
||
for (i = 0; i < exportedConditions.length; i++) { | ||
var exportedCondition = exportedConditions[i]; | ||
if (exportedCondition === 'default' || conditions.indexOf(exportedCondition) !== -1) { | ||
try { | ||
return resolvePackageTarget( | ||
packagePath, | ||
parent, | ||
key, | ||
target[exportedCondition], | ||
subpath, | ||
internal, | ||
conditions | ||
); | ||
} catch (e) { | ||
if (!e || e.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') { | ||
throw e; | ||
} | ||
} | ||
} | ||
} | ||
|
||
throw makeError('ERR_PACKAGE_PATH_NOT_EXPORTED', key === '.' | ||
? 'No "exports" main resolved in ' + packagePath + path.sep + 'package.json.' | ||
: 'Package subpath ' + key + ' is not defined by "exports" in ' + packagePath + path.sep + 'package.json imported from ' + parent + '.'); | ||
} | ||
|
||
function resolveImportExport(packagePath, parent, matchObj, matchKey, isImports, conditions) { | ||
if (Object.prototype.hasOwnProperty.call(matchObj, matchKey) && matchKey[matchKey.length - 1] !== '*') { | ||
return { | ||
resolved: resolvePackageTarget(packagePath, parent, matchKey, matchObj[matchKey], '', isImports, conditions), | ||
exact: true | ||
}; | ||
} | ||
|
||
var longestMatchingExport = ''; | ||
var exportedPaths = Object.keys(matchObj); | ||
|
||
for (var i = 0; i < exportedPaths.length; i++) { | ||
var exportedPath = exportedPaths[i]; | ||
if (exportedPath[exportedPath.length - 1] === '/' && startsWith(matchKey, exportedPath) && exportedPath.length > longestMatchingExport.length) { | ||
longestMatchingExport = exportedPath; | ||
} | ||
} | ||
|
||
if (longestMatchingExport === '') { | ||
throw makeError('ERR_PACKAGE_PATH_NOT_EXPORTED', 'Package subpath ' + matchKey + ' is not defined by "exports" in ' | ||
+ packagePath + path.sep + 'package.json imported from ' + parent + '.'); | ||
} | ||
|
||
return { | ||
resolved: resolvePackageTarget( | ||
packagePath, | ||
parent, | ||
longestMatchingExport, | ||
matchObj[longestMatchingExport], | ||
matchKey.substring(longestMatchingExport.length - 1), | ||
isImports, | ||
conditions | ||
), | ||
exact: false | ||
}; | ||
} | ||
|
||
module.exports = function resolveExports(packagePath, parent, subpath, exports, conditions) { | ||
return resolveImportExport( | ||
packagePath, | ||
parent, | ||
validateExports(exports, packagePath), | ||
'.' + subpath, | ||
false, | ||
conditions | ||
); | ||
}; |
Oops, something went wrong.