@@ -31,7 +31,7 @@ namespace tns {
3131// definitions live in ModuleInternalCallbacks.cpp; this header-style
3232// forward block lets HMRSupport.cpp call them without pulling the
3333// resolver header in (avoids a circular include).
34- void SetImportMap (const std::string& json );
34+ void SetImportMapEntries (const std::vector<std::pair<std:: string, std::string>>& entries );
3535void SetVolatilePatterns (const std::vector<std::string>& patterns);
3636std::vector<std::string> GetLoadedModuleUrls ();
3737
@@ -178,10 +178,53 @@ static bool IsSupportedDevSessionPlatform(const std::string& platform) {
178178 return platform == " android" ;
179179}
180180
181+ // Parse an import-map value (a JSON string OR a JS object of shape
182+ // `{ imports: { "<key>": "<url>", ... } }`) into flat (key → URL) entries using
183+ // V8's own JSON/object model. Returns true if it found an `imports` object
184+ // (even if empty); false if the value is unusable. Only flat string key→URL
185+ // mappings are honored; non-string import values are skipped.
186+ static bool ReadImportMapEntries (v8::Isolate* isolate,
187+ v8::Local<v8::Context> context,
188+ v8::Local<v8::Value> importMapValue,
189+ std::vector<std::pair<std::string, std::string>>* out) {
190+ v8::Local<v8::Value> mapVal = importMapValue;
191+ if (mapVal->IsString ()) {
192+ v8::Local<v8::Value> parsed;
193+ if (!v8::JSON::Parse (context, mapVal.As <v8::String>()).ToLocal (&parsed)) {
194+ return false ;
195+ }
196+ mapVal = parsed;
197+ }
198+ if (!mapVal->IsObject ()) return false ;
199+ v8::Local<v8::Object> mapObj = mapVal.As <v8::Object>();
200+
201+ v8::Local<v8::Value> importsVal;
202+ if (!mapObj->Get (context, ToV8String (isolate, " imports" )).ToLocal (&importsVal) ||
203+ !importsVal->IsObject ()) {
204+ return false ;
205+ }
206+ v8::Local<v8::Object> imports = importsVal.As <v8::Object>();
207+ v8::Local<v8::Array> keys;
208+ if (!imports->GetOwnPropertyNames (context).ToLocal (&keys)) return false ;
209+
210+ for (uint32_t i = 0 ; i < keys->Length (); ++i) {
211+ v8::Local<v8::Value> keyVal;
212+ if (!keys->Get (context, i).ToLocal (&keyVal)) continue ;
213+ v8::Local<v8::Value> valVal;
214+ if (!imports->Get (context, keyVal).ToLocal (&valVal) || !valVal->IsString ()) continue ;
215+ v8::String::Utf8Value keyUtf8 (isolate, keyVal);
216+ v8::String::Utf8Value valUtf8 (isolate, valVal);
217+ if (*keyUtf8 && *valUtf8) {
218+ out->emplace_back (std::string (*keyUtf8), std::string (*valUtf8));
219+ }
220+ }
221+ return true ;
222+ }
223+
181224// Apply the v8::Object payload of `__nsConfigureDevRuntime`: re-validate the
182- // `importMap` shape and serialize it back to JSON for `SetImportMap`. Parsing
183- // runs entirely in V8 (via `ConfigureDevRuntimeCallback`), so this is a thin
184- // wrapper over that shared validation .
225+ // `importMap` shape and read its entries via `ReadImportMapEntries` (V8-based
226+ // parsing) into the process-wide map. Mirrors the import-map handling in
227+ // `ConfigureDevRuntimeCallback` .
185228static bool ApplyDevRuntimeConfigObject (v8::Isolate* isolate,
186229 v8::Local<v8::Context> context,
187230 v8::Local<v8::Object> payload,
@@ -202,49 +245,20 @@ static bool ApplyDevRuntimeConfigObject(v8::Isolate* isolate,
202245 return false ;
203246 }
204247
205- // Use JSON.stringify on the importMap object — keeps the on-disk format
206- // identical to what `__nsConfigureRuntime` already accepts.
207- v8::Local<v8::Object> jsonObj;
208- v8::Local<v8::Value> globalJson;
209- if (!context->Global ()->Get (context, ToV8String (isolate, " JSON" )).ToLocal (&globalJson) ||
210- !globalJson->IsObject ()) {
211- if (errorMessage != nullptr ) {
212- *errorMessage = " [__nsStartDevSession] JSON global unavailable" ;
213- }
214- return false ;
215- }
216- jsonObj = globalJson.As <v8::Object>();
217-
218- v8::Local<v8::Value> stringifyFnVal;
219- if (!jsonObj->Get (context, ToV8String (isolate, " stringify" )).ToLocal (&stringifyFnVal) ||
220- !stringifyFnVal->IsFunction ()) {
221- if (errorMessage != nullptr ) {
222- *errorMessage = " [__nsStartDevSession] JSON.stringify unavailable" ;
223- }
224- return false ;
225- }
226-
227- v8::Local<v8::Function> stringifyFn = stringifyFnVal.As <v8::Function>();
228- v8::Local<v8::Value> args[] = {importMapValue};
229- v8::MaybeLocal<v8::Value> maybeJson = stringifyFn->Call (context, jsonObj, 1 , args);
230- v8::Local<v8::Value> jsonVal;
231- if (!maybeJson.ToLocal (&jsonVal) || !jsonVal->IsString ()) {
248+ std::vector<std::pair<std::string, std::string>> importEntries;
249+ if (!ReadImportMapEntries (isolate, context, importMapValue, &importEntries)) {
232250 if (errorMessage != nullptr ) {
233- *errorMessage = " [__nsStartDevSession] failed to serialize importMap" ;
251+ *errorMessage = " [__nsStartDevSession] failed to read importMap" ;
234252 }
235253 return false ;
236254 }
237-
238- v8::String::Utf8Value jsonUtf8 (isolate, jsonVal);
239- std::string importMapJson = *jsonUtf8 ? *jsonUtf8 : " " ;
240- if (importMapJson.empty ()) {
255+ if (importEntries.empty ()) {
241256 if (errorMessage != nullptr ) {
242257 *errorMessage = " [__nsStartDevSession] runtime config importMap was empty" ;
243258 }
244259 return false ;
245260 }
246-
247- SetImportMap (importMapJson);
261+ SetImportMapEntries (importEntries);
248262
249263 std::vector<std::string> patterns;
250264 v8::Local<v8::Value> volatilePatternsValue;
@@ -447,14 +461,10 @@ bool ApplyDevRuntimeConfigFromUrl(const std::string& url,
447461 return true ;
448462}
449463
450- // Native-side mirror of `__NS_HMR_BOOT_COMPLETE__`. Read by the
451- // runloop pump in `MaybePumpJSThreadDuringBoot` so its gate is a
452- // single relaxed atomic load on the HMR-time hot path.
453- static std::atomic<bool > g_devSessionBootComplete{false };
454-
455- static inline bool IsDevSessionBootComplete () {
456- return g_devSessionBootComplete.load (std::memory_order_relaxed);
457- }
464+ // The live "dev-session boot complete" signal is the JS global
465+ // __NS_HMR_BOOT_COMPLETE__, set by ApplyDevSessionGlobals /
466+ // SetDevSessionBootComplete below. (There is no native runloop pump on
467+ // Android — the main NativeScript isolate runs JS on the UI thread.)
458468
459469void ApplyDevSessionGlobals (v8::Isolate* isolate,
460470 v8::Local<v8::Context> context,
@@ -464,7 +474,6 @@ void ApplyDevSessionGlobals(v8::Isolate* isolate,
464474 SetBooleanGlobal (isolate, context, " __NS_HMR_BOOT_COMPLETE__" , false );
465475 SetBooleanGlobal (isolate, context, " __NS_HMR_CLIENT_ACTIVE__" , false );
466476 SetBooleanGlobal (isolate, context, " __NS_HMR_BROWSER_RUNTIME_CLIENT_ACTIVE__" , false );
467- g_devSessionBootComplete.store (false , std::memory_order_relaxed);
468477 if (IsScriptLoadingLogEnabled ()) {
469478 DEBUG_WRITE (" [dev-session] globals applied session=%s origin=%s ws=%s bootComplete=false" ,
470479 session.sessionId .c_str (), session.origin .c_str (),
@@ -476,7 +485,6 @@ void SetDevSessionBootComplete(v8::Isolate* isolate,
476485 v8::Local<v8::Context> context,
477486 bool value) {
478487 SetBooleanGlobal (isolate, context, " __NS_HMR_BOOT_COMPLETE__" , value);
479- g_devSessionBootComplete.store (value, std::memory_order_relaxed);
480488 if (IsScriptLoadingLogEnabled ()) {
481489 DEBUG_WRITE (" [dev-session] __NS_HMR_BOOT_COMPLETE__=%s" ,
482490 value ? " true" : " false" );
@@ -2439,32 +2447,12 @@ void ConfigureDevRuntimeCallback(const v8::FunctionCallbackInfo<v8::Value>& info
24392447 v8::Local<v8::Value> importMapVal;
24402448 if (config->Get (ctx, ToV8String (isolate, " importMap" )).ToLocal (&importMapVal) &&
24412449 !importMapVal->IsUndefined () && !importMapVal->IsNull ()) {
2442- std::string jsonStr;
2443- if (importMapVal->IsString ()) {
2444- v8::String::Utf8Value utf8 (isolate, importMapVal);
2445- if (*utf8) jsonStr = *utf8;
2446- } else if (importMapVal->IsObject ()) {
2447- v8::Local<v8::Value> jsonGlobalVal;
2448- if (ctx->Global ()->Get (ctx, ToV8String (isolate, " JSON" )).ToLocal (&jsonGlobalVal) &&
2449- jsonGlobalVal->IsObject ()) {
2450- v8::Local<v8::Object> jsonObj = jsonGlobalVal.As <v8::Object>();
2451- v8::Local<v8::Value> stringifyVal;
2452- if (jsonObj->Get (ctx, ToV8String (isolate, " stringify" )).ToLocal (&stringifyVal) &&
2453- stringifyVal->IsFunction ()) {
2454- v8::Local<v8::Function> stringify = stringifyVal.As <v8::Function>();
2455- v8::Local<v8::Value> args[] = {importMapVal};
2456- v8::Local<v8::Value> result;
2457- if (stringify->Call (ctx, jsonObj, 1 , args).ToLocal (&result) && result->IsString ()) {
2458- v8::String::Utf8Value utf8 (isolate, result);
2459- if (*utf8) jsonStr = *utf8;
2460- }
2461- }
2462- }
2463- }
2464- if (!jsonStr.empty ()) {
2465- SetImportMap (jsonStr);
2450+ std::vector<std::pair<std::string, std::string>> importEntries;
2451+ if (ReadImportMapEntries (isolate, ctx, importMapVal, &importEntries) &&
2452+ !importEntries.empty ()) {
2453+ SetImportMapEntries (importEntries);
24662454 if (logScriptLoading) {
2467- DEBUG_WRITE (" [__nsConfigureRuntime] import map set (%zu bytes )" , jsonStr .size ());
2455+ DEBUG_WRITE (" [__nsConfigureRuntime] import map set (%zu entries )" , importEntries .size ());
24682456 }
24692457 }
24702458 }
@@ -2909,6 +2897,22 @@ void ApplyStyleUpdateCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
29092897 }
29102898}
29112899
2900+ // Debug-only diagnostic: expose CanonicalizeHttpUrlKey to JS so the test harness
2901+ // can pin its identity behavior. Not part of the @nativescript/vite client API.
2902+ // The whole installer is gated on isDebuggable at the call site, so this never
2903+ // ships in release.
2904+ void CanonicalizeHttpUrlKeyCallback (const v8::FunctionCallbackInfo<v8::Value>& info) {
2905+ v8::Isolate* isolate = info.GetIsolate ();
2906+ v8::HandleScope scope (isolate);
2907+ if (info.Length () < 1 || !info[0 ]->IsString ()) {
2908+ info.GetReturnValue ().SetEmptyString ();
2909+ return ;
2910+ }
2911+ v8::String::Utf8Value u (isolate, info[0 ]);
2912+ std::string key = CanonicalizeHttpUrlKey (*u ? std::string (*u) : std::string ());
2913+ info.GetReturnValue ().Set (ToV8String (isolate, key.c_str ()));
2914+ }
2915+
29122916void GetLoadedModuleUrlsCallback (const v8::FunctionCallbackInfo<v8::Value>& info) {
29132917 v8::Isolate* isolate = info.GetIsolate ();
29142918 v8::HandleScope scope (isolate);
@@ -2962,6 +2966,7 @@ void InitializeHmrDevGlobals(v8::Isolate* isolate, v8::Local<v8::Context> contex
29622966 InstallGlobalFunction (isolate, context, " __nsReloadDevApp" , ReloadDevAppCallback);
29632967 InstallGlobalFunction (isolate, context, " __nsApplyStyleUpdate" , ApplyStyleUpdateCallback);
29642968 InstallGlobalFunction (isolate, context, " __nsGetLoadedModuleUrls" , GetLoadedModuleUrlsCallback);
2969+ InstallGlobalFunction (isolate, context, " __nsCanonicalizeHttpUrlKey" , CanonicalizeHttpUrlKeyCallback);
29652970}
29662971
29672972void CleanupHMRGlobals () {
0 commit comments