@@ -1912,6 +1912,178 @@ static bool IsDocumentsPath(const std::string& path) {
19121912 }
19131913 }
19141914
1915+ // ── Blob URL support (e.g., blob:nativescript/<uuid>) ──
1916+ // Also useful for HMR updates where we can load a blob URL
1917+ // We retrieve the blob content from the global BLOB_STORE via URL.InternalAccessor.getData()
1918+ // and compile/execute it as an ES module.
1919+ if (!normalizedSpec.empty () && StartsWith (normalizedSpec, " blob:nativescript/" )) {
1920+ if (IsScriptLoadingLogEnabled ()) {
1921+ Log (@" [dyn-import][blob] trying blob URL %s " , normalizedSpec.c_str ());
1922+ }
1923+
1924+ // Call URL.InternalAccessor.getData(url) to retrieve the blob data
1925+ v8::TryCatch tc (isolate);
1926+ v8::Local<v8::Object> globalObj = context->Global ();
1927+
1928+ // Get URL constructor
1929+ v8::Local<v8::Value> urlCtorVal;
1930+ if (!globalObj->Get (context, tns::ToV8String (isolate, " URL" )).ToLocal (&urlCtorVal) || !urlCtorVal->IsFunction ()) {
1931+ if (IsScriptLoadingLogEnabled ()) {
1932+ Log (@" [dyn-import][blob] URL constructor not found" );
1933+ }
1934+ resolver->Reject (context, v8::Exception::Error (tns::ToV8String (isolate, " URL constructor not available" ))).FromMaybe (false );
1935+ return scope.Escape (resolver->GetPromise ());
1936+ }
1937+ v8::Local<v8::Object> urlCtor = urlCtorVal.As <v8::Object>();
1938+
1939+ // Get URL.InternalAccessor
1940+ v8::Local<v8::Value> internalAccessorVal;
1941+ if (!urlCtor->Get (context, tns::ToV8String (isolate, " InternalAccessor" )).ToLocal (&internalAccessorVal) || !internalAccessorVal->IsObject ()) {
1942+ if (IsScriptLoadingLogEnabled ()) {
1943+ Log (@" [dyn-import][blob] URL.InternalAccessor not found" );
1944+ }
1945+ resolver->Reject (context, v8::Exception::Error (tns::ToV8String (isolate, " URL.InternalAccessor not available" ))).FromMaybe (false );
1946+ return scope.Escape (resolver->GetPromise ());
1947+ }
1948+ v8::Local<v8::Object> internalAccessor = internalAccessorVal.As <v8::Object>();
1949+
1950+ // Get URL.InternalAccessor.getData function
1951+ v8::Local<v8::Value> getDataVal;
1952+ if (!internalAccessor->Get (context, tns::ToV8String (isolate, " getData" )).ToLocal (&getDataVal) || !getDataVal->IsFunction ()) {
1953+ if (IsScriptLoadingLogEnabled ()) {
1954+ Log (@" [dyn-import][blob] URL.InternalAccessor.getData not found" );
1955+ }
1956+ resolver->Reject (context, v8::Exception::Error (tns::ToV8String (isolate, " URL.InternalAccessor.getData not available" ))).FromMaybe (false );
1957+ return scope.Escape (resolver->GetPromise ());
1958+ }
1959+ v8::Local<v8::Function> getDataFn = getDataVal.As <v8::Function>();
1960+
1961+ // Call getData(url)
1962+ v8::Local<v8::Value> urlArg = tns::ToV8String (isolate, normalizedSpec.c_str ());
1963+ v8::Local<v8::Value> blobDataVal;
1964+ if (!getDataFn->Call (context, internalAccessor, 1 , &urlArg).ToLocal (&blobDataVal) || blobDataVal->IsNullOrUndefined ()) {
1965+ if (IsScriptLoadingLogEnabled ()) {
1966+ Log (@" [dyn-import][blob] blob not found in BLOB_STORE: %s " , normalizedSpec.c_str ());
1967+ }
1968+ std::string msg = " Blob not found: " + normalizedSpec;
1969+ resolver->Reject (context, v8::Exception::Error (tns::ToV8String (isolate, msg.c_str ()))).FromMaybe (false );
1970+ return scope.Escape (resolver->GetPromise ());
1971+ }
1972+
1973+ // blobDataVal should be {blob: Blob, type: string, ext: string}
1974+ // We need to get the text from the Blob
1975+ if (!blobDataVal->IsObject ()) {
1976+ if (IsScriptLoadingLogEnabled ()) {
1977+ Log (@" [dyn-import][blob] blob data is not an object" );
1978+ }
1979+ resolver->Reject (context, v8::Exception::Error (tns::ToV8String (isolate, " Invalid blob data" ))).FromMaybe (false );
1980+ return scope.Escape (resolver->GetPromise ());
1981+ }
1982+ v8::Local<v8::Object> blobData = blobDataVal.As <v8::Object>();
1983+
1984+ // Get the actual Blob object
1985+ v8::Local<v8::Value> blobVal;
1986+ if (!blobData->Get (context, tns::ToV8String (isolate, " blob" )).ToLocal (&blobVal) || !blobVal->IsObject ()) {
1987+ if (IsScriptLoadingLogEnabled ()) {
1988+ Log (@" [dyn-import][blob] blob property not found" );
1989+ }
1990+ resolver->Reject (context, v8::Exception::Error (tns::ToV8String (isolate, " Blob object not found" ))).FromMaybe (false );
1991+ return scope.Escape (resolver->GetPromise ());
1992+ }
1993+ v8::Local<v8::Object> blobObj = blobVal.As <v8::Object>();
1994+
1995+ // Call blob.text() to get the source code as a Promise
1996+ v8::Local<v8::Value> textFnVal;
1997+ if (!blobObj->Get (context, tns::ToV8String (isolate, " text" )).ToLocal (&textFnVal) || !textFnVal->IsFunction ()) {
1998+ if (IsScriptLoadingLogEnabled ()) {
1999+ Log (@" [dyn-import][blob] Blob.text() not available" );
2000+ }
2001+ resolver->Reject (context, v8::Exception::Error (tns::ToV8String (isolate, " Blob.text() not available" ))).FromMaybe (false );
2002+ return scope.Escape (resolver->GetPromise ());
2003+ }
2004+ v8::Local<v8::Function> textFn = textFnVal.As <v8::Function>();
2005+
2006+ v8::Local<v8::Value> textPromiseVal;
2007+ if (!textFn->Call (context, blobObj, 0 , nullptr ).ToLocal (&textPromiseVal) || !textPromiseVal->IsPromise ()) {
2008+ if (IsScriptLoadingLogEnabled ()) {
2009+ Log (@" [dyn-import][blob] Blob.text() did not return a Promise" );
2010+ }
2011+ resolver->Reject (context, v8::Exception::Error (tns::ToV8String (isolate, " Blob.text() failed" ))).FromMaybe (false );
2012+ return scope.Escape (resolver->GetPromise ());
2013+ }
2014+ v8::Local<v8::Promise> textPromise = textPromiseVal.As <v8::Promise>();
2015+
2016+ // Create data structure to pass to the callbacks
2017+ struct BlobImportData {
2018+ v8::Global<v8::Promise::Resolver> resolver;
2019+ v8::Global<v8::Context> ctx;
2020+ std::string blobUrl;
2021+ };
2022+ auto * data = new BlobImportData{
2023+ v8::Global<v8::Promise::Resolver>(isolate, resolver),
2024+ v8::Global<v8::Context>(isolate, context),
2025+ normalizedSpec
2026+ };
2027+
2028+ // Success callback: compile and execute the module
2029+ auto onFulfilled = [](const v8::FunctionCallbackInfo<v8::Value>& info) {
2030+ v8::Isolate* iso = info.GetIsolate ();
2031+ v8::HandleScope hs (iso);
2032+ if (!info.Data ()->IsExternal ()) return ;
2033+ auto * d = static_cast <BlobImportData*>(info.Data ().As <v8::External>()->Value ());
2034+ v8::Local<v8::Context> ctx = d->ctx .Get (iso);
2035+ v8::Local<v8::Promise::Resolver> res = d->resolver .Get (iso);
2036+
2037+ if (info.Length () < 1 || !info[0 ]->IsString ()) {
2038+ res->Reject (ctx, v8::Exception::Error (tns::ToV8String (iso, " Blob text is not a string" ))).FromMaybe (false );
2039+ delete d;
2040+ return ;
2041+ }
2042+
2043+ v8::String::Utf8Value codeUtf8 (iso, info[0 ]);
2044+ std::string code = *codeUtf8 ? *codeUtf8 : " " ;
2045+
2046+ if (IsScriptLoadingLogEnabled ()) {
2047+ Log (@" [dyn-import][blob] compiling blob module, code length=%zu " , code.size ());
2048+ }
2049+
2050+ // Compile and execute the module
2051+ v8::MaybeLocal<v8::Module> modMaybe = CompileModuleFromSource (iso, ctx, code, d->blobUrl );
2052+ v8::Local<v8::Module> mod;
2053+ if (!modMaybe.ToLocal (&mod)) {
2054+ res->Reject (ctx, v8::Exception::Error (tns::ToV8String (iso, " Failed to compile blob module" ))).FromMaybe (false );
2055+ delete d;
2056+ return ;
2057+ }
2058+
2059+ // Register the module
2060+ g_moduleRegistry[d->blobUrl].Reset (iso, mod);
2061+
2062+ res->Resolve (ctx, mod->GetModuleNamespace ()).FromMaybe (false );
2063+ delete d;
2064+ };
2065+
2066+ // Error callback
2067+ auto onRejected = [](const v8::FunctionCallbackInfo<v8::Value>& info) {
2068+ v8::Isolate* iso = info.GetIsolate ();
2069+ v8::HandleScope hs (iso);
2070+ if (!info.Data ()->IsExternal ()) return ;
2071+ auto * d = static_cast <BlobImportData*>(info.Data ().As <v8::External>()->Value ());
2072+ v8::Local<v8::Context> ctx = d->ctx .Get (iso);
2073+ v8::Local<v8::Promise::Resolver> res = d->resolver .Get (iso);
2074+ v8::Local<v8::Value> reason = info.Length () > 0 ? info[0 ] : v8::Exception::Error (tns::ToV8String (iso, " Blob text() failed" ));
2075+ res->Reject (ctx, reason).FromMaybe (false );
2076+ delete d;
2077+ };
2078+
2079+ v8::Local<v8::Function> onFulfilledFn = v8::Function::New (context, onFulfilled, v8::External::New (isolate, data)).ToLocalChecked ();
2080+ v8::Local<v8::Function> onRejectedFn = v8::Function::New (context, onRejected, v8::External::New (isolate, data)).ToLocalChecked ();
2081+
2082+ textPromise->Then (context, onFulfilledFn, onRejectedFn).FromMaybe (v8::Local<v8::Promise>());
2083+
2084+ return scope.Escape (resolver->GetPromise ());
2085+ }
2086+
19152087 // If spec is an HTTP(S) URL, try HTTP fetch+compile directly
19162088 if (!normalizedSpec.empty () && (StartsWith (normalizedSpec, " http://" ) || StartsWith (normalizedSpec, " https://" ))) {
19172089 if (IsScriptLoadingLogEnabled ()) {
0 commit comments