-
-
Notifications
You must be signed in to change notification settings - Fork 184
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
19 changed files
with
540 additions
and
36 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,178 @@ | ||
var path = require('path'); | ||
|
||
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 validateEnvNames(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; | ||
} | ||
|
||
var startsWith; | ||
|
||
if (typeof String.prototype.startsWith === 'function') { | ||
startsWith = function (path, exportedPath) { | ||
return path.startsWith(exportedPath); | ||
}; | ||
} else { | ||
startsWith = function (path, exportedPath) { | ||
for (var i = 0; i < exportedPath.length; i++) { | ||
if (path[i] !== exportedPath[i]) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
}; | ||
} | ||
|
||
function resolveExportsTarget(packagePath, parent, key, target, subpath, env) { | ||
if (typeof target === 'string') { | ||
var resolvedTarget = path.resolve(packagePath, target); | ||
if (!(/^\.\//).test(target) || resolvedTarget.indexOf('/node_modules/', packagePath.length - 1) !== -1 || !startsWith(resolvedTarget, packagePath)) { | ||
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); | ||
|
||
if (!startsWith(resolved, resolvedTarget)) { | ||
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 resolveExportsTarget(packagePath, parent, key, target[i], subpath, env); | ||
} 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 exportedEnvs = validateEnvNames(Object.keys(target)); | ||
|
||
for (i = 0; i < exportedEnvs.length; i++) { | ||
var exportedEnv = exportedEnvs[i]; | ||
if (exportedEnv === 'default' || env.indexOf(exportedEnv) !== -1) { | ||
try { | ||
return resolveExportsTarget(packagePath, parent, key, target[exportedEnv], subpath, env); | ||
} 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 + '.'); | ||
} | ||
|
||
module.exports = function resolveExports(packagePath, parent, subpath, exports, env) { | ||
exports = validateExports(exports, packagePath); // eslint-disable-line no-param-reassign | ||
subpath = '.' + subpath; // eslint-disable-line no-param-reassign | ||
|
||
if (subpath === '.' && exports['.'] === undefined) { | ||
return; | ||
} | ||
|
||
var resolved; | ||
var isDirectoryExport; | ||
if (Object.prototype.hasOwnProperty.call(exports, subpath)) { | ||
resolved = resolveExportsTarget(packagePath, parent, subpath, exports[subpath], '', env); | ||
isDirectoryExport = false; | ||
} else { | ||
var longestMatchingExport = ''; | ||
var exportedPaths = Object.keys(exports); | ||
|
||
for (var i = 0; i < exportedPaths.length; i++) { | ||
var exportedPath = exportedPaths[i]; | ||
if (exportedPath[exportedPath.length - 1] === '/' && startsWith(subpath, exportedPath) && exportedPath.length > longestMatchingExport.length) { | ||
longestMatchingExport = exportedPath; | ||
} | ||
} | ||
|
||
if (longestMatchingExport === '') { | ||
throw makeError('ERR_PACKAGE_PATH_NOT_EXPORTED', 'Package subpath ' + subpath + ' is not defined by "exports" in ' | ||
+ packagePath + path.sep + 'package.json imported from ' + parent + '.'); | ||
} | ||
|
||
resolved = resolveExportsTarget( | ||
packagePath, | ||
parent, | ||
longestMatchingExport, | ||
exports[longestMatchingExport], | ||
subpath.substring(longestMatchingExport.length - 1), | ||
env | ||
); | ||
isDirectoryExport = true; | ||
} | ||
|
||
return { | ||
path: resolved, | ||
tryLoadAsDirectory: isDirectoryExport && subpath !== './' | ||
}; | ||
}; |
Oops, something went wrong.