<link rel="preload" href="./blazor.boot.json" as="fetch" crossorigin="use-credentials">
<link rel="prefetch" href="./dotnet.native.js" as="fetch" crossorigin="anonymous">
<link rel="prefetch" href="./dotnet.runtime.js" as="fetch" crossorigin="anonymous">
- <link rel="prefetch" href="./dotnet.native.wasm" as="fetch" crossorigin="anonymous">
- <!-- users should consider if they optimize for the first load or subsequent load from memory snapshot -->
- <link rel="prefetch" href="./icudt.dat" as="fetch" crossorigin="anonymous">
- <link rel="prefetch" href="./System.Private.CoreLib.wasm" as="fetch" crossorigin="anonymous">
+ <link rel="prefetch" href="./advanced-sample.lib.module.js" as="fetch" crossorigin="anonymous">
</head>
<body>
// here we show how emscripten could be further configured
// It is preferred to use specific 'with***' methods instead in all other cases.
.withConfig({
+ startupMemoryCache: true,
resources: {
modulesAfterConfigLoaded: {
"advanced-sample.lib.module.js": ""
<link rel="preload" href="./_framework/blazor.boot.json" as="fetch" crossorigin="use-credentials">
<link rel="prefetch" href="./_framework/dotnet.native.js" as="fetch" crossorigin="anonymous">
<link rel="prefetch" href="./_framework/dotnet.runtime.js" as="fetch" crossorigin="anonymous">
- <link rel="prefetch" href="./_framework/dotnet.native.wasm" as="fetch" crossorigin="anonymous">
- <!-- users should consider if they optimize for the first load or subsequent load from memory snapshot -->
- <link rel="prefetch" href="./_framework/System.Private.CoreLib.dll" as="fetch" crossorigin="anonymous">
</head>
<body>
}
const runtime = await dotnet
+ .withConfig({
+ maxParallelDownloads: 10000,
+ // diagnosticTracing:true,
+ })
.withModuleConfig({
printErr: () => undefined,
print: () => undefined,
if (window.parent != window) {
window.parent.resolveAppStartEvent("onConfigLoaded");
}
- // config.diagnosticTracing = true;
}
})
.create();
### Pre-fetching
In order to start downloading application resources as soon as possible you can add HTML elements to `<head>` of your page similar to:
+Adding too many files into prefetch could be counterproductive.
+Please benchmark your startup performance on real target devices and with realistic network conditions.
```html
<link rel="preload" href="./_framework/blazor.boot.json" as="fetch" crossorigin="use-credentials">
<link rel="prefetch" href="./_framework/dotnet.native.js" as="fetch" crossorigin="anonymous">
<link rel="prefetch" href="./_framework/dotnet.runtime.js" as="fetch" crossorigin="anonymous">
-<link rel="prefetch" href="./_framework/dotnet.native.wasm" as="fetch" crossorigin="anonymous">
```
See also [link rel prefetch on MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/prefetch)
import { Module, linkerDisableLegacyJsInterop, exportedRuntimeAPI, passEmscriptenInternals, runtimeHelpers, setRuntimeGlobals, } from "./globals";
import { GlobalObjects, is_nullish } from "./types/internal";
-import { configureEmscriptenStartup, configureWorkerStartup } from "./startup";
+import { configureEmscriptenStartup, configureRuntimeStartup, configureWorkerStartup } from "./startup";
import { create_weak_ref } from "./weak-ref";
import { export_internal } from "./exports-internal";
// export external API
export {
- passEmscriptenInternals, initializeExports, initializeReplacements, configureEmscriptenStartup, configureWorkerStartup, setRuntimeGlobals
+ passEmscriptenInternals, initializeExports, initializeReplacements, configureRuntimeStartup, configureEmscriptenStartup, configureWorkerStartup, setRuntimeGlobals
};
\ No newline at end of file
gitHash,
allAssetsInMemory: createPromiseController<void>(),
dotnetReady: createPromiseController<any>(),
- memorySnapshotSkippedOrDone: createPromiseController<void>(),
afterInstantiateWasm: createPromiseController<void>(),
beforePreInit: createPromiseController<void>(),
afterPreInit: createPromiseController<void>(),
"pdb": true,
"heap": true,
"icu": true,
- ...jsModulesAssetTypes,
- "dotnetwasm": true,
};
// these assets are instantiated differently than the main flow
return !(asset.behavior == "icu" && asset.name != loaderHelpers.preferredIcuAsset);
}
-function convert_single_asset(modulesAssets: AssetEntryInternal[], resource: ResourceList | undefined, behavior: SingleAssetBehaviors): AssetEntryInternal {
+function convert_single_asset(assetsCollection: AssetEntryInternal[], resource: ResourceList | undefined, behavior: SingleAssetBehaviors): AssetEntryInternal {
const keys = Object.keys(resource || {});
mono_assert(keys.length == 1, `Expect to have one ${behavior} asset in resources`);
set_single_asset(asset);
// so that we can use it on the worker too
- modulesAssets.push(asset);
+ assetsCollection.push(asset);
return asset;
}
countAndStartDownload(asset);
}
- // continue after the dotnet.runtime.js was loaded
- await loaderHelpers.runtimeModuleLoaded.promise;
-
// continue after we know if memory snapshot is available or not
- await runtimeHelpers.memorySnapshotSkippedOrDone.promise;
+ await loaderHelpers.memorySnapshotSkippedOrDone.promise;
// start fetching assets in parallel, only if memory snapshot is not available.
for (const asset of containedInSnapshotAssets) {
- if (!runtimeHelpers.loadedMemorySnapshot) {
+ if (!runtimeHelpers.loadedMemorySnapshotSize) {
countAndStartDownload(asset);
} else {
// Otherwise cleanup in case we were given pending download. It would be even better if we could abort the download.
}
loaderHelpers.allDownloadsQueued.promise_control.resolve();
+
+ // continue after the dotnet.runtime.js was loaded
await loaderHelpers.runtimeModuleLoaded.promise;
const promises_of_asset_instantiation: Promise<void>[] = [];
// wait till after onRuntimeInitialized and after memory snapshot is loaded or skipped
await runtimeHelpers.beforeOnRuntimeInitialized.promise;
- await runtimeHelpers.memorySnapshotSkippedOrDone.promise;
runtimeHelpers.instantiate_asset(asset, url, data);
}
} else {
mono_assert(resources.jsModuleNative, "resources.jsModuleNative must be defined");
mono_assert(resources.jsModuleRuntime, "resources.jsModuleRuntime must be defined");
mono_assert(!MonoWasmThreads || resources.jsModuleWorker, "resources.jsModuleWorker must be defined");
- convert_single_asset(modulesAssets, resources.wasmNative, "dotnetwasm");
+ convert_single_asset(alwaysLoadedAssets, resources.wasmNative, "dotnetwasm");
convert_single_asset(modulesAssets, resources.jsModuleNative, "js-module-native");
convert_single_asset(modulesAssets, resources.jsModuleRuntime, "js-module-runtime");
if (MonoWasmThreads) {
import { mono_exit } from "./exit";
import { makeURLAbsoluteWithApplicationBase } from "./polyfills";
import { appendUniqueQuery } from "./assets";
+import { mono_assert } from "./globals";
export function deep_merge_config(target: MonoConfigInternal, source: MonoConfigInternal): MonoConfigInternal {
// no need to merge the same object
await loaderHelpers.afterConfigLoaded.promise;
return;
}
- configLoaded = true;
- if (!configFilePath) {
- normalizeConfig();
- loaderHelpers.afterConfigLoaded.promise_control.resolve(loaderHelpers.config);
- return;
- }
- mono_log_debug("mono_wasm_load_config");
try {
- await loadBootConfig(module);
+ configLoaded = true;
+ if (configFilePath) {
+ mono_log_debug("mono_wasm_load_config");
+ await loadBootConfig(module);
+ }
normalizeConfig();
normalizeConfig();
+ mono_assert(!loaderHelpers.config.startupMemoryCache || !module.instantiateWasm, "startupMemoryCache is not supported with Module.instantiateWasm");
+
loaderHelpers.afterConfigLoaded.promise_control.resolve(loaderHelpers.config);
+ if (!loaderHelpers.config.startupMemoryCache) {
+ loaderHelpers.memorySnapshotSkippedOrDone.promise_control.resolve();
+ }
} catch (err) {
const errMessage = `Failed to load config file ${configFilePath} ${err} ${(err as Error)?.stack}`;
loaderHelpers.config = module.config = Object.assign(loaderHelpers.config, { message: errMessage, error: err, isError: true });
loaderHelpers.afterConfigLoaded.promise_control.reject(reason);
loaderHelpers.wasmDownloadPromise.promise_control.reject(reason);
loaderHelpers.runtimeModuleLoaded.promise_control.reject(reason);
+ loaderHelpers.memorySnapshotSkippedOrDone.promise_control.reject(reason);
if (runtimeHelpers.dotnetReady) {
runtimeHelpers.dotnetReady.promise_control.reject(reason);
- runtimeHelpers.memorySnapshotSkippedOrDone.promise_control.reject(reason);
runtimeHelpers.afterInstantiateWasm.promise_control.reject(reason);
runtimeHelpers.beforePreInit.promise_control.reject(reason);
runtimeHelpers.afterPreInit.promise_control.reject(reason);
allDownloadsQueued: createPromiseController<void>(),
wasmDownloadPromise: createPromiseController<AssetEntryInternal>(),
runtimeModuleLoaded: createPromiseController<void>(),
+ memorySnapshotSkippedOrDone: createPromiseController<void>(),
is_exited,
is_runtime_running,
}
async function initializeModules(es6Modules: [RuntimeModuleExportsInternal, NativeModuleExportsInternal]) {
- const { initializeExports, initializeReplacements, configureEmscriptenStartup, configureWorkerStartup, setRuntimeGlobals, passEmscriptenInternals } = es6Modules[0];
+ const { initializeExports, initializeReplacements, configureRuntimeStartup, configureEmscriptenStartup, configureWorkerStartup, setRuntimeGlobals, passEmscriptenInternals } = es6Modules[0];
const { default: emscriptenFactory } = es6Modules[1];
setRuntimeGlobals(globalObjectsRoot);
initializeExports(globalObjectsRoot);
+ await configureRuntimeStartup();
loaderHelpers.runtimeModuleLoaded.promise_control.resolve();
emscriptenFactory((originalModule: EmscriptenModuleInternal) => {
mono_exit(1, err);
});
- init_globalization();
-
setTimeout(() => {
+ init_globalization();
mono_download_assets(); // intentionally not awaited
}, 0);
}
}
-export async function getMemorySnapshotSize(): Promise<number | undefined> {
+export async function checkMemorySnapshotSize(): Promise<void> {
try {
+ if (!runtimeHelpers.config.startupMemoryCache) {
+ // we could start downloading DLLs because snapshot is disabled
+ return;
+ }
+
const cacheKey = await getCacheKey();
if (!cacheKey) {
- return undefined;
+ return;
}
const cache = await openCache();
if (!cache) {
- return undefined;
+ return;
}
const res = await cache.match(cacheKey);
const contentLength = res?.headers.get("content-length");
- return contentLength ? parseInt(contentLength) : undefined;
+ const memorySize = contentLength ? parseInt(contentLength) : undefined;
+
+ runtimeHelpers.loadedMemorySnapshotSize = memorySize;
+ runtimeHelpers.storeMemorySnapshotPending = !memorySize;
} catch (ex) {
mono_log_warn("Failed find memory snapshot in the cache", ex);
- return undefined;
+ }
+ finally {
+ if (!runtimeHelpers.loadedMemorySnapshotSize) {
+ // we could start downloading DLLs because there is no snapshot yet
+ loaderHelpers.memorySnapshotSkippedOrDone.promise_control.resolve();
+ }
}
}
import { mono_wasm_init_diagnostics } from "./diagnostics";
import { replace_linker_placeholders } from "./exports-binding";
import { endMeasure, MeasuredBlock, startMeasure } from "./profiler";
-import { getMemorySnapshot, storeMemorySnapshot, getMemorySnapshotSize } from "./snapshot";
+import { checkMemorySnapshotSize, getMemorySnapshot, storeMemorySnapshot } from "./snapshot";
import { mono_log_debug, mono_log_error, mono_log_warn, mono_set_thread_id } from "./logging";
// threads
// default size if MonoConfig.pthreadPoolSize is undefined
const MONO_PTHREAD_POOL_SIZE = 4;
+export async function configureRuntimeStartup(): Promise<void> {
+ if (linkerWasmEnableSIMD) {
+ mono_assert(await loaderHelpers.simd(), "This browser/engine doesn't support WASM SIMD. Please use a modern version. See also https://aka.ms/dotnet-wasm-features");
+ }
+ if (linkerWasmEnableEH) {
+ mono_assert(await loaderHelpers.exceptions(), "This browser/engine doesn't support WASM exception handling. Please use a modern version. See also https://aka.ms/dotnet-wasm-features");
+ }
+
+ await init_polyfills_async();
+
+ await checkMemorySnapshotSize();
+}
+
// we are making emscripten startup async friendly
// emscripten is executing the events without awaiting it and so we need to block progress via PromiseControllers above
export function configureEmscriptenStartup(module: DotnetModuleInternal): void {
const mark = startMeasure();
if (userInstantiateWasm) {
- // user wasm instantiation doesn't support memory snapshots
- runtimeHelpers.memorySnapshotSkippedOrDone.promise_control.resolve();
const exports = userInstantiateWasm(imports, (instance: WebAssembly.Instance, module: WebAssembly.Module | undefined) => {
endMeasure(mark, MeasuredBlock.instantiateWasm);
runtimeHelpers.afterInstantiateWasm.promise_control.resolve();
mono_log_debug("mono_wasm_pre_init_essential_async");
Module.addRunDependency("mono_wasm_pre_init_essential_async");
- if (linkerWasmEnableSIMD) {
- mono_assert(await loaderHelpers.simd(), "This browser/engine doesn't support WASM SIMD. Please use a modern version. See also https://aka.ms/dotnet-wasm-features");
- }
- if (linkerWasmEnableEH) {
- mono_assert(await loaderHelpers.exceptions(), "This browser/engine doesn't support WASM exception handling. Please use a modern version. See also https://aka.ms/dotnet-wasm-features");
- }
-
- await init_polyfills_async();
if (MonoWasmThreads) {
preAllocatePThreadWorkerPool(MONO_PTHREAD_POOL_SIZE, runtimeHelpers.config);
): Promise<void> {
// this is called so early that even Module exports like addRunDependency don't exist yet
try {
- let memorySize: number | undefined = undefined;
await loaderHelpers.afterConfigLoaded;
mono_log_debug("instantiate_wasm_module");
- if (runtimeHelpers.config.startupMemoryCache) {
- memorySize = await getMemorySnapshotSize();
- runtimeHelpers.loadedMemorySnapshot = !!memorySize;
- runtimeHelpers.storeMemorySnapshotPending = !runtimeHelpers.loadedMemorySnapshot;
- }
- if (!runtimeHelpers.loadedMemorySnapshot) {
- // we should start downloading DLLs etc as they are not in the snapshot
- runtimeHelpers.memorySnapshotSkippedOrDone.promise_control.resolve();
- }
-
await runtimeHelpers.beforePreInit.promise;
Module.addRunDependency("instantiate_wasm_module");
mono_log_debug("instantiate_wasm_module done");
- if (runtimeHelpers.loadedMemorySnapshot) {
+ if (runtimeHelpers.loadedMemorySnapshotSize) {
try {
const wasmMemory = (Module.asm?.memory || Module.wasmMemory)!;
// .grow() takes a delta compared to the previous size
- wasmMemory.grow((memorySize! - wasmMemory.buffer.byteLength + 65535) >>> 16);
+ wasmMemory.grow((runtimeHelpers.loadedMemorySnapshotSize! - wasmMemory.buffer.byteLength + 65535) >>> 16);
runtimeHelpers.updateMemoryViews();
} catch (err) {
mono_log_warn("failed to resize memory for the snapshot", err);
- runtimeHelpers.loadedMemorySnapshot = false;
+ runtimeHelpers.loadedMemorySnapshotSize = undefined;
}
// now we know if the loading of memory succeeded or not, we can start loading the rest of the assets
- runtimeHelpers.memorySnapshotSkippedOrDone.promise_control.resolve();
+ loaderHelpers.memorySnapshotSkippedOrDone.promise_control.resolve();
}
runtimeHelpers.afterInstantiateWasm.promise_control.resolve();
} catch (err) {
async function mono_wasm_before_memory_snapshot() {
const mark = startMeasure();
- if (runtimeHelpers.loadedMemorySnapshot) {
+ if (runtimeHelpers.loadedMemorySnapshotSize) {
// get the bytes after we re-sized the memory, so that we don't have too much memory in use at the same time
const memoryBytes = await getMemorySnapshot();
const heapU8 = localHeapViewU8();
allDownloadsQueued: PromiseAndController<void>,
wasmDownloadPromise: PromiseAndController<AssetEntryInternal>,
runtimeModuleLoaded: PromiseAndController<void>,
+ memorySnapshotSkippedOrDone: PromiseAndController<void>,
is_exited: () => boolean,
is_runtime_running: () => boolean,
mono_wasm_runtime_is_ready: boolean;
mono_wasm_bindings_is_ready: boolean;
- loadedMemorySnapshot: boolean,
+ loadedMemorySnapshotSize?: number,
enablePerfMeasure: boolean;
waitForDebugger?: number;
ExitStatus: ExitStatusError;
allAssetsInMemory: PromiseAndController<void>,
dotnetReady: PromiseAndController<any>,
- memorySnapshotSkippedOrDone: PromiseAndController<void>,
afterInstantiateWasm: PromiseAndController<void>,
beforePreInit: PromiseAndController<void>,
afterPreInit: PromiseAndController<void>,
export type initializeExportsType = (globalObjects: GlobalObjects) => RuntimeAPI;
export type initializeReplacementsType = (replacements: EmscriptenReplacements) => void;
export type configureEmscriptenStartupType = (module: DotnetModuleInternal) => void;
+export type configureRuntimeStartupType = () => Promise<void>;
export type configureWorkerStartupType = (module: DotnetModuleInternal) => Promise<void>
setRuntimeGlobals: setGlobalObjectsType,
initializeExports: initializeExportsType,
initializeReplacements: initializeReplacementsType,
+ configureRuntimeStartup: configureRuntimeStartupType,
configureEmscriptenStartup: configureEmscriptenStartupType,
configureWorkerStartup: configureWorkerStartupType,
passEmscriptenInternals: passEmscriptenInternalsType,