| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -349,33 +349,36 @@ Node.js and the browser can be written: | |||
| 349 | 349 | When resolving the `"."` export, if no matching target is found, the `"main"` | |
| 350 | 350 | will be used as the final fallback. | |
| 351 | 351 | ||
| 352 | - The conditions supported in Node.js are matched in the following order: | ||
| 352 | + The conditions supported in Node.js condition matching: | ||
| 353 | 353 | ||
| 354 | - 1. `"node"` - matched for any Node.js environment. Can be a CommonJS or ES | ||
| 354 | + * `"default"` - the generic fallback that will always match. Can be a CommonJS | ||
| 355 | + or ES module file. | ||
| 356 | + * `"import"` - matched when the package is loaded via `import` or | ||
| 357 | + `import()`. Can be any module format, this field does not set the type | ||
| 358 | + interpretation. _This is currently only supported behind the | ||
| 359 | + `--experimental-conditional-exports` flag._ | ||
| 360 | + * `"node"` - matched for any Node.js environment. Can be a CommonJS or ES | ||
| 355 | 361 | module file. _This is currently only supported behind the | |
| 356 | 362 | `--experimental-conditional-exports` flag._ | |
| 357 | - 2. `"require"` - matched when the package is loaded via `require()`. | ||
| 363 | + * `"require"` - matched when the package is loaded via `require()`. | ||
| 358 | 364 | _This is currently only supported behind the | |
| 359 | 365 | `--experimental-conditional-exports` flag._ | |
| 360 | - 3. `"import"` - matched when the package is loaded via `import` or | ||
| 361 | - `import()`. Can be any module format, this field does not set the type | ||
| 362 | - interpretation. _This is currently only supported behind the | ||
| 363 | - `--experimental-conditional-exports` flag._ | ||
| 364 | - 4. `"default"` - the generic fallback that will always match if no other | ||
| 365 | - more specific condition is matched first. Can be a CommonJS or ES module | ||
| 366 | - file. | ||
| 367 | 366 | ||
| 368 | - > Setting any of the above flagged conditions for a published package is not | ||
| 369 | - > recommended until they are unflagged to avoid breaking changes to packages in | ||
| 370 | - > future. | ||
| 367 | + Condition matching is applied in object order from first to last within the | ||
| 368 | + `"exports"` object. | ||
| 369 | + | ||
| 370 | + > Setting the above conditions for a published package is not recommended until | ||
| 371 | + > conditional exports have been unflagged to avoid breaking changes to packages. | ||
| 371 | 372 | ||
| 372 | 373 | Using the `"require"` condition it is possible to define a package that will | |
| 373 | 374 | have a different exported value for CommonJS and ES modules, which can be a | |
| 374 | 375 | hazard in that it can result in having two separate instances of the same | |
| 375 | 376 | package in use in an application, which can cause a number of bugs. | |
| 376 | 377 | ||
| 377 | 378 | Other conditions such as `"browser"`, `"electron"`, `"deno"`, `"react-native"`, | |
| 378 | - etc. could be defined in other runtimes or tools. | ||
| 379 | + etc. could be defined in other runtimes or tools. Condition names must not start | ||
| 380 | + with `"."` or be numbers. Further restrictions, definitions or guidance on | ||
| 381 | + condition names may be provided in future. | ||
| 379 | 382 | ||
| 380 | 383 | #### Exports Sugar | |
| 381 | 384 | ||
@@ -1557,13 +1560,15 @@ _defaultEnv_ is the conditional environment name priority array, | |||
| 1557 | 1560 | > 1. If _resolved_ is contained in _resolvedTarget_, then | |
| 1558 | 1561 | > 1. Return _resolved_. | |
| 1559 | 1562 | > 1. Otherwise, if _target_ is a non-null Object, then | |
| 1560 | - > 1. If _target_ has an object key matching one of the names in _env_, then | ||
| 1561 | - > 1. Let _targetValue_ be the corresponding value of the first object key | ||
| 1562 | - > of _target_ in _env_. | ||
| 1563 | - > 1. Let _resolved_ be the result of **PACKAGE_EXPORTS_TARGET_RESOLVE** | ||
| 1564 | - > (_packageURL_, _targetValue_, _subpath_, _env_). | ||
| 1565 | - > 1. Assert: _resolved_ is a String. | ||
| 1566 | - > 1. Return _resolved_. | ||
| 1563 | + > 1. If _exports_ contains any index property keys, as defined in ECMA-262 | ||
| 1564 | + > [6.1.7 Array Index][], throw an _Invalid Package Configuration_ error. | ||
| 1565 | + > 1. For each property _p_ of _target_, in object insertion order as, | ||
| 1566 | + > 1. If _env_ contains an entry for _p_, then | ||
| 1567 | + > 1. Let _targetValue_ be the value of the _p_ property in _target_. | ||
| 1568 | + > 1. Let _resolved_ be the result of **PACKAGE_EXPORTS_TARGET_RESOLVE** | ||
| 1569 | + > (_packageURL_, _targetValue_, _subpath_, _env_). | ||
| 1570 | + > 1. Assert: _resolved_ is a String. | ||
| 1571 | + > 1. Return _resolved_. | ||
| 1567 | 1572 | > 1. Otherwise, if _target_ is an Array, then | |
| 1568 | 1573 | > 1. For each item _targetValue_ in _target_, do | |
| 1569 | 1574 | > 1. If _targetValue_ is an Array, continue the loop. | |
@@ -1659,3 +1664,4 @@ success! | |||
| 1659 | 1664 | [special scheme]: https://url.spec.whatwg.org/#special-scheme | |
| 1660 | 1665 | [the official standard format]: https://tc39.github.io/ecma262/#sec-modules | |
| 1661 | 1666 | [transpiler loader example]: #esm_transpiler_loader | |
| 1667 | + [6.1.7 Array Index]: https://tc39.es/ecma262/#integer-index | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -26,13 +26,16 @@ const { | |||
| 26 | 26 | Error, | |
| 27 | 27 | JSONParse, | |
| 28 | 28 | Map, | |
| 29 | + Number, | ||
| 29 | 30 | ObjectCreate, | |
| 30 | 31 | ObjectDefineProperty, | |
| 31 | 32 | ObjectFreeze, | |
| 33 | + ObjectIs, | ||
| 32 | 34 | ObjectKeys, | |
| 33 | 35 | ObjectPrototypeHasOwnProperty, | |
| 34 | 36 | ReflectSet, | |
| 35 | 37 | SafeMap, | |
| 38 | + String, | ||
| 36 | 39 | StringPrototypeIndexOf, | |
| 37 | 40 | StringPrototypeMatch, | |
| 38 | 41 | StringPrototypeSlice, | |
@@ -555,6 +558,18 @@ function resolveExports(nmPath, request, absoluteRequest) { | |||
| 555 | 558 | return path.resolve(nmPath, request); | |
| 556 | 559 | } | |
| 557 | 560 | ||
| 561 | + function isArrayIndex(p) { | ||
| 562 | + assert(typeof p === 'string'); | ||
| 563 | + const n = Number(p); | ||
| 564 | + if (String(n) !== p) | ||
| 565 | + return false; | ||
| 566 | + if (ObjectIs(n, +0)) | ||
| 567 | + return true; | ||
| 568 | + if (!Number.isInteger(n)) | ||
| 569 | + return false; | ||
| 570 | + return n >= 0 && n < (2 ** 32) - 1; | ||
| 571 | + } | ||
| 572 | + | ||
| 558 | 573 | function resolveExportsTarget(pkgPath, target, subpath, basePath, mappingKey) { | |
| 559 | 574 | if (typeof target === 'string') { | |
| 560 | 575 | if (target.startsWith('./') && | |
@@ -585,34 +600,33 @@ function resolveExportsTarget(pkgPath, target, subpath, basePath, mappingKey) { | |||
| 585 | 600 | } | |
| 586 | 601 | } | |
| 587 | 602 | } else if (typeof target === 'object' && target !== null) { | |
| 588 | - if (experimentalConditionalExports && | ||
| 589 | - ObjectPrototypeHasOwnProperty(target, 'node')) { | ||
| 590 | - try { | ||
| 591 | - const result = resolveExportsTarget(pkgPath, target.node, subpath, | ||
| 592 | - basePath, mappingKey); | ||
| 593 | - emitExperimentalWarning('Conditional exports'); | ||
| 594 | - return result; | ||
| 595 | - } catch (e) { | ||
| 596 | - if (e.code !== 'MODULE_NOT_FOUND') throw e; | ||
| 597 | - } | ||
| 598 | - } | ||
| 599 | - if (experimentalConditionalExports && | ||
| 600 | - ObjectPrototypeHasOwnProperty(target, 'require')) { | ||
| 601 | - try { | ||
| 602 | - const result = resolveExportsTarget(pkgPath, target.require, subpath, | ||
| 603 | - basePath, mappingKey); | ||
| 604 | - emitExperimentalWarning('Conditional exports'); | ||
| 605 | - return result; | ||
| 606 | - } catch (e) { | ||
| 607 | - if (e.code !== 'MODULE_NOT_FOUND') throw e; | ||
| 608 | - } | ||
| 603 | + const keys = ObjectKeys(target); | ||
| 604 | + if (keys.some(isArrayIndex)) { | ||
| 605 | + throw new ERR_INVALID_PACKAGE_CONFIG(basePath, '"exports" cannot ' + | ||
| 606 | + 'contain numeric property keys.'); | ||
| 609 | 607 | } | |
| 610 | - if (ObjectPrototypeHasOwnProperty(target, 'default')) { | ||
| 611 | - try { | ||
| 612 | - return resolveExportsTarget(pkgPath, target.default, subpath, | ||
| 613 | - basePath, mappingKey); | ||
| 614 | - } catch (e) { | ||
| 615 | - if (e.code !== 'MODULE_NOT_FOUND') throw e; | ||
| 608 | + for (const p of keys) { | ||
| 609 | + switch (p) { | ||
| 610 | + case 'node': | ||
| 611 | + case 'require': | ||
| 612 | + if (!experimentalConditionalExports) | ||
| 613 | + continue; | ||
| 614 | + try { | ||
| 615 | + emitExperimentalWarning('Conditional exports'); | ||
| 616 | + const result = resolveExportsTarget(pkgPath, target[p], subpath, | ||
| 617 | + basePath, mappingKey); | ||
| 618 | + return result; | ||
| 619 | + } catch (e) { | ||
| 620 | + if (e.code !== 'MODULE_NOT_FOUND') throw e; | ||
| 621 | + } | ||
| 622 | + break; | ||
| 623 | + case 'default': | ||
| 624 | + try { | ||
| 625 | + return resolveExportsTarget(pkgPath, target.default, subpath, | ||
| 626 | + basePath, mappingKey); | ||
| 627 | + } catch (e) { | ||
| 628 | + if (e.code !== 'MODULE_NOT_FOUND') throw e; | ||
| 629 | + } | ||
| 616 | 630 | } | |
| 617 | 631 | } | |
| 618 | 632 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -203,7 +203,6 @@ constexpr size_t kFsStatsBufferLength = | |||
| 203 | 203 | V(crypto_rsa_pss_string, "rsa-pss") \ | |
| 204 | 204 | V(cwd_string, "cwd") \ | |
| 205 | 205 | V(data_string, "data") \ | |
| 206 | - V(default_string, "default") \ | ||
| 207 | 206 | V(dest_string, "dest") \ | |
| 208 | 207 | V(destroyed_string, "destroyed") \ | |
| 209 | 208 | V(detached_string, "detached") \ | |
@@ -258,7 +257,6 @@ constexpr size_t kFsStatsBufferLength = | |||
| 258 | 257 | V(http_1_1_string, "http/1.1") \ | |
| 259 | 258 | V(identity_string, "identity") \ | |
| 260 | 259 | V(ignore_string, "ignore") \ | |
| 261 | - V(import_string, "import") \ | ||
| 262 | 260 | V(infoaccess_string, "infoAccess") \ | |
| 263 | 261 | V(inherit_string, "inherit") \ | |
| 264 | 262 | V(input_string, "input") \ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -12,7 +12,6 @@ | |||
| 12 | 12 | #include <sys/stat.h> // S_IFDIR | |
| 13 | 13 | ||
| 14 | 14 | #include <algorithm> | |
| 15 | - #include <climits> // PATH_MAX | ||
| 16 | 15 | ||
| 17 | 16 | namespace node { | |
| 18 | 17 | namespace loader { | |
@@ -908,6 +907,25 @@ Maybe<URL> ResolveExportsTargetString(Environment* env, | |||
| 908 | 907 | return Just(subpath_resolved); | |
| 909 | 908 | } | |
| 910 | 909 | ||
| 910 | + bool IsArrayIndex(Environment* env, Local<Value> p) { | ||
| 911 | + Local<Context> context = env->context(); | ||
| 912 | + Local<String> p_str = p->ToString(context).ToLocalChecked(); | ||
| 913 | + double n_dbl = static_cast<double>(p_str->NumberValue(context).FromJust()); | ||
| 914 | + Local<Number> n = Number::New(env->isolate(), n_dbl); | ||
| 915 | + Local<String> cmp_str = n->ToString(context).ToLocalChecked(); | ||
| 916 | + if (!p_str->Equals(context, cmp_str).FromJust()) { | ||
| 917 | + return false; | ||
| 918 | + } | ||
| 919 | + if (n_dbl == 0 && std::signbit(n_dbl) == false) { | ||
| 920 | + return true; | ||
| 921 | + } | ||
| 922 | + Local<Integer> cmp_integer; | ||
| 923 | + if (!n->ToInteger(context).ToLocal(&cmp_integer)) { | ||
| 924 | + return false; | ||
| 925 | + } | ||
| 926 | + return n_dbl > 0 && n_dbl < (2 ^ 32) - 1; | ||
| 927 | + } | ||
| 928 | + | ||
| 911 | 929 | Maybe<URL> ResolveExportsTarget(Environment* env, | |
| 912 | 930 | const URL& pjson_url, | |
| 913 | 931 | Local<Value> target, | |
@@ -953,44 +971,50 @@ Maybe<URL> ResolveExportsTarget(Environment* env, | |||
| 953 | 971 | return Nothing<URL>(); | |
| 954 | 972 | } else if (target->IsObject()) { | |
| 955 | 973 | Local<Object> target_obj = target.As<Object>(); | |
| 956 | - bool matched = false; | ||
| 974 | + Local<Array> target_obj_keys = | ||
| 975 | + target_obj->GetOwnPropertyNames(context).ToLocalChecked(); | ||
| 957 | 976 | Local<Value> conditionalTarget; | |
| 958 | - if (env->options()->experimental_conditional_exports && | ||
| 959 | - target_obj->HasOwnProperty(context, env->node_string()).FromJust()) { | ||
| 960 | - matched = true; | ||
| 961 | - conditionalTarget = | ||
| 962 | - target_obj->Get(context, env->node_string()).ToLocalChecked(); | ||
| 963 | - Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url, | ||
| 964 | - conditionalTarget, subpath, pkg_subpath, base, false); | ||
| 965 | - if (!resolved.IsNothing()) { | ||
| 966 | - ProcessEmitExperimentalWarning(env, "Conditional exports"); | ||
| 967 | - return resolved; | ||
| 977 | + bool matched = false; | ||
| 978 | + for (uint32_t i = 0; i < target_obj_keys->Length(); ++i) { | ||
| 979 | + Local<Value> key = | ||
| 980 | + target_obj_keys->Get(context, i).ToLocalChecked(); | ||
| 981 | + if (IsArrayIndex(env, key)) { | ||
| 982 | + const std::string msg = "Invalid package config for " + | ||
| 983 | + pjson_url.ToFilePath() + ", \"exports\" cannot contain numeric " + | ||
| 984 | + "property keys."; | ||
| 985 | + node::THROW_ERR_INVALID_PACKAGE_CONFIG(env, msg.c_str()); | ||
| 986 | + return Nothing<URL>(); | ||
| 968 | 987 | } | |
| 969 | 988 | } | |
| 970 | - if (env->options()->experimental_conditional_exports && | ||
| 971 | - target_obj->HasOwnProperty(context, env->import_string()).FromJust()) { | ||
| 972 | - matched = true; | ||
| 973 | - conditionalTarget = | ||
| 974 | - target_obj->Get(context, env->import_string()).ToLocalChecked(); | ||
| 975 | - Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url, | ||
| 989 | + for (uint32_t i = 0; i < target_obj_keys->Length(); ++i) { | ||
| 990 | + Local<Value> key = target_obj_keys->Get(context, i).ToLocalChecked(); | ||
| 991 | + Utf8Value key_utf8(env->isolate(), | ||
| 992 | + key->ToString(context).ToLocalChecked()); | ||
| 993 | + std::string key_str(*key_utf8, key_utf8.length()); | ||
| 994 | + if (key_str == "node" || key_str == "import") { | ||
| 995 | + if (!env->options()->experimental_conditional_exports) continue; | ||
| 996 | + matched = true; | ||
| 997 | + conditionalTarget = target_obj->Get(context, key).ToLocalChecked(); | ||
| 998 | + Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url, | ||
| 976 | 999 | conditionalTarget, subpath, pkg_subpath, base, false); | |
| 977 | - if (!resolved.IsNothing()) { | ||
| 978 | - return resolved; | ||
| 979 | - } | ||
| 980 | - } | ||
| 981 | - if (target_obj->HasOwnProperty(context, env->default_string()).FromJust()) { | ||
| 982 | - matched = true; | ||
| 983 | - conditionalTarget = | ||
| 984 | - target_obj->Get(context, env->default_string()).ToLocalChecked(); | ||
| 985 | - Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url, | ||
| 1000 | + if (!resolved.IsNothing()) { | ||
| 1001 | + ProcessEmitExperimentalWarning(env, "Conditional exports"); | ||
| 1002 | + return resolved; | ||
| 1003 | + } | ||
| 1004 | + } else if (key_str == "default") { | ||
| 1005 | + matched = true; | ||
| 1006 | + conditionalTarget = target_obj->Get(context, key).ToLocalChecked(); | ||
| 1007 | + Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url, | ||
| 986 | 1008 | conditionalTarget, subpath, pkg_subpath, base, false); | |
| 987 | - if (!resolved.IsNothing()) { | ||
| 988 | - return resolved; | ||
| 1009 | + if (!resolved.IsNothing()) { | ||
| 1010 | + ProcessEmitExperimentalWarning(env, "Conditional exports"); | ||
| 1011 | + return resolved; | ||
| 1012 | + } | ||
| 989 | 1013 | } | |
| 990 | 1014 | } | |
| 991 | 1015 | if (matched && throw_invalid) { | |
| 992 | 1016 | Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url, | |
| 993 | - conditionalTarget, subpath, pkg_subpath, base, true); | ||
| 1017 | + conditionalTarget, subpath, pkg_subpath, base, true); | ||
| 994 | 1018 | CHECK(resolved.IsNothing()); | |
| 995 | 1019 | return Nothing<URL>(); | |
| 996 | 1020 | } | |
@@ -1013,8 +1037,8 @@ Maybe<bool> IsConditionalExportsMainSugar(Environment* env, | |||
| 1013 | 1037 | exports_obj->GetOwnPropertyNames(context).ToLocalChecked(); | |
| 1014 | 1038 | bool isConditionalSugar = false; | |
| 1015 | 1039 | for (uint32_t i = 0; i < keys->Length(); ++i) { | |
| 1016 | - Local<String> key = keys->Get(context, i).ToLocalChecked().As<String>(); | ||
| 1017 | - Utf8Value key_utf8(env->isolate(), key); | ||
| 1040 | + Local<Value> key = keys->Get(context, i).ToLocalChecked(); | ||
| 1041 | + Utf8Value key_utf8(env->isolate(), key->ToString(context).ToLocalChecked()); | ||
| 1018 | 1042 | bool curIsConditionalSugar = key_utf8.length() == 0 || key_utf8[0] != '.'; | |
| 1019 | 1043 | if (i == 0) { | |
| 1020 | 1044 | isConditionalSugar = curIsConditionalSugar; | |
@@ -1122,13 +1146,13 @@ Maybe<URL> PackageExportsResolve(Environment* env, | |||
| 1122 | 1146 | Local<Array> keys = | |
| 1123 | 1147 | exports_obj->GetOwnPropertyNames(context).ToLocalChecked(); | |
| 1124 | 1148 | for (uint32_t i = 0; i < keys->Length(); ++i) { | |
| 1125 | - Local<String> key = keys->Get(context, i).ToLocalChecked().As<String>(); | ||
| 1126 | - Utf8Value key_utf8(isolate, key); | ||
| 1149 | + Local<Value> key = keys->Get(context, i).ToLocalChecked(); | ||
| 1150 | + Utf8Value key_utf8(isolate, key->ToString(context).ToLocalChecked()); | ||
| 1127 | 1151 | std::string key_str(*key_utf8, key_utf8.length()); | |
| 1128 | 1152 | if (key_str.back() != '/') continue; | |
| 1129 | 1153 | if (pkg_subpath.substr(0, key_str.length()) == key_str && | |
| 1130 | 1154 | key_str.length() > best_match_str.length()) { | |
| 1131 | - best_match = key; | ||
| 1155 | + best_match = key->ToString(context).ToLocalChecked(); | ||
| 1132 | 1156 | best_match_str = key_str; | |
| 1133 | 1157 | } | |
| 1134 | 1158 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -125,6 +125,11 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js'; | |||
| 125 | 125 | 'ERR_MODULE_NOT_FOUND'); | |
| 126 | 126 | })); | |
| 127 | 127 | ||
| 128 | + // Package export with numeric index properties must throw a validation error | ||
| 129 | + loadFixture('pkgexports-numeric').catch(mustCall((err) => { | ||
| 130 | + strictEqual(err.code, 'ERR_INVALID_PACKAGE_CONFIG'); | ||
| 131 | + })); | ||
| 132 | + | ||
| 128 | 133 | // Sugar conditional exports main mixed failure case | |
| 129 | 134 | loadFixture('pkgexports-sugar-fail').catch(mustCall((err) => { | |
| 130 | 135 | strictEqual(err.code, 'ERR_INVALID_PACKAGE_CONFIG'); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments