@@ -1402,6 +1402,91 @@ void KickstartHmrPrefetchCallback(const v8::FunctionCallbackInfo<v8::Value>& inf
14021402 buildResult (ok, fetched, elapsedMs);
14031403}
14041404
1405+ // `__NS_DEV__.seedModuleBodies(entries)` — batch prewarm-cache seeding.
1406+ //
1407+ // The JS bootstrap downloads `/__ns_dev__/boot-archive` (NDJSON of
1408+ // {url, body} lines) and hands the parsed entries here. Each entry lands in
1409+ // the one-shot prewarm cache (`g_prefetchCache`, consumed by `HttpFetchText`
1410+ // during V8's synchronous module walk), behind the same gates as a kickstart
1411+ // fetch. Mechanism only: the dev server computed the closure and produced
1412+ // the bodies; the runtime just stores them.
1413+ //
1414+ // Accepts Array<{ url, body }>. Returns { ok, seeded, bytes }; callers fall
1415+ // back to `kickstartPrefetch(urls)` when nothing was seeded.
1416+ void SeedModuleBodiesCallback (const v8::FunctionCallbackInfo<v8::Value>& info) {
1417+ v8::Isolate* isolate = info.GetIsolate ();
1418+ v8::HandleScope scope (isolate);
1419+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext ();
1420+
1421+ auto buildResult = [&](bool ok, size_t seeded, size_t bytes) {
1422+ v8::Local<v8::Object> result = v8::Object::New (isolate);
1423+ (void )result->Set (ctx, ToV8String (isolate, " ok" ), v8::Boolean::New (isolate, ok));
1424+ (void )result->Set (ctx, ToV8String (isolate, " seeded" ),
1425+ v8::Integer::NewFromUnsigned (isolate, (uint32_t )seeded));
1426+ (void )result->Set (ctx, ToV8String (isolate, " bytes" ),
1427+ v8::Number::New (isolate, (double )bytes));
1428+ info.GetReturnValue ().Set (result);
1429+ };
1430+
1431+ if (info.Length () < 1 || !info[0 ]->IsArray ()) {
1432+ if (tns::IsScriptLoadingLogEnabled ()) {
1433+ DEBUG_WRITE (" [__NS_DEV__.seedModuleBodies] expected Array<{url, body}>" );
1434+ }
1435+ buildResult (false , 0 , 0 );
1436+ return ;
1437+ }
1438+
1439+ v8::Local<v8::Array> arr = info[0 ].As <v8::Array>();
1440+ const uint32_t len = arr->Length ();
1441+ v8::Local<v8::String> urlKey = ToV8String (isolate, " url" );
1442+ v8::Local<v8::String> bodyKey = ToV8String (isolate, " body" );
1443+
1444+ size_t seeded = 0 ;
1445+ size_t bytes = 0 ;
1446+ for (uint32_t i = 0 ; i < len; i++) {
1447+ v8::Local<v8::Value> elemVal;
1448+ if (!arr->Get (ctx, i).ToLocal (&elemVal) || !elemVal->IsObject ()) continue ;
1449+ v8::Local<v8::Object> elem = elemVal.As <v8::Object>();
1450+
1451+ v8::Local<v8::Value> urlVal;
1452+ if (!elem->Get (ctx, urlKey).ToLocal (&urlVal) || !urlVal->IsString ()) continue ;
1453+ v8::String::Utf8Value urlU8 (isolate, urlVal);
1454+ if (!*urlU8) continue ;
1455+ std::string url (*urlU8);
1456+ if (url.empty ()) continue ;
1457+
1458+ // Same gates a kickstart fetch passes before it may populate the
1459+ // prewarm cache (scheme, JS-source shape, remote-URL allowlist).
1460+ if (!StartsWith (url, " http://" ) && !StartsWith (url, " https://" )) continue ;
1461+ if (!LooksLikeJsSourceUrl (url)) continue ;
1462+ if (!IsRemoteUrlAllowed (url)) continue ;
1463+
1464+ v8::Local<v8::Value> bodyVal;
1465+ if (!elem->Get (ctx, bodyKey).ToLocal (&bodyVal) || !bodyVal->IsString ()) continue ;
1466+ v8::String::Utf8Value bodyU8 (isolate, bodyVal);
1467+ if (!*bodyU8) continue ;
1468+ std::string body (*bodyU8);
1469+ if (body.empty ()) continue ;
1470+
1471+ const size_t bodySize = body.size ();
1472+ // Overwrite unconditionally — the archive body is the authoritative
1473+ // fresh copy, mirroring the kickstart's overwrite semantics.
1474+ {
1475+ std::lock_guard<std::mutex> lock (g_prefetchMutex);
1476+ g_prefetchCache[url] = std::move (body);
1477+ }
1478+ ++seeded;
1479+ bytes += bodySize;
1480+ }
1481+
1482+ if (tns::IsScriptLoadingLogEnabled ()) {
1483+ DEBUG_WRITE (" [__NS_DEV__.seedModuleBodies] seeded=%lu bytes=%lu of %u entries" ,
1484+ (unsigned long )seeded, (unsigned long )bytes, len);
1485+ }
1486+
1487+ buildResult (seeded > 0 , seeded, bytes);
1488+ }
1489+
14051490void GetLoadedModuleUrlsCallback (const v8::FunctionCallbackInfo<v8::Value>& info) {
14061491 v8::Isolate* isolate = info.GetIsolate ();
14071492 v8::HandleScope scope (isolate);
@@ -1468,6 +1553,7 @@ void InitializeHmrDevGlobals(v8::Isolate* isolate, v8::Local<v8::Context> contex
14681553 InstallDevFunction (isolate, context, dev, " configureRuntime" , ConfigureDevRuntimeCallback);
14691554 InstallDevFunction (isolate, context, dev, " invalidateModules" , InvalidateModulesCallback);
14701555 InstallDevFunction (isolate, context, dev, " kickstartPrefetch" , KickstartHmrPrefetchCallback);
1556+ InstallDevFunction (isolate, context, dev, " seedModuleBodies" , SeedModuleBodiesCallback);
14711557 InstallDevFunction (isolate, context, dev, " getLoadedModuleUrls" , GetLoadedModuleUrlsCallback);
14721558 InstallDevFunction (isolate, context, dev, " setDevBootComplete" , SetDevBootCompleteCallback);
14731559
0 commit comments