@@ -39,6 +39,7 @@ using v8::Module;
3939using v8::Nothing;
4040using v8::Number;
4141using v8::Object;
42+ using v8::Persistent;
4243using v8::PrimitiveArray;
4344using v8::Promise;
4445using v8::ScriptCompiler;
@@ -537,6 +538,7 @@ using Exists = PackageConfig::Exists;
537538using IsValid = PackageConfig::IsValid;
538539using HasMain = PackageConfig::HasMain;
539540using PackageType = PackageConfig::PackageType;
541+ using HasExports = PackageConfig::HasExports;
540542
541543Maybe<const PackageConfig*> GetPackageConfig (Environment* env,
542544 const std::string& path,
@@ -558,7 +560,8 @@ Maybe<const PackageConfig*> GetPackageConfig(Environment* env,
558560 if (source.IsNothing ()) {
559561 auto entry = env->package_json_cache .emplace (path,
560562 PackageConfig { Exists::No, IsValid::Yes, HasMain::No, " " ,
561- PackageType::None });
563+ PackageType::None, HasExports::No,
564+ Persistent<Value>() });
562565 return Just (&entry.first ->second );
563566 }
564567
@@ -578,7 +581,8 @@ Maybe<const PackageConfig*> GetPackageConfig(Environment* env,
578581 !pkg_json_v->ToObject (context).ToLocal (&pkg_json)) {
579582 env->package_json_cache .emplace (path,
580583 PackageConfig { Exists::Yes, IsValid::No, HasMain::No, " " ,
581- PackageType::None });
584+ PackageType::None, HasExports::No,
585+ Persistent<Value>() });
582586 std::string msg = " Invalid JSON in '" + path +
583587 " ' imported from " + base.ToFilePath ();
584588 node::THROW_ERR_INVALID_PACKAGE_CONFIG (env, msg.c_str ());
@@ -609,22 +613,23 @@ Maybe<const PackageConfig*> GetPackageConfig(Environment* env,
609613 }
610614
611615 Local<Value> exports_v;
616+ Persistent<Value> exports;
612617 if (pkg_json->Get (env->context (),
613618 env->exports_string ()).ToLocal (&exports_v) &&
614- (exports_v->IsObject () || exports_v->IsString () ||
615- exports_v->IsBoolean ())) {
616- Global<Value> exports;
619+ (exports_v->IsObject () ||
620+ (exports_v->IsBoolean () && exports_v->IsFalse ()))) {
617621 exports.Reset (env->isolate (), exports_v);
618622
619623 auto entry = env->package_json_cache .emplace (path,
620624 PackageConfig { Exists::Yes, IsValid::Yes, has_main, main_std,
621- pkg_type });
625+ pkg_type, HasExports::Yes, exports });
622626 return Just (&entry.first ->second );
623627 }
624628
625629 auto entry = env->package_json_cache .emplace (path,
626630 PackageConfig { Exists::Yes, IsValid::Yes, has_main, main_std,
627- pkg_type });
631+ pkg_type, HasExports::No,
632+ Persistent<Value>() });
628633 return Just (&entry.first ->second );
629634}
630635
@@ -646,7 +651,7 @@ Maybe<const PackageConfig*> GetPackageScopeConfig(Environment* env,
646651 if (pjson_url.path () == last_pjson_url.path ()) {
647652 auto entry = env->package_json_cache .emplace (pjson_url.ToFilePath (),
648653 PackageConfig { Exists::No, IsValid::Yes, HasMain::No, " " ,
649- PackageType::None });
654+ PackageType::None, HasExports::No, Persistent<Value>() });
650655 const PackageConfig* pcfg = &entry.first ->second ;
651656 return Just (pcfg);
652657 }
@@ -772,6 +777,20 @@ Maybe<URL> PackageMainResolve(Environment* env,
772777 const PackageConfig& pcfg,
773778 const URL& base) {
774779 if (pcfg.exists == Exists::Yes) {
780+ Local<Value> exports = pcfg.exports .Get (env->isolate ());
781+ if (pcfg.has_exports == HasExports::Yes && exports->IsObject ()) {
782+ Local<Object> exports_obj = exports.As <Object>();
783+ Local<String> dot_string = String::NewFromUtf8 (env->isolate (), " ." ,
784+ v8::NewStringType::kNormal ).ToLocalChecked ();
785+ auto dot_main =
786+ exports_obj->Get (env->context (), dot_string).ToLocalChecked ();
787+ if (dot_main->IsString ()) {
788+ Utf8Value main_utf8 (env->isolate (), dot_main.As <v8::String>());
789+ std::string main (*main_utf8, main_utf8.length ());
790+ URL main_url (" ./" + main, pjson_url);
791+ return FinalizeResolution (env, main_url, base);
792+ }
793+ }
775794 if (pcfg.has_main == HasMain::Yes) {
776795 URL resolved (pcfg.main , pjson_url);
777796 const std::string& path = resolved.ToFilePath ();
@@ -800,6 +819,66 @@ Maybe<URL> PackageMainResolve(Environment* env,
800819 return Nothing<URL>();
801820}
802821
822+ Maybe<URL> PackageExportsResolve (Environment* env,
823+ const URL& pjson_url,
824+ const std::string& pkg_subpath,
825+ const PackageConfig& pcfg,
826+ const URL& base) {
827+ Isolate* isolate = env->isolate ();
828+ Local<Context> context = env->context ();
829+ Local<Value> exports = pcfg.exports .Get (isolate);
830+ if (exports->IsObject ()) {
831+ Local<Object> exports_obj = exports.As <Object>();
832+ Local<String> subpath = String::NewFromUtf8 (isolate,
833+ pkg_subpath.c_str (), v8::NewStringType::kNormal ).ToLocalChecked ();
834+
835+ auto target = exports_obj->Get (context, subpath).ToLocalChecked ();
836+ // TODO(@guybedford): Target validation
837+ if (target->IsString ()) {
838+ Utf8Value target_utf8 (isolate, target.As <v8::String>());
839+ std::string target (*target_utf8, target_utf8.length ());
840+ if (target.substr (0 , 2 ) == " ./" ) {
841+ URL target_url (target, pjson_url);
842+ return FinalizeResolution (env, target_url, base);
843+ }
844+ }
845+
846+ Local<String> best_match;
847+ std::string best_match_str = " " ;
848+ Local<Array> keys =
849+ exports_obj->GetOwnPropertyNames (context).ToLocalChecked ();
850+ for (uint32_t i = 0 ; i < keys->Length (); ++i) {
851+ Local<String> key = keys->Get (context, i).ToLocalChecked ().As <String>();
852+ Utf8Value key_utf8 (isolate, key);
853+ std::string key_str (*key_utf8, key_utf8.length ());
854+ if (key_str.back () != ' /' ) continue ;
855+ if (pkg_subpath.substr (0 , key_str.length ()) == key_str &&
856+ key_str.length () > best_match_str.length ()) {
857+ best_match = key;
858+ best_match_str = key_str;
859+ }
860+ }
861+
862+ if (best_match_str.length () > 0 ) {
863+ auto target = exports_obj->Get (context, best_match).ToLocalChecked ();
864+ if (target->IsString ()) {
865+ Utf8Value target_utf8 (isolate, target.As <v8::String>());
866+ std::string target (*target_utf8, target_utf8.length ());
867+ if (target.back () == ' /' && target.substr (0 , 2 ) == " ./" ) {
868+ std::string subpath = pkg_subpath.substr (best_match_str.length ());
869+ URL target_url (target + subpath, pjson_url);
870+ return FinalizeResolution (env, target_url, base);
871+ }
872+ }
873+ }
874+ }
875+ std::string msg = " Package exports for '" +
876+ URL (" ." , pjson_url).ToFilePath () + " ' do not define a '" + pkg_subpath +
877+ " ' subpath, imported from " + base.ToFilePath ();
878+ node::THROW_ERR_MODULE_NOT_FOUND (env, msg.c_str ());
879+ return Nothing<URL>();
880+ }
881+
803882Maybe<URL> PackageResolve (Environment* env,
804883 const std::string& specifier,
805884 const URL& base) {
@@ -847,7 +926,12 @@ Maybe<URL> PackageResolve(Environment* env,
847926 if (!pkg_subpath.length ()) {
848927 return PackageMainResolve (env, pjson_url, *pcfg.FromJust (), base);
849928 } else {
850- return FinalizeResolution (env, URL (pkg_subpath, pjson_url), base);
929+ if (pcfg.FromJust ()->has_exports == HasExports::Yes) {
930+ return PackageExportsResolve (env, pjson_url, pkg_subpath,
931+ *pcfg.FromJust (), base);
932+ } else {
933+ return FinalizeResolution (env, URL (pkg_subpath, pjson_url), base);
934+ }
851935 }
852936 CHECK (false );
853937 // Cross-platform root check.
0 commit comments