| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -242,13 +242,13 @@ throw when an attempt is made to import them: | |||
| 242 | 242 | ||
| 243 | 243 | ```js | |
| 244 | 244 | import submodule from 'es-module-package/private-module.js'; | |
| 245 | - // Throws - Package exports error | ||
| 245 | + // Throws - Module not found | ||
| 246 | 246 | ``` | |
| 247 | 247 | ||
| 248 | 248 | > Note: this is not a strong encapsulation as any private modules can still be | |
| 249 | 249 | > loaded by absolute paths. | |
| 250 | 250 | ||
| 251 | - Folders can also be mapped with package exports as well: | ||
| 251 | + Folders can also be mapped with package exports: | ||
| 252 | 252 | ||
| 253 | 253 | <!-- eslint-skip --> | |
| 254 | 254 | ```js | |
@@ -268,8 +268,24 @@ import feature from 'es-module-package/features/x.js'; | |||
| 268 | 268 | If a package has no exports, setting `"exports": false` can be used instead of | |
| 269 | 269 | `"exports": {}` to indicate the package does not intend for submodules to be | |
| 270 | 270 | exposed. | |
| 271 | - This is just a convention that works because `false`, just like `{}`, has no | ||
| 272 | - iterable own properties. | ||
| 271 | + | ||
| 272 | + Any invalid exports entries will be ignored. This includes exports not | ||
| 273 | + starting with `"./"` or a missing trailing `"/"` for directory exports. | ||
| 274 | + | ||
| 275 | + Array fallback support is provided for exports, similarly to import maps | ||
| 276 | + in order to be forward-compatible with fallback workflows in future: | ||
| 277 | + | ||
| 278 | + <!-- eslint-skip --> | ||
| 279 | + ```js | ||
| 280 | + { | ||
| 281 | + "exports": { | ||
| 282 | + "./submodule": ["not:valid", "./submodule.js"] | ||
| 283 | + } | ||
| 284 | + } | ||
| 285 | + ``` | ||
| 286 | + | ||
| 287 | + Since `"not:valid"` is not a supported target, `"./submodule.js"` is used | ||
| 288 | + instead as the fallback, as if it were the only target. | ||
| 273 | 289 | ||
| 274 | 290 | ## <code>import</code> Specifiers | |
| 275 | 291 | ||
@@ -660,7 +676,7 @@ CommonJS loader. Additional formats such as _"addon"_ can be extended in future | |||
| 660 | 676 | updates. | |
| 661 | 677 | ||
| 662 | 678 | In the following algorithms, all subroutine errors are propagated as errors | |
| 663 | - of these top-level routines. | ||
| 679 | + of these top-level routines unless stated otherwise. | ||
| 664 | 680 | ||
| 665 | 681 | _isMain_ is **true** when resolving the Node.js application entry point. | |
| 666 | 682 | ||
@@ -681,6 +697,9 @@ _isMain_ is **true** when resolving the Node.js application entry point. | |||
| 681 | 697 | > 1. Note: _specifier_ is now a bare specifier. | |
| 682 | 698 | > 1. Set _resolvedURL_ the result of | |
| 683 | 699 | > **PACKAGE_RESOLVE**(_specifier_, _parentURL_). | |
| 700 | + > 1. If _resolvedURL_ contains any percent encodings of _"/"_ or _"\\"_ (_"%2f"_ | ||
| 701 | + > and _"%5C"_ respectively), then | ||
| 702 | + > 1. Throw an _Invalid Specifier_ error. | ||
| 684 | 703 | > 1. If the file at _resolvedURL_ does not exist, then | |
| 685 | 704 | > 1. Throw a _Module Not Found_ error. | |
| 686 | 705 | > 1. Set _resolvedURL_ to the real path of _resolvedURL_. | |
@@ -737,7 +756,7 @@ _isMain_ is **true** when resolving the Node.js application entry point. | |||
| 737 | 756 | > 1. If _pjson_ is **null**, then | |
| 738 | 757 | > 1. Throw a _Module Not Found_ error. | |
| 739 | 758 | > 1. If _pjson.main_ is a String, then | |
| 740 | - > 1. Let _resolvedMain_ be the concatenation of _packageURL_, "/", and | ||
| 759 | + > 1. Let _resolvedMain_ be the URL resolution of _packageURL_, "/", and | ||
| 741 | 760 | > _pjson.main_. | |
| 742 | 761 | > 1. If the file at _resolvedMain_ exists, then | |
| 743 | 762 | > 1. Return _resolvedMain_. | |
@@ -746,28 +765,49 @@ _isMain_ is **true** when resolving the Node.js application entry point. | |||
| 746 | 765 | > 1. Let _legacyMainURL_ be the result applying the legacy | |
| 747 | 766 | > **LOAD_AS_DIRECTORY** CommonJS resolver to _packageURL_, throwing a | |
| 748 | 767 | > _Module Not Found_ error for no resolution. | |
| 749 | - > 1. If _legacyMainURL_ does not end in _".js"_ then, | ||
| 750 | - > 1. Throw an _Unsupported File Extension_ error. | ||
| 751 | 768 | > 1. Return _legacyMainURL_. | |
| 752 | 769 | ||
| 753 | 770 | **PACKAGE_EXPORTS_RESOLVE**(_packageURL_, _packagePath_, _exports_) | |
| 754 | 771 | > 1. If _exports_ is an Object, then | |
| 755 | 772 | > 1. Set _packagePath_ to _"./"_ concatenated with _packagePath_. | |
| 756 | 773 | > 1. If _packagePath_ is a key of _exports_, then | |
| 757 | 774 | > 1. Let _target_ be the value of _exports[packagePath]_. | |
| 758 | - > 1. If _target_ is not a String, continue the loop. | ||
| 759 | - > 1. Return the URL resolution of the concatenation of _packageURL_ and | ||
| 760 | - > _target_. | ||
| 775 | + > 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_, | ||
| 776 | + > _""_). | ||
| 761 | 777 | > 1. Let _directoryKeys_ be the list of keys of _exports_ ending in | |
| 762 | 778 | > _"/"_, sorted by length descending. | |
| 763 | 779 | > 1. For each key _directory_ in _directoryKeys_, do | |
| 764 | 780 | > 1. If _packagePath_ starts with _directory_, then | |
| 765 | 781 | > 1. Let _target_ be the value of _exports[directory]_. | |
| 766 | - > 1. If _target_ is not a String, continue the loop. | ||
| 767 | 782 | > 1. Let _subpath_ be the substring of _target_ starting at the index | |
| 768 | 783 | > of the length of _directory_. | |
| 769 | - > 1. Return the URL resolution of the concatenation of _packageURL_, | ||
| 770 | - > _target_ and _subpath_. | ||
| 784 | + > 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_, | ||
| 785 | + > _subpath_). | ||
| 786 | + > 1. Throw a _Module Not Found_ error. | ||
| 787 | + | ||
| 788 | + **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_, _subpath_) | ||
| 789 | + > 1. If _target_ is a String, then | ||
| 790 | + > 1. If _target_ does not start with _"./"_, throw a _Module Not Found_ | ||
| 791 | + > error. | ||
| 792 | + > 1. If _subpath_ has non-zero length and _target_ does not end with _"/"_, | ||
| 793 | + > throw a _Module Not Found_ error. | ||
| 794 | + > 1. If _target_ or _subpath_ contain any _"node_modules"_ segments including | ||
| 795 | + > _"node_modules"_ percent-encoding, throw a _Module Not Found_ error. | ||
| 796 | + > 1. Let _resolvedTarget_ be the URL resolution of the concatenation of | ||
| 797 | + > _packageURL_ and _target_. | ||
| 798 | + > 1. If _resolvedTarget_ is contained in _packageURL_, then | ||
| 799 | + > 1. Let _resolved_ be the URL resolution of the concatenation of | ||
| 800 | + > _subpath_ and _resolvedTarget_. | ||
| 801 | + > 1. If _resolved_ is contained in _resolvedTarget_, then | ||
| 802 | + > 1. Return _resolved_. | ||
| 803 | + > 1. Otherwise, if _target_ is an Array, then | ||
| 804 | + > 1. For each item _targetValue_ in _target_, do | ||
| 805 | + > 1. If _targetValue_ is not a String, continue the loop. | ||
| 806 | + > 1. Let _resolved_ be the result of | ||
| 807 | + > **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _targetValue_, | ||
| 808 | + > _subpath_), continuing the loop on abrupt completion. | ||
| 809 | + > 1. Assert: _resolved_ is a String. | ||
| 810 | + > 1. Return _resolved_. | ||
| 771 | 811 | > 1. Throw a _Module Not Found_ error. | |
| 772 | 812 | ||
| 773 | 813 | **ESM_FORMAT**(_url_, _isMain_) | |
@@ -790,6 +830,7 @@ _isMain_ is **true** when resolving the Node.js application entry point. | |||
| 790 | 830 | **READ_PACKAGE_SCOPE**(_url_) | |
| 791 | 831 | > 1. Let _scopeURL_ be _url_. | |
| 792 | 832 | > 1. While _scopeURL_ is not the file system root, | |
| 833 | + > 1. If _scopeURL_ ends in a _"node_modules"_ path segment, return **null**. | ||
| 793 | 834 | > 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_scopeURL_). | |
| 794 | 835 | > 1. If _pjson_ is not **null**, then | |
| 795 | 836 | > 1. Return _pjson_. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -202,11 +202,12 @@ NODE_MODULES_PATHS(START) | |||
| 202 | 202 | 5. return DIRS | |
| 203 | 203 | ``` | |
| 204 | 204 | ||
| 205 | - If `--experimental-exports` is enabled, | ||
| 206 | - node allows packages loaded via `LOAD_NODE_MODULES` to explicitly declare | ||
| 207 | - which filepaths to expose and how they should be interpreted. | ||
| 208 | - This expands on the control packages already had using the `main` field. | ||
| 209 | - With this feature enabled, the `LOAD_NODE_MODULES` changes as follows: | ||
| 205 | + If `--experimental-exports` is enabled, Node.js allows packages loaded via | ||
| 206 | + `LOAD_NODE_MODULES` to explicitly declare which file paths to expose and how | ||
| 207 | + they should be interpreted. This expands on the control packages already had | ||
| 208 | + using the `main` field. | ||
| 209 | + | ||
| 210 | + With this feature enabled, the `LOAD_NODE_MODULES` changes are: | ||
| 210 | 211 | ||
| 211 | 212 | ```txt | |
| 212 | 213 | LOAD_NODE_MODULES(X, START) | |
@@ -224,10 +225,10 @@ RESOLVE_BARE_SPECIFIER(DIR, X) | |||
| 224 | 225 | b. If "exports" is null or undefined, GOTO 3. | |
| 225 | 226 | c. Find the longest key in "exports" that the subpath starts with. | |
| 226 | 227 | d. If no such key can be found, throw "not found". | |
| 227 | - e. If the key matches the subpath entirely, return DIR/name/${exports[key]}. | ||
| 228 | - f. If either the key or exports[key] do not end with a slash (`/`), | ||
| 229 | - throw "not found". | ||
| 230 | - g. Return DIR/name/${exports[key]}${subpath.slice(key.length)}. | ||
| 228 | + e. let RESOLVED_URL = | ||
| 229 | + PACKAGE_EXPORTS_TARGET_RESOLVE(pathToFileURL(DIR/name), exports[key], | ||
| 230 | + subpath.slice(key.length)), as defined in the esm resolver. | ||
| 231 | + f. return fileURLToPath(RESOLVED_URL) | ||
| 231 | 232 | 3. return DIR/X | |
| 232 | 233 | ``` | |
| 233 | 234 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -24,6 +24,7 @@ | |||
| 24 | 24 | const { | |
| 25 | 25 | JSON, | |
| 26 | 26 | Object, | |
| 27 | + ObjectPrototype, | ||
| 27 | 28 | Reflect, | |
| 28 | 29 | SafeMap, | |
| 29 | 30 | StringPrototype, | |
@@ -372,34 +373,32 @@ function resolveExports(nmPath, request, absoluteRequest) { | |||
| 372 | 373 | ||
| 373 | 374 | const basePath = path.resolve(nmPath, name); | |
| 374 | 375 | const pkgExports = readExports(basePath); | |
| 376 | + const mappingKey = `.${expansion}`; | ||
| 375 | 377 | ||
| 376 | - if (pkgExports != null) { | ||
| 377 | - const mappingKey = `.${expansion}`; | ||
| 378 | - const mapping = pkgExports[mappingKey]; | ||
| 379 | - if (typeof mapping === 'string') { | ||
| 380 | - return fileURLToPath(new URL(mapping, `${pathToFileURL(basePath)}/`)); | ||
| 378 | + if (typeof pkgExports === 'object' && pkgExports !== null) { | ||
| 379 | + if (ObjectPrototype.hasOwnProperty(pkgExports, mappingKey)) { | ||
| 380 | + const mapping = pkgExports[mappingKey]; | ||
| 381 | + return resolveExportsTarget(pathToFileURL(basePath + '/'), mapping, '', | ||
| 382 | + basePath, mappingKey); | ||
| 381 | 383 | } | |
| 382 | 384 | ||
| 383 | 385 | let dirMatch = ''; | |
| 384 | - for (const [candidateKey, candidateValue] of Object.entries(pkgExports)) { | ||
| 386 | + for (const candidateKey of Object.keys(pkgExports)) { | ||
| 385 | 387 | if (candidateKey[candidateKey.length - 1] !== '/') continue; | |
| 386 | - if (candidateValue[candidateValue.length - 1] !== '/') continue; | ||
| 387 | 388 | if (candidateKey.length > dirMatch.length && | |
| 388 | 389 | StringPrototype.startsWith(mappingKey, candidateKey)) { | |
| 389 | 390 | dirMatch = candidateKey; | |
| 390 | 391 | } | |
| 391 | 392 | } | |
| 392 | 393 | ||
| 393 | 394 | if (dirMatch !== '') { | |
| 394 | - const dirMapping = pkgExports[dirMatch]; | ||
| 395 | - const remainder = StringPrototype.slice(mappingKey, dirMatch.length); | ||
| 396 | - const expectedPrefix = | ||
| 397 | - new URL(dirMapping, `${pathToFileURL(basePath)}/`); | ||
| 398 | - const resolved = new URL(remainder, expectedPrefix).href; | ||
| 399 | - if (StringPrototype.startsWith(resolved, expectedPrefix.href)) { | ||
| 400 | - return fileURLToPath(resolved); | ||
| 401 | - } | ||
| 395 | + const mapping = pkgExports[dirMatch]; | ||
| 396 | + const subpath = StringPrototype.slice(mappingKey, dirMatch.length); | ||
| 397 | + return resolveExportsTarget(pathToFileURL(basePath + '/'), mapping, | ||
| 398 | + subpath, basePath, mappingKey); | ||
| 402 | 399 | } | |
| 400 | + } | ||
| 401 | + if (pkgExports != null) { | ||
| 403 | 402 | // eslint-disable-next-line no-restricted-syntax | |
| 404 | 403 | const e = new Error(`Package exports for '${basePath}' do not define ` + | |
| 405 | 404 | `a '${mappingKey}' subpath`); | |
@@ -411,6 +410,43 @@ function resolveExports(nmPath, request, absoluteRequest) { | |||
| 411 | 410 | return path.resolve(nmPath, request); | |
| 412 | 411 | } | |
| 413 | 412 | ||
| 413 | + function resolveExportsTarget(pkgPath, target, subpath, basePath, mappingKey) { | ||
| 414 | + if (typeof target === 'string') { | ||
| 415 | + if (target.startsWith('./') && | ||
| 416 | + (subpath.length === 0 || target.endsWith('/'))) { | ||
| 417 | + const resolvedTarget = new URL(target, pkgPath); | ||
| 418 | + const pkgPathPath = pkgPath.pathname; | ||
| 419 | + const resolvedTargetPath = resolvedTarget.pathname; | ||
| 420 | + if (StringPrototype.startsWith(resolvedTargetPath, pkgPathPath) && | ||
| 421 | + StringPrototype.indexOf(resolvedTargetPath, '/node_modules/', | ||
| 422 | + pkgPathPath.length - 1) === -1) { | ||
| 423 | + const resolved = new URL(subpath, resolvedTarget); | ||
| 424 | + const resolvedPath = resolved.pathname; | ||
| 425 | + if (StringPrototype.startsWith(resolvedPath, resolvedTargetPath) && | ||
| 426 | + StringPrototype.indexOf(resolvedPath, '/node_modules/', | ||
| 427 | + pkgPathPath.length - 1) === -1) { | ||
| 428 | + return fileURLToPath(resolved); | ||
| 429 | + } | ||
| 430 | + } | ||
| 431 | + } | ||
| 432 | + } else if (Array.isArray(target)) { | ||
| 433 | + for (const targetValue of target) { | ||
| 434 | + if (typeof targetValue !== 'string') continue; | ||
| 435 | + try { | ||
| 436 | + return resolveExportsTarget(pkgPath, targetValue, subpath, basePath, | ||
| 437 | + mappingKey); | ||
| 438 | + } catch (e) { | ||
| 439 | + if (e.code !== 'MODULE_NOT_FOUND') throw e; | ||
| 440 | + } | ||
| 441 | + } | ||
| 442 | + } | ||
| 443 | + // eslint-disable-next-line no-restricted-syntax | ||
| 444 | + const e = new Error(`Package exports for '${basePath}' do not define a ` + | ||
| 445 | + `valid '${mappingKey}' target${subpath ? 'for ' + subpath : ''}`); | ||
| 446 | + e.code = 'MODULE_NOT_FOUND'; | ||
| 447 | + throw e; | ||
| 448 | + } | ||
| 449 | + | ||
| 414 | 450 | Module._findPath = function(request, paths, isMain) { | |
| 415 | 451 | const absoluteRequest = path.isAbsolute(request); | |
| 416 | 452 | if (absoluteRequest) { | |
| Back | FazBrowse Home | New Git URL |
0 commit comments