| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent fa7de9b commit ce45aae
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -313,6 +313,33 @@ If a package has no exports, setting `"exports": false` can be used instead of | |||
| 313 | 313 | `"exports": {}` to indicate the package does not intend for submodules to be | |
| 314 | 314 | exposed. | |
| 315 | 315 | ||
| 316 | + Exports can also be used to map the main entry point of a package: | ||
| 317 | + | ||
| 318 | + <!-- eslint-skip --> | ||
| 319 | + ```js | ||
| 320 | + // ./node_modules/es-module-package/package.json | ||
| 321 | + { | ||
| 322 | + "exports": { | ||
| 323 | + ".": "./main.js" | ||
| 324 | + } | ||
| 325 | + } | ||
| 326 | + ``` | ||
| 327 | + | ||
| 328 | + where the "." indicates loading the package without any subpath. Exports will | ||
| 329 | + always override any existing `"main"` value for both CommonJS and | ||
| 330 | + ES module packages. | ||
| 331 | + | ||
| 332 | + For packages with only a main entry point, an `"exports"` value of just | ||
| 333 | + a string is also supported: | ||
| 334 | + | ||
| 335 | + <!-- eslint-skip --> | ||
| 336 | + ```js | ||
| 337 | + // ./node_modules/es-module-package/package.json | ||
| 338 | + { | ||
| 339 | + "exports": "./main.js" | ||
| 340 | + } | ||
| 341 | + ``` | ||
| 342 | + | ||
| 316 | 343 | Any invalid exports entries will be ignored. This includes exports not | |
| 317 | 344 | starting with `"./"` or a missing trailing `"/"` for directory exports. | |
| 318 | 345 | ||
@@ -841,6 +868,15 @@ _isMain_ is **true** when resolving the Node.js application entry point. | |||
| 841 | 868 | ||
| 842 | 869 | > 1. If _pjson_ is **null**, then | |
| 843 | 870 | > 1. Throw a _Module Not Found_ error. | |
| 871 | + > 1. If _pjson.exports_ is not **null** or **undefined**, then | ||
| 872 | + > 1. If _pjson.exports_ is a String or Array, then | ||
| 873 | + > 1. Return _PACKAGE_EXPORTS_TARGET_RESOLVE(packageURL, pjson.exports, | ||
| 874 | + > "")_. | ||
| 875 | + > 1. If _pjson.exports is an Object, then | ||
| 876 | + > 1. If _pjson.exports_ contains a _"."_ property, then | ||
| 877 | + > 1. Let _mainExport_ be the _"."_ property in _pjson.exports_. | ||
| 878 | + > 1. Return _PACKAGE_EXPORTS_TARGET_RESOLVE(packageURL, mainExport, | ||
| 879 | + > "")_. | ||
| 844 | 880 | > 1. If _pjson.main_ is a String, then | |
| 845 | 881 | > 1. Let _resolvedMain_ be the URL resolution of _packageURL_, "/", and | |
| 846 | 882 | > _pjson.main_. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -361,11 +361,11 @@ function findLongestRegisteredExtension(filename) { | |||
| 361 | 361 | // This only applies to requests of a specific form: | |
| 362 | 362 | // 1. name/.* | |
| 363 | 363 | // 2. @scope/name/.* | |
| 364 | - const EXPORTS_PATTERN = /^((?:@[^/\\%]+\/)?[^./\\%][^/\\%]*)(\/.*)$/; | ||
| 364 | + const EXPORTS_PATTERN = /^((?:@[^/\\%]+\/)?[^./\\%][^/\\%]*)(\/.*)?$/; | ||
| 365 | 365 | function resolveExports(nmPath, request, absoluteRequest) { | |
| 366 | 366 | // The implementation's behavior is meant to mirror resolution in ESM. | |
| 367 | 367 | if (experimentalExports && !absoluteRequest) { | |
| 368 | - const [, name, expansion] = | ||
| 368 | + const [, name, expansion = ''] = | ||
| 369 | 369 | StringPrototype.match(request, EXPORTS_PATTERN) || []; | |
| 370 | 370 | if (!name) { | |
| 371 | 371 | return path.resolve(nmPath, request); | |
@@ -398,6 +398,10 @@ function resolveExports(nmPath, request, absoluteRequest) { | |||
| 398 | 398 | subpath, basePath, mappingKey); | |
| 399 | 399 | } | |
| 400 | 400 | } | |
| 401 | + if (mappingKey === '.' && typeof pkgExports === 'string') { | ||
| 402 | + return resolveExportsTarget(pathToFileURL(basePath + '/'), pkgExports, | ||
| 403 | + '', basePath, mappingKey); | ||
| 404 | + } | ||
| 401 | 405 | if (pkgExports != null) { | |
| 402 | 406 | // eslint-disable-next-line no-restricted-syntax | |
| 403 | 407 | const e = new Error(`Package exports for '${basePath}' do not define ` + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -773,39 +773,6 @@ Maybe<URL> FinalizeResolution(Environment* env, | |||
| 773 | 773 | return Just(resolved); | |
| 774 | 774 | } | |
| 775 | 775 | ||
| 776 | - Maybe<URL> PackageMainResolve(Environment* env, | ||
| 777 | - const URL& pjson_url, | ||
| 778 | - const PackageConfig& pcfg, | ||
| 779 | - const URL& base) { | ||
| 780 | - if (pcfg.exists == Exists::Yes) { | ||
| 781 | - if (pcfg.has_main == HasMain::Yes) { | ||
| 782 | - URL resolved(pcfg.main, pjson_url); | ||
| 783 | - const std::string& path = resolved.ToFilePath(); | ||
| 784 | - if (CheckDescriptorAtPath(path) == FILE) { | ||
| 785 | - return Just(resolved); | ||
| 786 | - } | ||
| 787 | - } | ||
| 788 | - if (env->options()->es_module_specifier_resolution == "node") { | ||
| 789 | - if (pcfg.has_main == HasMain::Yes) { | ||
| 790 | - return FinalizeResolution(env, URL(pcfg.main, pjson_url), base); | ||
| 791 | - } else { | ||
| 792 | - return FinalizeResolution(env, URL("index", pjson_url), base); | ||
| 793 | - } | ||
| 794 | - } | ||
| 795 | - if (pcfg.type != PackageType::Module) { | ||
| 796 | - Maybe<URL> resolved = LegacyMainResolve(pjson_url, pcfg); | ||
| 797 | - if (!resolved.IsNothing()) { | ||
| 798 | - return resolved; | ||
| 799 | - } | ||
| 800 | - } | ||
| 801 | - } | ||
| 802 | - std::string msg = "Cannot find main entry point for " + | ||
| 803 | - URL(".", pjson_url).ToFilePath() + " imported from " + | ||
| 804 | - base.ToFilePath(); | ||
| 805 | - node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); | ||
| 806 | - return Nothing<URL>(); | ||
| 807 | - } | ||
| 808 | - | ||
| 809 | 776 | void ThrowExportsNotFound(Environment* env, | |
| 810 | 777 | const std::string& subpath, | |
| 811 | 778 | const URL& pjson_url, | |
@@ -887,6 +854,99 @@ Maybe<URL> ResolveExportsTarget(Environment* env, | |||
| 887 | 854 | return Just(subpath_resolved); | |
| 888 | 855 | } | |
| 889 | 856 | ||
| 857 | + Maybe<URL> PackageMainResolve(Environment* env, | ||
| 858 | + const URL& pjson_url, | ||
| 859 | + const PackageConfig& pcfg, | ||
| 860 | + const URL& base) { | ||
| 861 | + if (pcfg.exists == Exists::Yes) { | ||
| 862 | + Isolate* isolate = env->isolate(); | ||
| 863 | + Local<Context> context = env->context(); | ||
| 864 | + if (!pcfg.exports.IsEmpty()) { | ||
| 865 | + Local<Value> exports = pcfg.exports.Get(isolate); | ||
| 866 | + if (exports->IsString() || exports->IsObject() || exports->IsArray()) { | ||
| 867 | + Local<Value> target; | ||
| 868 | + if (!exports->IsObject()) { | ||
| 869 | + target = exports; | ||
| 870 | + } else { | ||
| 871 | + Local<Object> exports_obj = exports.As<Object>(); | ||
| 872 | + Local<String> dot_string = String::NewFromUtf8(env->isolate(), ".", | ||
| 873 | + v8::NewStringType::kNormal).ToLocalChecked(); | ||
| 874 | + target = | ||
| 875 | + exports_obj->Get(env->context(), dot_string).ToLocalChecked(); | ||
| 876 | + } | ||
| 877 | + if (target->IsString()) { | ||
| 878 | + Utf8Value target_utf8(isolate, target.As<v8::String>()); | ||
| 879 | + std::string target(*target_utf8, target_utf8.length()); | ||
| 880 | + Maybe<URL> resolved = ResolveExportsTarget(env, target, "", ".", | ||
| 881 | + pjson_url, base); | ||
| 882 | + if (resolved.IsNothing()) { | ||
| 883 | + ThrowExportsInvalid(env, ".", target, pjson_url, base); | ||
| 884 | + return Nothing<URL>(); | ||
| 885 | + } | ||
| 886 | + return FinalizeResolution(env, resolved.FromJust(), base); | ||
| 887 | + } else if (target->IsArray()) { | ||
| 888 | + Local<Array> target_arr = target.As<Array>(); | ||
| 889 | + const uint32_t length = target_arr->Length(); | ||
| 890 | + if (length == 0) { | ||
| 891 | + ThrowExportsInvalid(env, ".", target, pjson_url, base); | ||
| 892 | + return Nothing<URL>(); | ||
| 893 | + } | ||
| 894 | + for (uint32_t i = 0; i < length; i++) { | ||
| 895 | + auto target_item = target_arr->Get(context, i).ToLocalChecked(); | ||
| 896 | + if (target_item->IsString()) { | ||
| 897 | + Utf8Value target_utf8(isolate, target_item.As<v8::String>()); | ||
| 898 | + std::string target_str(*target_utf8, target_utf8.length()); | ||
| 899 | + Maybe<URL> resolved = ResolveExportsTarget(env, target_str, "", | ||
| 900 | + ".", pjson_url, base, false); | ||
| 901 | + if (resolved.IsNothing()) continue; | ||
| 902 | + return FinalizeResolution(env, resolved.FromJust(), base); | ||
| 903 | + } | ||
| 904 | + } | ||
| 905 | + auto invalid = target_arr->Get(context, length - 1).ToLocalChecked(); | ||
| 906 | + if (!invalid->IsString()) { | ||
| 907 | + ThrowExportsInvalid(env, ".", invalid, pjson_url, base); | ||
| 908 | + return Nothing<URL>(); | ||
| 909 | + } | ||
| 910 | + Utf8Value invalid_utf8(isolate, invalid.As<v8::String>()); | ||
| 911 | + std::string invalid_str(*invalid_utf8, invalid_utf8.length()); | ||
| 912 | + Maybe<URL> resolved = ResolveExportsTarget(env, invalid_str, "", | ||
| 913 | + ".", pjson_url, base); | ||
| 914 | + CHECK(resolved.IsNothing()); | ||
| 915 | + return Nothing<URL>(); | ||
| 916 | + } else { | ||
| 917 | + ThrowExportsInvalid(env, ".", target, pjson_url, base); | ||
| 918 | + return Nothing<URL>(); | ||
| 919 | + } | ||
| 920 | + } | ||
| 921 | + } | ||
| 922 | + if (pcfg.has_main == HasMain::Yes) { | ||
| 923 | + URL resolved(pcfg.main, pjson_url); | ||
| 924 | + const std::string& path = resolved.ToFilePath(); | ||
| 925 | + if (CheckDescriptorAtPath(path) == FILE) { | ||
| 926 | + return Just(resolved); | ||
| 927 | + } | ||
| 928 | + } | ||
| 929 | + if (env->options()->es_module_specifier_resolution == "node") { | ||
| 930 | + if (pcfg.has_main == HasMain::Yes) { | ||
| 931 | + return FinalizeResolution(env, URL(pcfg.main, pjson_url), base); | ||
| 932 | + } else { | ||
| 933 | + return FinalizeResolution(env, URL("index", pjson_url), base); | ||
| 934 | + } | ||
| 935 | + } | ||
| 936 | + if (pcfg.type != PackageType::Module) { | ||
| 937 | + Maybe<URL> resolved = LegacyMainResolve(pjson_url, pcfg); | ||
| 938 | + if (!resolved.IsNothing()) { | ||
| 939 | + return resolved; | ||
| 940 | + } | ||
| 941 | + } | ||
| 942 | + } | ||
| 943 | + std::string msg = "Cannot find main entry point for " + | ||
| 944 | + URL(".", pjson_url).ToFilePath() + " imported from " + | ||
| 945 | + base.ToFilePath(); | ||
| 946 | + node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); | ||
| 947 | + return Nothing<URL>(); | ||
| 948 | + } | ||
| 949 | + | ||
| 890 | 950 | Maybe<URL> PackageExportsResolve(Environment* env, | |
| 891 | 951 | const URL& pjson_url, | |
| 892 | 952 | const std::string& pkg_subpath, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -20,6 +20,8 @@ import { requireFixture, importFixture } from '../fixtures/pkgexports.mjs'; | |||
| 20 | 20 | // Fallbacks | |
| 21 | 21 | ['pkgexports/fallbackdir/asdf.js', { default: 'asdf' }], | |
| 22 | 22 | ['pkgexports/fallbackfile', { default: 'asdf' }], | |
| 23 | + // Dot main | ||
| 24 | + ['pkgexports', { default: 'asdf' }], | ||
| 23 | 25 | ]); | |
| 24 | 26 | for (const [validSpecifier, expected] of validSpecifiers) { | |
| 25 | 27 | if (validSpecifier === null) continue; | |
@@ -81,18 +83,6 @@ import { requireFixture, importFixture } from '../fixtures/pkgexports.mjs'; | |||
| 81 | 83 | })); | |
| 82 | 84 | } | |
| 83 | 85 | ||
| 84 | - // There's no main field so we won't find anything when importing the name. | ||
| 85 | - // The fact that "." is mapped is ignored, it's not a valid main config. | ||
| 86 | - loadFixture('pkgexports').catch(mustCall((err) => { | ||
| 87 | - if (isRequire) { | ||
| 88 | - strictEqual(err.code, 'MODULE_NOT_FOUND'); | ||
| 89 | - assertStartsWith(err.message, 'Cannot find module \'pkgexports\''); | ||
| 90 | - } else { | ||
| 91 | - strictEqual(err.code, 'ERR_MODULE_NOT_FOUND'); | ||
| 92 | - assertStartsWith(err.message, 'Cannot find main entry point'); | ||
| 93 | - } | ||
| 94 | - })); | ||
| 95 | - | ||
| 96 | 86 | // Covering out bases - not a file is still not a file after dir mapping. | |
| 97 | 87 | loadFixture('pkgexports/sub/not-a-file.js').catch(mustCall((err) => { | |
| 98 | 88 | strictEqual(err.code, (isRequire ? '' : 'ERR_') + 'MODULE_NOT_FOUND'); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments