diff --git a/eng/testing/scenarios/BuildWasmAppsJobsListCoreCLR.txt b/eng/testing/scenarios/BuildWasmAppsJobsListCoreCLR.txt
index 4f567f5cba1082..8cf543448b73fe 100644
--- a/eng/testing/scenarios/BuildWasmAppsJobsListCoreCLR.txt
+++ b/eng/testing/scenarios/BuildWasmAppsJobsListCoreCLR.txt
@@ -12,3 +12,4 @@ Wasm.Build.Tests.WasmRunOutOfAppBundleTests
Wasm.Build.Tests.WasmTemplateTests
Wasm.Build.Tests.MaxParallelDownloadsTests
Wasm.Build.Tests.LibraryInitializerTests
+Wasm.Build.Tests.DownloadThenInitTests
diff --git a/src/mono/wasm/Wasm.Build.Tests/DownloadThenInitTests.cs b/src/mono/wasm/Wasm.Build.Tests/DownloadThenInitTests.cs
index 6d8f0a6167602b..6d4e3c87d17bc6 100644
--- a/src/mono/wasm/Wasm.Build.Tests/DownloadThenInitTests.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/DownloadThenInitTests.cs
@@ -31,6 +31,15 @@ public async Task NoResourcesReFetchedAfterDownloadFinished(Configuration config
var resultTestOutput = result.TestOutput.ToList();
int index = resultTestOutput.FindIndex(s => s.Contains("download finished"));
Assert.True(index > 0); // number of fetched resources cannot be 0
+
+ // Verify onConfigLoaded was called during download()
+ Assert.Contains(resultTestOutput, s => s.Contains("onConfigLoaded was called during download"));
+
+ // Verify resources were actually fetched during download
+ var fetchesDuringDownload = resultTestOutput.Take(index + 1).Where(s => s.StartsWith("fetching")).ToList();
+ Assert.True(fetchesDuringDownload.Count > 0, "Expected resources to be fetched during download()");
+
+ // Verify no resources were re-fetched during create()
var afterDownload = resultTestOutput.Skip(index + 1).Where(s => s.StartsWith("fetching")).ToList();
if (afterDownload.Count > 0)
{
@@ -39,5 +48,39 @@ public async Task NoResourcesReFetchedAfterDownloadFinished(Configuration config
if (reFetchedResources.Any())
Assert.Fail($"Resources should not be fetched twice. Re-fetched on init: {string.Join(", ", reFetchedResources)}");
}
+
+ // Verify create() completed successfully
+ Assert.Contains(resultTestOutput, s => s.Contains("create finished"));
+ }
+
+ [Theory]
+ [InlineData(Configuration.Debug)]
+ [InlineData(Configuration.Release)]
+ public async Task HttpCacheOnlyThenCreateWorks(Configuration config)
+ {
+ ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.WasmBasicTestApp, "DownloadThenInitHttpCacheOnly");
+ BuildProject(info, config);
+ BrowserRunOptions options = new(config, TestScenario: "DownloadThenInitHttpCacheOnly");
+ RunResult result = await RunForBuildWithDotnetRun(options);
+ var resultTestOutput = result.TestOutput.ToList();
+ int index = resultTestOutput.FindIndex(s => s.Contains("download finished"));
+ Assert.True(index > 0);
+
+ // Verify loadBootResource was called during download(true)
+ Assert.Contains(resultTestOutput, s => s.Contains("loadBootResource was called"));
+
+ // Verify onConfigLoaded was called during download(true) — config init runs before prefetch
+ Assert.Contains(resultTestOutput, s => s.Contains("onConfigLoaded was called during download"));
+
+ // Verify prefetch requests happened during download
+ var fetchesDuringDownload = resultTestOutput.Take(index + 1).Where(s => s.StartsWith("fetching")).ToList();
+ Assert.True(fetchesDuringDownload.Count > 0, "Expected prefetch requests during download(true)");
+
+ // Verify resource fetches happened during create() (httpCacheOnly doesn't load into memory)
+ var fetchesAfterDownload = resultTestOutput.Skip(index + 1).Where(s => s.StartsWith("fetching")).ToList();
+ Assert.True(fetchesAfterDownload.Count > 0, "Expected resource fetches during create() after httpCacheOnly download");
+
+ // Verify create() completed successfully
+ Assert.Contains(resultTestOutput, s => s.Contains("create finished"));
}
}
diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js
index 84c77206bcf6fe..b7349232ba97f6 100644
--- a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js
+++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js
@@ -117,12 +117,50 @@ switch (testCase) {
.withInterpreterPgo(true);
break;
case "DownloadThenInit":
+ let dtConfigLoadedCalled = false;
+ dotnet.withModuleConfig({
+ onConfigLoaded: () => {
+ dtConfigLoadedCalled = true;
+ testOutput("onConfigLoaded called");
+ }
+ });
const originalFetch = globalThis.fetch;
globalThis.fetch = (url, fetchArgs) => {
testOutput("fetching " + url);
return originalFetch(url, fetchArgs);
};
await dotnet.download();
+ if (dtConfigLoadedCalled) {
+ testOutput("onConfigLoaded was called during download");
+ }
+ testOutput("download finished");
+ break;
+ case "DownloadThenInitHttpCacheOnly":
+ let loadBootResourceCalled = false;
+ let hcConfigLoadedCalled = false;
+ dotnet.withResourceLoader((type, name, defaultUri, integrity, behavior) => {
+ testOutput("loadBootResource " + type + " " + name);
+ loadBootResourceCalled = true;
+ return defaultUri;
+ });
+ dotnet.withModuleConfig({
+ onConfigLoaded: () => {
+ hcConfigLoadedCalled = true;
+ testOutput("onConfigLoaded called");
+ }
+ });
+ const originalFetch3 = globalThis.fetch;
+ globalThis.fetch = (url, fetchArgs) => {
+ testOutput("fetching " + url);
+ return originalFetch3(url, fetchArgs);
+ };
+ await dotnet.download(true);
+ if (loadBootResourceCalled) {
+ testOutput("loadBootResource was called");
+ }
+ if (hcConfigLoadedCalled) {
+ testOutput("onConfigLoaded was called during download");
+ }
testOutput("download finished");
break;
case "MaxParallelDownloads":
@@ -266,6 +304,10 @@ try {
exit(0);
break;
case "DownloadThenInit":
+ case "DownloadThenInitHttpCacheOnly":
+ testOutput("create finished");
+ exit(0);
+ break;
case "MaxParallelDownloads":
exit(0);
break;
diff --git a/src/native/libs/Common/JavaScript/loader/assets.ts b/src/native/libs/Common/JavaScript/loader/assets.ts
index aeb5c8d7244759..5fa921c55be5da 100644
--- a/src/native/libs/Common/JavaScript/loader/assets.ts
+++ b/src/native/libs/Common/JavaScript/loader/assets.ts
@@ -1,10 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import type { JsModuleExports, JsAsset, AssemblyAsset, WasmAsset, IcuAsset, EmscriptenModuleInternal, WebAssemblyBootResourceType, AssetEntryInternal, PromiseCompletionSource, LoadBootResourceCallback, InstantiateWasmSuccessCallback, SymbolsAsset } from "./types";
+import type { JsModuleExports, JsAsset, AssemblyAsset, WasmAsset, IcuAsset, EmscriptenModuleInternal, WebAssemblyBootResourceType, AssetEntryInternal, PromiseCompletionSource, LoadBootResourceCallback, InstantiateWasmSuccessCallback, SymbolsAsset, AssetBehaviors } from "./types";
import { dotnetAssert, dotnetLogger, dotnetInternals, dotnetBrowserHostExports, dotnetUpdateInternals, Module, dotnetDiagnosticsExports, dotnetNativeBrowserExports, dotnetApi } from "./cross-module";
-import { ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_NODE, browserVirtualAppBase } from "./per-module";
+import { ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_WEB, browserVirtualAppBase } from "./per-module";
import { createPromiseCompletionSource, delay } from "./promise-completion-source";
import { locateFile, makeURLAbsoluteWithApplicationBase } from "./bootstrap";
import { fetchLike, responseLike } from "./polyfills";
@@ -585,3 +585,125 @@ const leaveAfterInstantiation: { [key: string]: number | undefined } = {
"dotnetwasm": 1,
"webcil": 1,
};
+
+// Fetches all data resources into the browser HTTP cache without loading them into memory.
+// JS modules get hints instead of fetch() since they use import().
+export async function prefetchAllResources(): Promise {
+ const resources = loaderConfig.resources;
+ if (!resources) return;
+
+ const maxParallel = loaderConfig.maxParallelDownloads ?? 16;
+ const pending: Promise[] = [];
+ const queue: { url: string; hash?: string | null | ""; name: string; behavior: AssetBehaviors }[] = [];
+
+ function enqueueAsset(asset: { name?: string; resolvedUrl?: string; hash?: string | null | "" }, behavior: AssetBehaviors): void {
+ if (!asset.resolvedUrl && asset.name) {
+ asset.resolvedUrl = locateFile(asset.name);
+ }
+ if (asset.resolvedUrl && asset.name) {
+ queue.push({ url: asset.resolvedUrl, hash: asset.hash, name: asset.name, behavior });
+ }
+ }
+
+ // Data assets: fetch and discard
+ if (resources.coreAssembly) resources.coreAssembly.forEach(a => enqueueAsset(a, "assembly"));
+ if (resources.assembly) resources.assembly.forEach(a => enqueueAsset(a, "assembly"));
+ if (resources.coreVfs) resources.coreVfs.forEach(a => enqueueAsset(a, "vfs"));
+ if (resources.vfs) resources.vfs.forEach(a => enqueueAsset(a, "vfs"));
+ if (resources.icu) resources.icu.forEach(a => enqueueAsset(a, "icu"));
+ if (resources.wasmNative) resources.wasmNative.forEach(a => enqueueAsset(a, "dotnetwasm"));
+ if (resources.corePdb) resources.corePdb.forEach(a => enqueueAsset(a, "pdb"));
+ if (resources.pdb) resources.pdb.forEach(a => enqueueAsset(a, "pdb"));
+ if (resources.wasmSymbols) resources.wasmSymbols.forEach(a => enqueueAsset(a, "symbols"));
+ // Satellite resources
+ if (loaderConfig.loadAllSatelliteResources && resources.satelliteResources) {
+ for (const culture of Object.keys(resources.satelliteResources)) {
+ for (const asset of resources.satelliteResources[culture]) {
+ if (!asset.resolvedUrl && asset.name) {
+ asset.resolvedUrl = locateFile(`${culture}/${asset.name}`);
+ }
+ enqueueAsset(asset, "assembly");
+ }
+ }
+ }
+
+ // JS modules: add in web environments
+ prefetchJSModuleLinks([
+ ...(resources.jsModuleNative || []),
+ ...(resources.jsModuleRuntime || []),
+ ...(resources.jsModuleDiagnostics || []),
+ ...(resources.jsModuleWorker || []),
+ ...(resources.modulesAfterConfigLoaded || []),
+ ...(resources.modulesAfterRuntimeReady || []),
+ ]);
+
+ // Fetch data assets with throttling
+ let index = 0;
+ async function worker(): Promise {
+ while (index < queue.length) {
+ const item = queue[index++];
+ try {
+ await prefetchUrl(item.url, item.hash, item.name, item.behavior);
+ } catch {
+ // Best-effort cache warming — individual failures are non-fatal.
+ // The subsequent create() call will handle retries and error reporting.
+ }
+ }
+ }
+ const workerCount = Math.min(maxParallel, queue.length);
+ for (let i = 0; i < workerCount; i++) {
+ pending.push(worker());
+ }
+ await Promise.all(pending);
+}
+
+export function prefetchJSModuleLinks(modules: JsAsset[]): void {
+ if (!ENVIRONMENT_IS_WEB) return;
+ const document = globalThis.document;
+ const documentHead = document?.head;
+ if (!document || !documentHead) return;
+ for (const mod of modules) {
+ if (!mod.resolvedUrl && mod.name) {
+ mod.resolvedUrl = locateFile(mod.name, true);
+ }
+ if (mod.resolvedUrl) {
+ const link = document.createElement("link");
+ link.rel = "prefetch";
+ link.href = mod.resolvedUrl;
+ link.as = "script";
+ documentHead.appendChild(link);
+ }
+ }
+}
+
+async function prefetchUrl(url: string, hash?: string | null | "", name?: string, behavior?: AssetBehaviors): Promise {
+ // Respect loadBootResourceCallback so prefetch goes through the same custom loader as create()
+ if (typeof loadBootResourceCallback === "function" && behavior && name) {
+ const blazorType = behaviorToBlazorAssetTypeMap[behavior];
+ if (blazorType) {
+ const customLoadResult = loadBootResourceCallback(blazorType, name, url, hash ?? "", behavior);
+ if (typeof customLoadResult === "string") {
+ url = makeURLAbsoluteWithApplicationBase(customLoadResult);
+ } else if (customLoadResult != null && typeof customLoadResult === "object") {
+ // Custom loader returned a Response promise — await and discard
+ const response = await (customLoadResult as Promise);
+ if (typeof response?.arrayBuffer === "function") {
+ await response.arrayBuffer();
+ }
+ return;
+ }
+ }
+ }
+ const fetchOptions: RequestInit = {};
+ if (!loaderConfig.disableNoCacheFetch) {
+ fetchOptions.cache = "no-cache";
+ }
+ if (!loaderConfig.disableIntegrityCheck && hash) {
+ fetchOptions.integrity = hash;
+ }
+ const response = await fetchLike(url, fetchOptions);
+ if (response.ok) {
+ // Read the body to ensure the response is fully received into cache
+ await response.arrayBuffer();
+ }
+}
diff --git a/src/native/libs/Common/JavaScript/loader/dotnet.d.ts b/src/native/libs/Common/JavaScript/loader/dotnet.d.ts
index 0f7e61e51c8715..60946334f7e9f4 100644
--- a/src/native/libs/Common/JavaScript/loader/dotnet.d.ts
+++ b/src/native/libs/Common/JavaScript/loader/dotnet.d.ts
@@ -106,8 +106,12 @@ interface DotnetHostBuilder {
withResourceLoader(loadBootResource?: LoadBootResourceCallback): DotnetHostBuilder;
/**
* Downloads all the assets but doesn't create the runtime instance.
+ * @param httpCacheOnly If true, resources are only fetched into the browser HTTP cache
+ * and discarded. A subsequent create() call will re-fetch from cache and do full init.
+ * If false (default), resources are downloaded and loaded into WASM memory, so that
+ * a subsequent create() call only needs to initialize the managed runtime.
*/
- download(): Promise;
+ download(httpCacheOnly?: boolean): Promise;
/**
* Starts the runtime and returns promise of the API object.
*/
diff --git a/src/native/libs/Common/JavaScript/loader/host-builder.ts b/src/native/libs/Common/JavaScript/loader/host-builder.ts
index eebdaebdbb4732..8000f1282c0454 100644
--- a/src/native/libs/Common/JavaScript/loader/host-builder.ts
+++ b/src/native/libs/Common/JavaScript/loader/host-builder.ts
@@ -102,10 +102,10 @@ export class HostBuilder implements DotnetHostBuilder {
return this;
}
- async download(): Promise {
+ async download(httpCacheOnly?: boolean): Promise {
try {
validateLoaderConfig();
- return createRuntime(true);
+ return createRuntime(true, httpCacheOnly ?? false);
} catch (err) {
exit(1, err);
throw err;
diff --git a/src/native/libs/Common/JavaScript/loader/run.ts b/src/native/libs/Common/JavaScript/loader/run.ts
index 25347cedbc061e..230f54fca8e150 100644
--- a/src/native/libs/Common/JavaScript/loader/run.ts
+++ b/src/native/libs/Common/JavaScript/loader/run.ts
@@ -1,59 +1,108 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import type { JsModuleExports, EmscriptenModuleInternal, JsAsset } from "./types";
+import type { JsModuleExports, EmscriptenModuleInternal, JsAsset, PromiseCompletionSource } from "./types";
import { dotnetAssert, dotnetInternals, dotnetBrowserHostExports, Module } from "./cross-module";
import { exit, runtimeState } from "./exit";
import { createPromiseCompletionSource } from "./promise-completion-source";
import { getIcuResourceName } from "./icu";
import { loaderConfig, validateLoaderConfig } from "./config";
-import { fetchAssembly, fetchIcu, fetchNativeSymbols, fetchPdb, fetchSatelliteAssemblies, fetchVfs, fetchMainWasm, loadDotnetModule, loadJSModule, nativeModulePromiseController, verifyAllAssetsDownloaded, callLibraryInitializerOnRuntimeReady, callLibraryInitializerOnRuntimeConfigLoaded } from "./assets";
+import { fetchAssembly, fetchIcu, fetchNativeSymbols, fetchPdb, fetchSatelliteAssemblies, fetchVfs, fetchMainWasm, loadDotnetModule, loadJSModule, nativeModulePromiseController, verifyAllAssetsDownloaded, callLibraryInitializerOnRuntimeReady, callLibraryInitializerOnRuntimeConfigLoaded, prefetchAllResources, prefetchJSModuleLinks } from "./assets";
import { initPolyfills } from "./polyfills";
import { validateEngineFeatures } from "./bootstrap";
const runMainPromiseController = createPromiseCompletionSource();
-// WASM-TODO: downloadOnly https://github.com/dotnet/runtime/issues/124896
-// WASM-TODO: debugLevel
+type DownloadMode = "none" | "cacheOnly" | "intoMemory";
+let downloadMode: DownloadMode = "none";
+let downloadDeferred: PromiseCompletionSource | undefined;
+let downloadedIntoMemory = false;
+let configInitialized = false;
+let modulesAfterConfigLoadedCache: [JsAsset, Promise][] = [];
// many things happen in parallel here, but order matters for performance!
// ideally we want to utilize network and CPU at the same time
-export async function createRuntime(downloadOnly: boolean): Promise {
+export async function createRuntime(downloadOnly: boolean, httpCacheOnly: boolean = false): Promise {
if (!loaderConfig.resources || !loaderConfig.resources.coreAssembly || !loaderConfig.resources.coreAssembly.length) throw new Error("Invalid config, resources is not set");
try {
runtimeState.creatingRuntime = true;
- const resources = loaderConfig.resources;
-
- await validateEngineFeatures();
- if (typeof Module.onConfigLoaded === "function") {
- await Module.onConfigLoaded(loaderConfig);
+ // Re-entrancy guard: await any in-flight download, skip if already at requested level
+ if (downloadOnly) {
+ if (downloadDeferred) {
+ await downloadDeferred.promise;
+ }
+ if (downloadMode === "intoMemory" || (httpCacheOnly && downloadMode === "cacheOnly")) {
+ return;
+ }
+ downloadDeferred = createPromiseCompletionSource();
}
- validateLoaderConfig();
- const modulesAfterConfigLoadedPromises: [JsAsset, Promise][] = normalizeCollection(resources.modulesAfterConfigLoaded).map((a) => [a, callLibraryInitializerOnRuntimeConfigLoaded(a)]);
- await Promise.all(modulesAfterConfigLoadedPromises.map(([, p]) => p));
+ // Fast path: download() already loaded everything into memory, create() just needs to init
+ if (downloadedIntoMemory && !downloadOnly) {
+ Module.runtimeKeepalivePush();
+ await initializeCoreCLR();
- // Wire user-provided out/err overrides to Emscripten's print/printErr.
- // This must happen before the native module loads so Emscripten picks them up.
- if (!Module.out) {
- // eslint-disable-next-line no-console
- Module.out = console.log.bind(console);
- }
- if (!Module.err) {
- // eslint-disable-next-line no-console
- Module.err = console.error.bind(console);
- }
- if (!Module.print) {
- Module.print = Module.out;
+ if (typeof Module.onDotnetReady === "function") {
+ await Module.onDotnetReady();
+ }
+
+ const resources = loaderConfig.resources;
+ // modulesAfterRuntimeReady were only prefetched during download(), now load and call onRuntimeReady.
+ const modulesAfterRuntimeReadyPromises: [JsAsset, Promise][] = normalizeCollection(resources.modulesAfterRuntimeReady).map((a) => [a, loadJSModule(a)]);
+ // modulesAfterConfigLoaded were loaded during download() — call onRuntimeReady for them too.
+ await Promise.all([...modulesAfterConfigLoadedCache, ...modulesAfterRuntimeReadyPromises].map(callLibraryInitializerOnRuntimeReady));
+ return;
}
- if (!Module.printErr) {
- Module.printErr = Module.err;
+
+ const resources = loaderConfig.resources;
+
+ // Run config initialization once: onConfigLoaded, modulesAfterConfigLoaded, polyfills.
+ // This must happen before any asset fetches so that URL overrides take effect.
+ let modulesAfterConfigLoadedPromises: [JsAsset, Promise][] = [];
+ if (!configInitialized) {
+ await validateEngineFeatures();
+
+ if (typeof Module.onConfigLoaded === "function") {
+ await Module.onConfigLoaded(loaderConfig);
+ }
+ validateLoaderConfig();
+
+ modulesAfterConfigLoadedPromises = normalizeCollection(resources.modulesAfterConfigLoaded).map((a) => [a, callLibraryInitializerOnRuntimeConfigLoaded(a)]);
+ await Promise.all(modulesAfterConfigLoadedPromises.map(([, p]) => p));
+
+ // Wire user-provided out/err overrides to Emscripten's print/printErr.
+ // This must happen before the native module loads so Emscripten picks them up.
+ if (!Module.out) {
+ // eslint-disable-next-line no-console
+ Module.out = console.log.bind(console);
+ }
+ if (!Module.err) {
+ // eslint-disable-next-line no-console
+ Module.err = console.error.bind(console);
+ }
+ if (!Module.print) {
+ Module.print = Module.out;
+ }
+ if (!Module.printErr) {
+ Module.printErr = Module.err;
+ }
+
+ // after onConfigLoaded hooks that could install polyfills, our polyfills can be initialized
+ await initPolyfills();
+
+ configInitialized = true;
+ modulesAfterConfigLoadedCache = modulesAfterConfigLoadedPromises;
}
- // after onConfigLoaded hooks that could install polyfills, our polyfills can be initialized
- await initPolyfills();
+ // HTTP cache only path: just fetch all resources into browser cache and discard
+ if (downloadOnly && httpCacheOnly) {
+ await prefetchAllResources();
+ downloadMode = "cacheOnly";
+ downloadDeferred?.resolve(undefined as unknown as void);
+ return;
+ }
if (resources.jsModuleDiagnostics && resources.jsModuleDiagnostics.length > 0) {
const diagnosticsModule = await loadDotnetModule(resources.jsModuleDiagnostics[0]);
@@ -82,7 +131,14 @@ export async function createRuntime(downloadOnly: boolean): Promise {
const isDebuggingSupported = loaderConfig.debugLevel != 0;
const corePDBsPromise = forEachResource(resources.corePdb, fetchPdb, () => isDebuggingSupported);
const pdbsPromise = forEachResource(resources.pdb, fetchPdb, () => isDebuggingSupported);
- const modulesAfterRuntimeReadyPromises: [JsAsset, Promise][] = normalizeCollection(resources.modulesAfterRuntimeReady).map((a) => [a, loadJSModule(a)]);
+ // In download-only mode, just add prefetch hints for runtime-ready modules so create() loads them from cache.
+ // In create mode, load them now so onRuntimeReady can be called later.
+ let modulesAfterRuntimeReadyPromises: [JsAsset, Promise][] = [];
+ if (downloadOnly) {
+ prefetchJSModuleLinks(normalizeCollection(resources.modulesAfterRuntimeReady));
+ } else {
+ modulesAfterRuntimeReadyPromises = normalizeCollection(resources.modulesAfterRuntimeReady).map((a) => [a, loadJSModule(a)]);
+ }
const nativeModule = await nativeModulePromise;
const modulePromise = nativeModule.dotnetInitializeModule(dotnetInternals);
@@ -113,6 +169,9 @@ export async function createRuntime(downloadOnly: boolean): Promise {
verifyAllAssetsDownloaded();
if (downloadOnly) {
+ downloadMode = "intoMemory";
+ downloadedIntoMemory = true;
+ downloadDeferred?.resolve(undefined as unknown as void);
return;
}
@@ -123,6 +182,7 @@ export async function createRuntime(downloadOnly: boolean): Promise {
await Promise.all([...modulesAfterConfigLoadedPromises, ...modulesAfterRuntimeReadyPromises].map(callLibraryInitializerOnRuntimeReady));
} catch (err) {
+ downloadDeferred?.reject(err);
exit(1, err);
} finally {
runtimeState.creatingRuntime = false;
diff --git a/src/native/libs/Common/JavaScript/types/public-api.ts b/src/native/libs/Common/JavaScript/types/public-api.ts
index eca35c36b9fa06..a187a04f1639a0 100644
--- a/src/native/libs/Common/JavaScript/types/public-api.ts
+++ b/src/native/libs/Common/JavaScript/types/public-api.ts
@@ -70,8 +70,12 @@ export interface DotnetHostBuilder {
withResourceLoader(loadBootResource?: LoadBootResourceCallback): DotnetHostBuilder;
/**
* Downloads all the assets but doesn't create the runtime instance.
+ * @param httpCacheOnly If true, resources are only fetched into the browser HTTP cache
+ * and discarded. A subsequent create() call will re-fetch from cache and do full init.
+ * If false (default), resources are downloaded and loaded into WASM memory, so that
+ * a subsequent create() call only needs to initialize the managed runtime.
*/
- download(): Promise;
+ download(httpCacheOnly?: boolean): Promise;
/**
* Starts the runtime and returns promise of the API object.
*/