From e1c04bc6662cf6eac68720e0d82d48253d61de39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 25 Jul 2023 14:12:47 +0200 Subject: [PATCH 01/65] Remove assets from public monoConfig --- src/mono/wasm/runtime/dotnet.d.ts | 4 ---- src/mono/wasm/runtime/loader/run.ts | 2 +- src/mono/wasm/runtime/types/index.ts | 4 ---- src/mono/wasm/runtime/types/internal.ts | 1 + 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index e42d6e190f4c8c..b642e2817389ce 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -99,10 +99,6 @@ type MonoConfig = { * The subfolder containing managed assemblies and pdbs. This is relative to dotnet.js script. */ assemblyRootFolder?: string; - /** - * A list of assets to load along with the runtime. - */ - assets?: AssetEntry[]; /** * Additional search locations for assets. */ diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts index a05f2e04d25719..6d9e0007abd647 100644 --- a/src/mono/wasm/runtime/loader/run.ts +++ b/src/mono/wasm/runtime/loader/run.ts @@ -472,7 +472,7 @@ function initializeModules(es6Modules: [RuntimeModuleExportsInternal, NativeModu } async function createEmscriptenMain(): Promise { - if (!module.configSrc && (!module.config || Object.keys(module.config).length === 0 || !module.config.assets)) { + if (!module.configSrc && (!module.config || Object.keys(module.config).length === 0 || !module.config.resources)) { // if config file location nor assets are provided module.configSrc = "./blazor.boot.json"; } diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 6222355fddb4cf..0edc4d3f20a580 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -32,10 +32,6 @@ export type MonoConfig = { * The subfolder containing managed assemblies and pdbs. This is relative to dotnet.js script. */ assemblyRootFolder?: string, - /** - * A list of assets to load along with the runtime. - */ - assets?: AssetEntry[], /** * Additional search locations for assets. */ diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index bc91e975c439ae..27cbffb4566e3a 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -69,6 +69,7 @@ export function coerceNull(ptr: T | nu // when adding new fields, please consider if it should be impacting the snapshot hash. If not, please drop it in the snapshot getCacheKey() export type MonoConfigInternal = MonoConfig & { + assets?: AssetEntry[], runtimeOptions?: string[], // array of runtime options as strings aotProfilerOptions?: AOTProfilerOptions, // dictionary-style Object. If omitted, aot profiler will not be initialized. browserProfilerOptions?: BrowserProfilerOptions, // dictionary-style Object. If omitted, browser profiler will not be initialized. From ddff687803c07d7cd9b11dc083d642d78fbeef5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 25 Jul 2023 14:13:13 +0200 Subject: [PATCH 02/65] Remove BootConfigResult.initAsync --- .../wasm/runtime/loader/blazor/BootConfig.ts | 30 ---------------- .../runtime/loader/blazor/_Integration.ts | 36 +++++++++++++++---- src/mono/wasm/runtime/loader/config.ts | 3 +- 3 files changed, 32 insertions(+), 37 deletions(-) diff --git a/src/mono/wasm/runtime/loader/blazor/BootConfig.ts b/src/mono/wasm/runtime/loader/blazor/BootConfig.ts index 2687b72b029338..1267c5e5dbe408 100644 --- a/src/mono/wasm/runtime/loader/blazor/BootConfig.ts +++ b/src/mono/wasm/runtime/loader/blazor/BootConfig.ts @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { BootJsonData } from "../../types/blazor"; -import type { LoadBootResourceCallback } from "../../types"; import { loaderHelpers } from "../globals"; export class BootConfigResult { @@ -21,34 +20,5 @@ export class BootConfigResult { return new BootConfigResult(bootConfig, applicationEnvironment); } - - static async initAsync(loadBootResource?: LoadBootResourceCallback, environment?: string): Promise { - const defaultBootJsonLocation = "_framework/blazor.boot.json"; - - const loaderResponse = loadBootResource !== undefined ? - loadBootResource("manifest", "blazor.boot.json", defaultBootJsonLocation, "") : - defaultLoadBlazorBootJson(defaultBootJsonLocation); - - let bootConfigResponse: Response; - - if (!loaderResponse) { - bootConfigResponse = await defaultLoadBlazorBootJson(defaultBootJsonLocation); - } else if (typeof loaderResponse === "string") { - bootConfigResponse = await defaultLoadBlazorBootJson(loaderResponse); - } else { - bootConfigResponse = await loaderResponse; - } - - const bootConfig: BootJsonData = await bootConfigResponse.json(); - return BootConfigResult.fromFetchResponse(bootConfigResponse, bootConfig, environment); - - function defaultLoadBlazorBootJson(url: string): Promise { - return fetch(url, { - method: "GET", - credentials: "include", - cache: "no-cache", - }); - } - } } diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 2cfdaac4e4f75c..edd86f9b024f66 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -15,14 +15,38 @@ import { appendUniqueQuery } from "../assets"; let resourceLoader: WebAssemblyResourceLoader; export async function loadBootConfig(config: MonoConfigInternal, module: DotnetModuleInternal) { - const bootConfigPromise = BootConfigResult.initAsync(loaderHelpers.loadBootResource, config.applicationEnvironment); - const bootConfigResult: BootConfigResult = await bootConfigPromise; - await initializeBootConfig(bootConfigResult, module, loaderHelpers.loadBootResource); + const defaultBootJsonLocation = "_framework/blazor.boot.json"; + const loaderResponse = loaderHelpers.loadBootResource !== undefined ? + loaderHelpers.loadBootResource("manifest", "blazor.boot.json", defaultBootJsonLocation, "") : + defaultLoadBlazorBootJson(defaultBootJsonLocation); + + let bootConfigResponse: Response; + + if (!loaderResponse) { + bootConfigResponse = await defaultLoadBlazorBootJson(defaultBootJsonLocation); + } else if (typeof loaderResponse === "string") { + bootConfigResponse = await defaultLoadBlazorBootJson(loaderResponse); + } else { + bootConfigResponse = await loaderResponse; + } + + const bootConfig: BootJsonData = await bootConfigResponse.json(); + const bootConfigResult = await BootConfigResult.fromFetchResponse(bootConfigResponse, bootConfig, config.applicationEnvironment); + + await initializeBootConfig(bootConfigResult.bootConfig, bootConfigResult.applicationEnvironment, module, loaderHelpers.loadBootResource); + + function defaultLoadBlazorBootJson(url: string): Promise { + return fetch(url, { + method: "GET", + credentials: "include", + cache: "no-cache", + }); + } } -export async function initializeBootConfig(bootConfigResult: BootConfigResult, module: DotnetModuleInternal, loadBootResource?: LoadBootResourceCallback) { - INTERNAL.resourceLoader = resourceLoader = await WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, loadBootResource); - mapBootConfigToMonoConfig(loaderHelpers.config, bootConfigResult.applicationEnvironment); +export async function initializeBootConfig(bootConfig: BootJsonData, applicationEnvironment: string, module: DotnetModuleInternal, loadBootResource?: LoadBootResourceCallback) { + INTERNAL.resourceLoader = resourceLoader = await WebAssemblyResourceLoader.initAsync(bootConfig, loadBootResource); + mapBootConfigToMonoConfig(loaderHelpers.config, applicationEnvironment); if (ENVIRONMENT_IS_WEB) { setupModuleForBlazor(module); diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index 6b1b5932a39059..5deccf25f5cf66 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -91,7 +91,8 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi if (loadedAnyConfig.resources) { // If we found boot config schema normalizeConfig(); - await initializeBootConfig(BootConfigResult.fromFetchResponse(configResponse, loadedAnyConfig as BootJsonData, loaderHelpers.config.applicationEnvironment), module, loaderHelpers.loadBootResource); + const bootConfigResult = BootConfigResult.fromFetchResponse(configResponse, loadedAnyConfig as BootJsonData, loaderHelpers.config.applicationEnvironment); + await initializeBootConfig(bootConfigResult.bootConfig, bootConfigResult.applicationEnvironment, module, loaderHelpers.loadBootResource); } else { // Otherwise we found mono config schema const loadedConfig = loadedAnyConfig as MonoConfigInternal; From 1d9b6d321ca1a070073253c02f1bd9429fc20d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 25 Jul 2023 14:32:57 +0200 Subject: [PATCH 03/65] Remove BootConfigResult --- .../wasm/runtime/loader/blazor/BootConfig.ts | 24 ------ .../runtime/loader/blazor/_Integration.ts | 84 ++++++++++++------- src/mono/wasm/runtime/loader/config.ts | 5 +- 3 files changed, 53 insertions(+), 60 deletions(-) delete mode 100644 src/mono/wasm/runtime/loader/blazor/BootConfig.ts diff --git a/src/mono/wasm/runtime/loader/blazor/BootConfig.ts b/src/mono/wasm/runtime/loader/blazor/BootConfig.ts deleted file mode 100644 index 1267c5e5dbe408..00000000000000 --- a/src/mono/wasm/runtime/loader/blazor/BootConfig.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import type { BootJsonData } from "../../types/blazor"; -import { loaderHelpers } from "../globals"; - -export class BootConfigResult { - private constructor(public bootConfig: BootJsonData, public applicationEnvironment: string) { - } - - static fromFetchResponse(bootConfigResponse: Response, bootConfig: BootJsonData, environment: string | undefined): BootConfigResult { - const applicationEnvironment = environment - || (loaderHelpers.getApplicationEnvironment && loaderHelpers.getApplicationEnvironment(bootConfigResponse)) - || bootConfigResponse.headers.get("Blazor-Environment") - || bootConfigResponse.headers.get("DotNet-Environment") - || "Production"; - - bootConfig.modifiableAssemblies = bootConfigResponse.headers.get("DOTNET-MODIFIABLE-ASSEMBLIES"); - bootConfig.aspnetCoreBrowserTools = bootConfigResponse.headers.get("ASPNETCORE-BROWSER-TOOLS"); - - return new BootConfigResult(bootConfig, applicationEnvironment); - } -} - diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index edd86f9b024f66..de59201b403baa 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -6,7 +6,6 @@ import { GlobalizationMode, type AssetBehaviours, type AssetEntry, type LoadBoot import type { BootJsonData } from "../../types/blazor"; import { ENVIRONMENT_IS_WEB, INTERNAL, loaderHelpers } from "../globals"; -import { BootConfigResult } from "./BootConfig"; import { WebAssemblyResourceLoader } from "./WebAssemblyResourceLoader"; import { hasDebuggingEnabled } from "./_Polyfill"; import { ICUDataMode } from "../../types/blazor"; @@ -31,9 +30,8 @@ export async function loadBootConfig(config: MonoConfigInternal, module: DotnetM } const bootConfig: BootJsonData = await bootConfigResponse.json(); - const bootConfigResult = await BootConfigResult.fromFetchResponse(bootConfigResponse, bootConfig, config.applicationEnvironment); - await initializeBootConfig(bootConfigResult.bootConfig, bootConfigResult.applicationEnvironment, module, loaderHelpers.loadBootResource); + await initializeBootConfig(config, bootConfigResponse, bootConfig, module, loaderHelpers.loadBootResource); function defaultLoadBlazorBootJson(url: string): Promise { return fetch(url, { @@ -44,9 +42,33 @@ export async function loadBootConfig(config: MonoConfigInternal, module: DotnetM } } -export async function initializeBootConfig(bootConfig: BootJsonData, applicationEnvironment: string, module: DotnetModuleInternal, loadBootResource?: LoadBootResourceCallback) { +function readBootConfigResponseHeaders(config: MonoConfigInternal, bootConfigResponse: Response) { + config.applicationEnvironment = config.applicationEnvironment + || (loaderHelpers.getApplicationEnvironment && loaderHelpers.getApplicationEnvironment(bootConfigResponse)) + || bootConfigResponse.headers.get("Blazor-Environment") + || bootConfigResponse.headers.get("DotNet-Environment") + || "Production"; + + if (!config.environmentVariables) + config.environmentVariables = {}; + + const modifiableAssemblies = bootConfigResponse.headers.get("DOTNET-MODIFIABLE-ASSEMBLIES"); + if (modifiableAssemblies) { + // Configure the app to enable hot reload in Development. + config.environmentVariables["DOTNET_MODIFIABLE_ASSEMBLIES"] = modifiableAssemblies; + } + + const aspnetCoreBrowserTools = bootConfigResponse.headers.get("ASPNETCORE-BROWSER-TOOLS"); + if (aspnetCoreBrowserTools) { + // See https://github.com/dotnet/aspnetcore/issues/37357#issuecomment-941237000 + config.environmentVariables["__ASPNETCORE_BROWSER_TOOLS"] = aspnetCoreBrowserTools; + } +} + +export async function initializeBootConfig(config: MonoConfigInternal, bootConfigResponse: Response, bootConfig: BootJsonData, module: DotnetModuleInternal, loadBootResource?: LoadBootResourceCallback) { + readBootConfigResponseHeaders(config, bootConfigResponse); INTERNAL.resourceLoader = resourceLoader = await WebAssemblyResourceLoader.initAsync(bootConfig, loadBootResource); - mapBootConfigToMonoConfig(loaderHelpers.config, applicationEnvironment); + mapBootConfigToMonoConfig(config); if (ENVIRONMENT_IS_WEB) { setupModuleForBlazor(module); @@ -101,7 +123,7 @@ export function setupModuleForBlazor(module: DotnetModuleInternal) { loaderHelpers.downloadResource = downloadResource; // polyfills were already assigned } -export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, applicationEnvironment: string) { +export function mapBootConfigToMonoConfig(config: MonoConfigInternal) { const resources = resourceLoader.bootConfig.resources; const assets: AssetEntry[] = []; @@ -109,16 +131,14 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl // From boot config ...(resourceLoader.bootConfig.environmentVariables || {}), // From JavaScript - ...(moduleConfig.environmentVariables || {}) + ...(config.environmentVariables || {}) }; - moduleConfig.applicationEnvironment = applicationEnvironment; - - moduleConfig.remoteSources = (resourceLoader.bootConfig.resources as any).remoteSources; - moduleConfig.assetsHash = resourceLoader.bootConfig.resources.hash; - moduleConfig.assets = assets; - moduleConfig.extensions = resourceLoader.bootConfig.extensions; - moduleConfig.resources = { + config.remoteSources = (resourceLoader.bootConfig.resources as any).remoteSources; + config.assetsHash = resourceLoader.bootConfig.resources.hash; + config.assets = assets; + config.extensions = resourceLoader.bootConfig.extensions; + config.resources = { extensions: resources.extensions }; @@ -127,8 +147,8 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl // - Build (release) => debugBuild=true & debugLevel=0 => 0 // - Publish (debug) => debugBuild=false & debugLevel=-1 => 0 // - Publish (release) => debugBuild=false & debugLevel=0 => 0 - moduleConfig.debugLevel = hasDebuggingEnabled(resourceLoader.bootConfig) ? resourceLoader.bootConfig.debugLevel : 0; - moduleConfig.mainAssemblyName = resourceLoader.bootConfig.entryAssembly; + config.debugLevel = hasDebuggingEnabled(resourceLoader.bootConfig) ? resourceLoader.bootConfig.debugLevel : 0; + config.mainAssemblyName = resourceLoader.bootConfig.entryAssembly; const anyBootConfig = (resourceLoader.bootConfig as any); for (const key in resourceLoader.bootConfig) { @@ -140,18 +160,18 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl } // FIXME this mix of both formats is ugly temporary hack - Object.assign(moduleConfig, { + Object.assign(config, { ...resourceLoader.bootConfig, }); - moduleConfig.environmentVariables = environmentVariables; + config.environmentVariables = environmentVariables; if (resourceLoader.bootConfig.startupMemoryCache !== undefined) { - moduleConfig.startupMemoryCache = resourceLoader.bootConfig.startupMemoryCache; + config.startupMemoryCache = resourceLoader.bootConfig.startupMemoryCache; } if (resourceLoader.bootConfig.runtimeOptions) { - moduleConfig.runtimeOptions = [...(moduleConfig.runtimeOptions || []), ...resourceLoader.bootConfig.runtimeOptions]; + config.runtimeOptions = [...(config.runtimeOptions || []), ...resourceLoader.bootConfig.runtimeOptions]; } // any runtime owned assets, with proper behavior already set @@ -181,8 +201,8 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl assets.push(asset); } } - const applicationCulture = moduleConfig.applicationCulture || (ENVIRONMENT_IS_WEB ? (navigator.languages && navigator.languages[0]) : Intl.DateTimeFormat().resolvedOptions().locale); - const icuDataResourceName = getICUResourceName(resourceLoader.bootConfig, moduleConfig, applicationCulture); + const applicationCulture = config.applicationCulture || (ENVIRONMENT_IS_WEB ? (navigator.languages && navigator.languages[0]) : Intl.DateTimeFormat().resolvedOptions().locale); + const icuDataResourceName = getICUResourceName(resourceLoader.bootConfig, config, applicationCulture); let hasIcuData = false; for (const name in resources.runtime) { const behavior = behaviorByName(name) as any; @@ -213,7 +233,7 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl assets.push(asset); } - if (moduleConfig.loadAllSatelliteResources && resources.satelliteResources) { + if (config.loadAllSatelliteResources && resources.satelliteResources) { for (const culture in resources.satelliteResources) { for (const name in resources.satelliteResources[culture]) { assets.push({ @@ -227,12 +247,12 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl } for (let i = 0; i < resourceLoader.bootConfig.config.length; i++) { - const config = resourceLoader.bootConfig.config[i]; - const configFileName = fileName(config); - if (configFileName === "appsettings.json" || configFileName === `appsettings.${applicationEnvironment}.json`) { + const configUrl = resourceLoader.bootConfig.config[i]; + const configFileName = fileName(configUrl); + if (configFileName === "appsettings.json" || configFileName === `appsettings.${config.applicationEnvironment}.json`) { assets.push({ name: configFileName, - resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(config), "vfs"), + resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(configUrl), "vfs"), behavior: "vfs", }); } @@ -252,7 +272,7 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl } if (!hasIcuData) { - moduleConfig.globalizationMode = GlobalizationMode.Invariant; + config.globalizationMode = GlobalizationMode.Invariant; } if (resourceLoader.bootConfig.modifiableAssemblies) { @@ -265,17 +285,17 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl environmentVariables["__ASPNETCORE_BROWSER_TOOLS"] = resourceLoader.bootConfig.aspnetCoreBrowserTools; } - if (moduleConfig.applicationCulture) { + if (config.applicationCulture) { // If a culture is specified via start options use that to initialize the Emscripten \ .NET culture. - environmentVariables["LANG"] = `${moduleConfig.applicationCulture}.UTF-8`; + environmentVariables["LANG"] = `${config.applicationCulture}.UTF-8`; } if (resourceLoader.bootConfig.startupMemoryCache !== undefined) { - moduleConfig.startupMemoryCache = resourceLoader.bootConfig.startupMemoryCache; + config.startupMemoryCache = resourceLoader.bootConfig.startupMemoryCache; } if (resourceLoader.bootConfig.runtimeOptions) { - moduleConfig.runtimeOptions = [...(moduleConfig.runtimeOptions || []), ...(resourceLoader.bootConfig.runtimeOptions || [])]; + config.runtimeOptions = [...(config.runtimeOptions || []), ...(resourceLoader.bootConfig.runtimeOptions || [])]; } } diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index 5deccf25f5cf66..6dbe972ced9448 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -6,8 +6,6 @@ import type { DotnetModuleInternal, MonoConfigInternal } from "../types/internal import type { DotnetModuleConfig } from "../types"; import { ENVIRONMENT_IS_WEB, exportedRuntimeAPI, loaderHelpers, runtimeHelpers } from "./globals"; import { initializeBootConfig, loadBootConfig } from "./blazor/_Integration"; -import { BootConfigResult } from "./blazor/BootConfig"; -import { BootJsonData } from "../types/blazor"; import { mono_log_error, mono_log_debug } from "./logging"; import { invokeLibraryInitializers } from "./libraryInitializers"; import { mono_exit } from "./exit"; @@ -91,8 +89,7 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi if (loadedAnyConfig.resources) { // If we found boot config schema normalizeConfig(); - const bootConfigResult = BootConfigResult.fromFetchResponse(configResponse, loadedAnyConfig as BootJsonData, loaderHelpers.config.applicationEnvironment); - await initializeBootConfig(bootConfigResult.bootConfig, bootConfigResult.applicationEnvironment, module, loaderHelpers.loadBootResource); + await initializeBootConfig(loadedAnyConfig, configResponse, loadedAnyConfig, module, loaderHelpers.loadBootResource); } else { // Otherwise we found mono config schema const loadedConfig = loadedAnyConfig as MonoConfigInternal; From 4677dda62b7b1210dd7b107c7a91bfc42e2d2875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 25 Jul 2023 15:11:52 +0200 Subject: [PATCH 04/65] Remove WebAssemblyResourceLoader --- src/mono/wasm/runtime/dotnet.d.ts | 4 + src/mono/wasm/runtime/lazyLoading.ts | 12 +- .../blazor/WebAssemblyResourceLoader.ts | 248 ------------------ .../runtime/loader/blazor/_Integration.ts | 83 +++--- .../wasm/runtime/loader/blazor/_Polyfill.ts | 8 +- src/mono/wasm/runtime/loader/config.ts | 2 +- src/mono/wasm/runtime/loader/globals.ts | 3 + .../wasm/runtime/loader/resourceLoader.ts | 240 +++++++++++++++++ src/mono/wasm/runtime/satelliteAssemblies.ts | 8 +- src/mono/wasm/runtime/startup.ts | 2 +- src/mono/wasm/runtime/types/index.ts | 5 + src/mono/wasm/runtime/types/internal.ts | 8 +- 12 files changed, 307 insertions(+), 316 deletions(-) delete mode 100644 src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts create mode 100644 src/mono/wasm/runtime/loader/resourceLoader.ts diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index b642e2817389ce..59e2519b3ac55b 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -129,6 +129,10 @@ type MonoConfig = { * debugLevel < 0 enables debugging and disables debug logging. */ debugLevel?: number; + /** + * Gets a value that determines whether to enable caching of the 'resources' inside a CacheStorage instance within the browser. + */ + cacheBootResources?: boolean; /** * Enables diagnostic log messages during startup */ diff --git a/src/mono/wasm/runtime/lazyLoading.ts b/src/mono/wasm/runtime/lazyLoading.ts index 8402e7f37e2217..cfcaa8df18efed 100644 --- a/src/mono/wasm/runtime/lazyLoading.ts +++ b/src/mono/wasm/runtime/lazyLoading.ts @@ -1,12 +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 { INTERNAL, loaderHelpers, runtimeHelpers } from "./globals"; -import type { WebAssemblyResourceLoader } from "./loader/blazor/WebAssemblyResourceLoader"; +import { loaderHelpers, runtimeHelpers } from "./globals"; export async function loadLazyAssembly(assemblyNameToLoad: string): Promise { - const resourceLoader: WebAssemblyResourceLoader = INTERNAL.resourceLoader; - const resources = resourceLoader.bootConfig.resources; + const resources = loaderHelpers.config.resources!; const lazyAssemblies = resources.lazyAssembly; if (!lazyAssemblies) { throw new Error("No assemblies have been marked as lazy-loadable. Use the 'BlazorWebAssemblyLazyLoad' item group in your project file to enable lazy loading an assembly."); @@ -23,14 +21,14 @@ export async function loadLazyAssembly(assemblyNameToLoad: string): Promise response.arrayBuffer()); + const dllBytesPromise = loaderHelpers.loadResource(dllNameToLoad, loaderHelpers.locateFile(dllNameToLoad), lazyAssemblies[dllNameToLoad], "assembly").response.then(response => response.arrayBuffer()); let dll = null; let pdb = null; if (shouldLoadPdb) { - const pdbBytesPromise = await resourceLoader.loadResource(pdbNameToLoad, loaderHelpers.locateFile(pdbNameToLoad), lazyAssemblies[pdbNameToLoad], "pdb").response.then(response => response.arrayBuffer()); + const pdbBytesPromise = await loaderHelpers.loadResource(pdbNameToLoad, loaderHelpers.locateFile(pdbNameToLoad), lazyAssemblies[pdbNameToLoad], "pdb").response.then(response => response.arrayBuffer()); const [dllBytes, pdbBytes] = await Promise.all([dllBytesPromise, pdbBytesPromise]); dll = new Uint8Array(dllBytes); diff --git a/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts b/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts deleted file mode 100644 index cd90ffb2b9a7ed..00000000000000 --- a/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts +++ /dev/null @@ -1,248 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import type { LoadBootResourceCallback, WebAssemblyBootResourceType } from "../../types"; -import type { BootJsonData, ResourceList } from "../../types/blazor"; -import { loaderHelpers } from "../globals"; -import { toAbsoluteUri } from "./_Polyfill"; -const networkFetchCacheMode = "no-cache"; - -const cacheSkipResourceTypes = ["configuration"]; - -export class WebAssemblyResourceLoader { - private usedCacheKeys: { [key: string]: boolean } = {}; - - private networkLoads: { [name: string]: LoadLogEntry } = {}; - - private cacheLoads: { [name: string]: LoadLogEntry } = {}; - - static async initAsync(bootConfig: BootJsonData, loadBootResource?: LoadBootResourceCallback): Promise { - const cache = await getCacheToUseIfEnabled(bootConfig); - return new WebAssemblyResourceLoader(bootConfig, cache, loadBootResource); - } - - constructor(readonly bootConfig: BootJsonData, readonly cacheIfUsed: Cache | null, readonly loadBootResource?: LoadBootResourceCallback) { - } - - loadResources(resources: ResourceList, url: (name: string) => string, resourceType: WebAssemblyBootResourceType): LoadingResource[] { - return Object.keys(resources) - .map(name => this.loadResource(name, url(name), resources[name], resourceType)); - } - - loadResource(name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType): LoadingResource { - const response = this.cacheIfUsed && !cacheSkipResourceTypes.includes(resourceType) - ? this.loadResourceWithCaching(this.cacheIfUsed, name, url, contentHash, resourceType) - : this.loadResourceWithoutCaching(name, url, contentHash, resourceType); - - const absoluteUrl = toAbsoluteUri(url); - - if (resourceType == "assembly") { - loaderHelpers.loadedAssemblies.push(absoluteUrl); - } - return { name, url: absoluteUrl, response }; - } - - logToConsole(): void { - const cacheLoadsEntries = Object.values(this.cacheLoads); - const networkLoadsEntries = Object.values(this.networkLoads); - const cacheResponseBytes = countTotalBytes(cacheLoadsEntries); - const networkResponseBytes = countTotalBytes(networkLoadsEntries); - const totalResponseBytes = cacheResponseBytes + networkResponseBytes; - if (totalResponseBytes === 0) { - // We have no perf stats to display, likely because caching is not in use. - return; - } - - const linkerDisabledWarning = this.bootConfig.linkerEnabled ? "%c" : "\n%cThis application was built with linking (tree shaking) disabled. Published applications will be significantly smaller."; - // eslint-disable-next-line no-console - console.groupCollapsed(`%cdotnet%c Loaded ${toDataSizeString(totalResponseBytes)} resources${linkerDisabledWarning}`, "background: purple; color: white; padding: 1px 3px; border-radius: 3px;", "font-weight: bold;", "font-weight: normal;"); - - if (cacheLoadsEntries.length) { - // eslint-disable-next-line no-console - console.groupCollapsed(`Loaded ${toDataSizeString(cacheResponseBytes)} resources from cache`); - // eslint-disable-next-line no-console - console.table(this.cacheLoads); - // eslint-disable-next-line no-console - console.groupEnd(); - } - - if (networkLoadsEntries.length) { - // eslint-disable-next-line no-console - console.groupCollapsed(`Loaded ${toDataSizeString(networkResponseBytes)} resources from network`); - // eslint-disable-next-line no-console - console.table(this.networkLoads); - // eslint-disable-next-line no-console - console.groupEnd(); - } - - // eslint-disable-next-line no-console - console.groupEnd(); - } - - async purgeUnusedCacheEntriesAsync(): Promise { - // We want to keep the cache small because, even though the browser will evict entries if it - // gets too big, we don't want to be considered problematic by the end user viewing storage stats - const cache = this.cacheIfUsed; - if (cache) { - const cachedRequests = await cache.keys(); - const deletionPromises = cachedRequests.map(async cachedRequest => { - if (!(cachedRequest.url in this.usedCacheKeys)) { - await cache.delete(cachedRequest); - } - }); - - await Promise.all(deletionPromises); - } - } - - private async loadResourceWithCaching(cache: Cache, name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType) { - // Since we are going to cache the response, we require there to be a content hash for integrity - // checking. We don't want to cache bad responses. There should always be a hash, because the build - // process generates this data. - if (!contentHash || contentHash.length === 0) { - throw new Error("Content hash is required"); - } - - const cacheKey = toAbsoluteUri(`${url}.${contentHash}`); - this.usedCacheKeys[cacheKey] = true; - - let cachedResponse: Response | undefined; - try { - cachedResponse = await cache.match(cacheKey); - } catch { - // Be tolerant to errors reading from the cache. This is a guard for https://bugs.chromium.org/p/chromium/issues/detail?id=968444 where - // chromium browsers may sometimes throw when working with the cache. - } - - if (cachedResponse) { - // It's in the cache. - const responseBytes = parseInt(cachedResponse.headers.get("content-length") || "0"); - this.cacheLoads[name] = { responseBytes }; - return cachedResponse; - } else { - // It's not in the cache. Fetch from network. - const networkResponse = await this.loadResourceWithoutCaching(name, url, contentHash, resourceType); - this.addToCacheAsync(cache, name, cacheKey, networkResponse); // Don't await - add to cache in background - return networkResponse; - } - } - - private loadResourceWithoutCaching(name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType): Promise { - // Allow developers to override how the resource is loaded - if (this.loadBootResource) { - const customLoadResult = this.loadBootResource(resourceType, name, url, contentHash); - if (customLoadResult instanceof Promise) { - // They are supplying an entire custom response, so just use that - return customLoadResult; - } else if (typeof customLoadResult === "string") { - // They are supplying a custom URL, so use that with the default fetch behavior - url = customLoadResult; - } - } - - // Note that if cacheBootResources was explicitly disabled, we also bypass hash checking - // This is to give developers an easy opt-out from the entire caching/validation flow if - // there's anything they don't like about it. - const fetchOptions: RequestInit = { - cache: networkFetchCacheMode - }; - - if (resourceType === "configuration") { - // Include credentials so the server can allow download / provide user specific file - fetchOptions.credentials = "include"; - } else { - // Any other resource than configuration should provide integrity check - fetchOptions.integrity = this.bootConfig.cacheBootResources ? contentHash : undefined; - } - - return fetch(url, fetchOptions); - } - - private async addToCacheAsync(cache: Cache, name: string, cacheKey: string, response: Response) { - // We have to clone in order to put this in the cache *and* not prevent other code from - // reading the original response stream. - const responseData = await response.clone().arrayBuffer(); - - // Now is an ideal moment to capture the performance stats for the request, since it - // only just completed and is most likely to still be in the buffer. However this is - // only done on a 'best effort' basis. Even if we do receive an entry, some of its - // properties may be blanked out if it was a CORS request. - const performanceEntry = getPerformanceEntry(response.url); - const responseBytes = (performanceEntry && performanceEntry.encodedBodySize) || undefined; - this.networkLoads[name] = { responseBytes }; - - // Add to cache as a custom response object so we can track extra data such as responseBytes - // We can't rely on the server sending content-length (ASP.NET Core doesn't by default) - const responseToCache = new Response(responseData, { - headers: { - "content-type": response.headers.get("content-type") || "", - "content-length": (responseBytes || response.headers.get("content-length") || "").toString(), - }, - }); - - try { - await cache.put(cacheKey, responseToCache); - } catch { - // Be tolerant to errors writing to the cache. This is a guard for https://bugs.chromium.org/p/chromium/issues/detail?id=968444 where - // chromium browsers may sometimes throw when performing cache operations. - } - } -} - -async function getCacheToUseIfEnabled(bootConfig: BootJsonData): Promise { - // caches will be undefined if we're running on an insecure origin (secure means https or localhost) - if (!bootConfig.cacheBootResources || typeof caches === "undefined") { - return null; - } - - // cache integrity is compromised if the first request has been served over http (except localhost) - // in this case, we want to disable caching and integrity validation - if (window.isSecureContext === false) { - return null; - } - - // Define a separate cache for each base href, so we're isolated from any other - // Blazor application running on the same origin. We need this so that we're free - // to purge from the cache anything we're not using and don't let it keep growing, - // since we don't want to be worst offenders for space usage. - const relativeBaseHref = document.baseURI.substring(document.location.origin.length); - const cacheName = `dotnet-resources-${relativeBaseHref}`; - - try { - // There's a Chromium bug we need to be aware of here: the CacheStorage APIs say that when - // caches.open(name) returns a promise that succeeds, the value is meant to be a Cache instance. - // However, if the browser was launched with a --user-data-dir param that's "too long" in some sense, - // then even through the promise resolves as success, the value given is `undefined`. - // See https://stackoverflow.com/a/46626574 and https://bugs.chromium.org/p/chromium/issues/detail?id=1054541 - // If we see this happening, return "null" to mean "proceed without caching". - return (await caches.open(cacheName)) || null; - } catch { - // There's no known scenario where we should get an exception here, but considering the - // Chromium bug above, let's tolerate it and treat as "proceed without caching". - return null; - } -} - -function countTotalBytes(loads: LoadLogEntry[]) { - return loads.reduce((prev, item) => prev + (item.responseBytes || 0), 0); -} - -function toDataSizeString(byteCount: number) { - return `${(byteCount / (1024 * 1024)).toFixed(2)} MB`; -} - -function getPerformanceEntry(url: string): PerformanceResourceTiming | undefined { - if (typeof performance !== "undefined") { - return performance.getEntriesByName(url)[0] as PerformanceResourceTiming; - } -} - -interface LoadLogEntry { - responseBytes: number | undefined; -} - -export interface LoadingResource { - name: string; - url: string; - response: Promise; -} diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index de59201b403baa..49f0783e2e4878 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -2,17 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { DotnetModuleInternal, MonoConfigInternal } from "../../types/internal"; -import { GlobalizationMode, type AssetBehaviours, type AssetEntry, type LoadBootResourceCallback, type LoadingResource, type WebAssemblyBootResourceType } from "../../types"; +import { GlobalizationMode, type AssetBehaviours, type AssetEntry, type LoadingResource, type WebAssemblyBootResourceType } from "../../types"; import type { BootJsonData } from "../../types/blazor"; -import { ENVIRONMENT_IS_WEB, INTERNAL, loaderHelpers } from "../globals"; -import { WebAssemblyResourceLoader } from "./WebAssemblyResourceLoader"; +import { ENVIRONMENT_IS_WEB, loaderHelpers } from "../globals"; +import { initCacheToUseIfEnabled, loadResource } from "../resourceLoader"; import { hasDebuggingEnabled } from "./_Polyfill"; import { ICUDataMode } from "../../types/blazor"; import { appendUniqueQuery } from "../assets"; -let resourceLoader: WebAssemblyResourceLoader; - export async function loadBootConfig(config: MonoConfigInternal, module: DotnetModuleInternal) { const defaultBootJsonLocation = "_framework/blazor.boot.json"; const loaderResponse = loaderHelpers.loadBootResource !== undefined ? @@ -31,7 +29,7 @@ export async function loadBootConfig(config: MonoConfigInternal, module: DotnetM const bootConfig: BootJsonData = await bootConfigResponse.json(); - await initializeBootConfig(config, bootConfigResponse, bootConfig, module, loaderHelpers.loadBootResource); + await initializeBootConfig(config, bootConfigResponse, bootConfig, module); function defaultLoadBlazorBootJson(url: string): Promise { return fetch(url, { @@ -65,14 +63,11 @@ function readBootConfigResponseHeaders(config: MonoConfigInternal, bootConfigRes } } -export async function initializeBootConfig(config: MonoConfigInternal, bootConfigResponse: Response, bootConfig: BootJsonData, module: DotnetModuleInternal, loadBootResource?: LoadBootResourceCallback) { +export async function initializeBootConfig(config: MonoConfigInternal, bootConfigResponse: Response, bootConfig: BootJsonData, module: DotnetModuleInternal) { readBootConfigResponseHeaders(config, bootConfigResponse); - INTERNAL.resourceLoader = resourceLoader = await WebAssemblyResourceLoader.initAsync(bootConfig, loadBootResource); - mapBootConfigToMonoConfig(config); - - if (ENVIRONMENT_IS_WEB) { - setupModuleForBlazor(module); - } + await initCacheToUseIfEnabled(config); + mapBootConfigToMonoConfig(config, bootConfig); + hookDownloadResource(module); } let resourcesLoaded = 0; @@ -97,7 +92,7 @@ const monoToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType | u "dotnetwasm": "dotnetwasm", }; -export function setupModuleForBlazor(module: DotnetModuleInternal) { +export function hookDownloadResource(module: DotnetModuleInternal) { // it would not `loadResource` on types for which there is no typesMap mapping const downloadResource = (asset: AssetEntry): LoadingResource | undefined => { // GOTCHA: the mapping to blazor asset type may not cover all mono owned asset types in the future in which case: @@ -106,7 +101,7 @@ export function setupModuleForBlazor(module: DotnetModuleInternal) { // C) or we could return `undefined` and let the runtime to load the asset. In which case the progress will not be reported on it and blazor will not be able to cache it. const type = monoToBlazorAssetTypeMap[asset.behavior]; if (type !== undefined) { - const res = resourceLoader.loadResource(asset.name, asset.resolvedUrl!, asset.hash!, type); + const res = loadResource(asset.name, asset.resolvedUrl!, asset.hash!, type); totalResources.add(asset.name!); res.response.then(() => { @@ -123,21 +118,23 @@ export function setupModuleForBlazor(module: DotnetModuleInternal) { loaderHelpers.downloadResource = downloadResource; // polyfills were already assigned } -export function mapBootConfigToMonoConfig(config: MonoConfigInternal) { - const resources = resourceLoader.bootConfig.resources; +export function mapBootConfigToMonoConfig(config: MonoConfigInternal, bootConfig: BootJsonData) { + const resources = bootConfig.resources; const assets: AssetEntry[] = []; const environmentVariables: any = { // From boot config - ...(resourceLoader.bootConfig.environmentVariables || {}), + ...(bootConfig.environmentVariables || {}), // From JavaScript ...(config.environmentVariables || {}) }; - config.remoteSources = (resourceLoader.bootConfig.resources as any).remoteSources; - config.assetsHash = resourceLoader.bootConfig.resources.hash; + config.cacheBootResources = bootConfig.cacheBootResources; + config.linkerEnabled = bootConfig.linkerEnabled; + config.remoteSources = (resources as any).remoteSources; + config.assetsHash = bootConfig.resources.hash; config.assets = assets; - config.extensions = resourceLoader.bootConfig.extensions; + config.extensions = bootConfig.extensions; config.resources = { extensions: resources.extensions }; @@ -147,11 +144,11 @@ export function mapBootConfigToMonoConfig(config: MonoConfigInternal) { // - Build (release) => debugBuild=true & debugLevel=0 => 0 // - Publish (debug) => debugBuild=false & debugLevel=-1 => 0 // - Publish (release) => debugBuild=false & debugLevel=0 => 0 - config.debugLevel = hasDebuggingEnabled(resourceLoader.bootConfig) ? resourceLoader.bootConfig.debugLevel : 0; - config.mainAssemblyName = resourceLoader.bootConfig.entryAssembly; + config.debugLevel = hasDebuggingEnabled(config) ? bootConfig.debugLevel : 0; + config.mainAssemblyName = bootConfig.entryAssembly; - const anyBootConfig = (resourceLoader.bootConfig as any); - for (const key in resourceLoader.bootConfig) { + const anyBootConfig = (bootConfig as any); + for (const key in bootConfig) { if (Object.prototype.hasOwnProperty.call(anyBootConfig, key)) { if (anyBootConfig[key] === null) { delete anyBootConfig[key]; @@ -161,17 +158,17 @@ export function mapBootConfigToMonoConfig(config: MonoConfigInternal) { // FIXME this mix of both formats is ugly temporary hack Object.assign(config, { - ...resourceLoader.bootConfig, + ...bootConfig, }); config.environmentVariables = environmentVariables; - if (resourceLoader.bootConfig.startupMemoryCache !== undefined) { - config.startupMemoryCache = resourceLoader.bootConfig.startupMemoryCache; + if (bootConfig.startupMemoryCache !== undefined) { + config.startupMemoryCache = bootConfig.startupMemoryCache; } - if (resourceLoader.bootConfig.runtimeOptions) { - config.runtimeOptions = [...(config.runtimeOptions || []), ...resourceLoader.bootConfig.runtimeOptions]; + if (bootConfig.runtimeOptions) { + config.runtimeOptions = [...(config.runtimeOptions || []), ...(bootConfig.runtimeOptions || [])]; } // any runtime owned assets, with proper behavior already set @@ -190,7 +187,7 @@ export function mapBootConfigToMonoConfig(config: MonoConfigInternal) { }; assets.push(asset); } - if (hasDebuggingEnabled(resourceLoader.bootConfig) && resources.pdb) { + if (hasDebuggingEnabled(config) && resources.pdb) { for (const name in resources.pdb) { const asset: AssetEntry = { name, @@ -202,13 +199,13 @@ export function mapBootConfigToMonoConfig(config: MonoConfigInternal) { } } const applicationCulture = config.applicationCulture || (ENVIRONMENT_IS_WEB ? (navigator.languages && navigator.languages[0]) : Intl.DateTimeFormat().resolvedOptions().locale); - const icuDataResourceName = getICUResourceName(resourceLoader.bootConfig, config, applicationCulture); + const icuDataResourceName = getICUResourceName(bootConfig, config, applicationCulture); let hasIcuData = false; for (const name in resources.runtime) { const behavior = behaviorByName(name) as any; let loadRemote = false; if (behavior === "icu") { - if (resourceLoader.bootConfig.icuDataMode === ICUDataMode.Invariant) { + if (bootConfig.icuDataMode === ICUDataMode.Invariant) { continue; } if (name !== icuDataResourceName) { @@ -246,8 +243,8 @@ export function mapBootConfigToMonoConfig(config: MonoConfigInternal) { } } - for (let i = 0; i < resourceLoader.bootConfig.config.length; i++) { - const configUrl = resourceLoader.bootConfig.config[i]; + for (let i = 0; i < bootConfig.config.length; i++) { + const configUrl = bootConfig.config[i]; const configFileName = fileName(configUrl); if (configFileName === "appsettings.json" || configFileName === `appsettings.${config.applicationEnvironment}.json`) { assets.push({ @@ -275,28 +272,20 @@ export function mapBootConfigToMonoConfig(config: MonoConfigInternal) { config.globalizationMode = GlobalizationMode.Invariant; } - if (resourceLoader.bootConfig.modifiableAssemblies) { + if (bootConfig.modifiableAssemblies) { // Configure the app to enable hot reload in Development. - environmentVariables["DOTNET_MODIFIABLE_ASSEMBLIES"] = resourceLoader.bootConfig.modifiableAssemblies; + environmentVariables["DOTNET_MODIFIABLE_ASSEMBLIES"] = bootConfig.modifiableAssemblies; } - if (resourceLoader.bootConfig.aspnetCoreBrowserTools) { + if (bootConfig.aspnetCoreBrowserTools) { // See https://github.com/dotnet/aspnetcore/issues/37357#issuecomment-941237000 - environmentVariables["__ASPNETCORE_BROWSER_TOOLS"] = resourceLoader.bootConfig.aspnetCoreBrowserTools; + environmentVariables["__ASPNETCORE_BROWSER_TOOLS"] = bootConfig.aspnetCoreBrowserTools; } if (config.applicationCulture) { // If a culture is specified via start options use that to initialize the Emscripten \ .NET culture. environmentVariables["LANG"] = `${config.applicationCulture}.UTF-8`; } - - if (resourceLoader.bootConfig.startupMemoryCache !== undefined) { - config.startupMemoryCache = resourceLoader.bootConfig.startupMemoryCache; - } - - if (resourceLoader.bootConfig.runtimeOptions) { - config.runtimeOptions = [...(config.runtimeOptions || []), ...(resourceLoader.bootConfig.runtimeOptions || [])]; - } } function fileName(name: string) { diff --git a/src/mono/wasm/runtime/loader/blazor/_Polyfill.ts b/src/mono/wasm/runtime/loader/blazor/_Polyfill.ts index efe700b72bcca3..07faff6353ab77 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Polyfill.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Polyfill.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { BootJsonData } from "../../types/blazor"; +import type { MonoConfig } from "../../types"; import { loaderHelpers } from "../globals"; let testAnchor: HTMLAnchorElement; @@ -11,12 +11,12 @@ export function toAbsoluteUri(relativeUri: string): string { return testAnchor.href; } -export function hasDebuggingEnabled(bootConfig: BootJsonData): boolean { +export function hasDebuggingEnabled(config: MonoConfig): boolean { // Copied from blazor MonoDebugger.ts/attachDebuggerHotkey if (!globalThis.navigator) { return false; } - const hasReferencedPdbs = !!bootConfig.resources.pdb; - return (hasReferencedPdbs || bootConfig.debugBuild || bootConfig.debugLevel != 0) && (loaderHelpers.isChromium || loaderHelpers.isFirefox); + const hasReferencedPdbs = !!config.resources!.pdb; + return (hasReferencedPdbs || config.debugLevel != 0) && (loaderHelpers.isChromium || loaderHelpers.isFirefox); } \ No newline at end of file diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index 6dbe972ced9448..7127cb60aabdb6 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -89,7 +89,7 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi if (loadedAnyConfig.resources) { // If we found boot config schema normalizeConfig(); - await initializeBootConfig(loadedAnyConfig, configResponse, loadedAnyConfig, module, loaderHelpers.loadBootResource); + await initializeBootConfig(loadedAnyConfig, configResponse, loadedAnyConfig, module); } else { // Otherwise we found mono config schema const loadedConfig = loadedAnyConfig as MonoConfigInternal; diff --git a/src/mono/wasm/runtime/loader/globals.ts b/src/mono/wasm/runtime/loader/globals.ts index 7fac0d5ac7e852..896531d3adc148 100644 --- a/src/mono/wasm/runtime/loader/globals.ts +++ b/src/mono/wasm/runtime/loader/globals.ts @@ -9,6 +9,7 @@ import { mono_download_assets, resolve_asset_path } from "./assets"; import { mono_log_error, setup_proxy_console } from "./logging"; import { hasDebuggingEnabled } from "./blazor/_Polyfill"; import { invokeLibraryInitializers } from "./libraryInitializers"; +import { loadResource, loadResources } from "./resourceLoader"; export const ENVIRONMENT_IS_NODE = typeof process == "object" && typeof process.versions == "object" && typeof process.versions.node == "string"; export const ENVIRONMENT_IS_WEB = typeof window == "object"; @@ -92,6 +93,8 @@ export function setLoaderGlobals( setup_proxy_console, hasDebuggingEnabled, + loadResource, + loadResources, invokeLibraryInitializers, } as Partial); diff --git a/src/mono/wasm/runtime/loader/resourceLoader.ts b/src/mono/wasm/runtime/loader/resourceLoader.ts new file mode 100644 index 00000000000000..ff67e19e93eaac --- /dev/null +++ b/src/mono/wasm/runtime/loader/resourceLoader.ts @@ -0,0 +1,240 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import type { MonoConfig, WebAssemblyBootResourceType } from "../types"; +import type { ResourceList } from "../types/blazor"; +import { loaderHelpers } from "./globals"; +import { toAbsoluteUri } from "./blazor/_Polyfill"; +const networkFetchCacheMode = "no-cache"; + +const cacheSkipResourceTypes = ["configuration"]; +const usedCacheKeys: { [key: string]: boolean } = {}; +const networkLoads: { [name: string]: LoadLogEntry } = {}; +const cacheLoads: { [name: string]: LoadLogEntry } = {}; +let cacheIfUsed: Cache | null; + +export function loadResources(resources: ResourceList, url: (name: string) => string, resourceType: WebAssemblyBootResourceType): LoadingResource[] { + return Object.keys(resources) + .map(name => loadResource(name, url(name), resources[name], resourceType)); +} + +export function loadResource(name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType): LoadingResource { + const response = cacheIfUsed && !cacheSkipResourceTypes.includes(resourceType) + ? loadResourceWithCaching(cacheIfUsed, name, url, contentHash, resourceType) + : loadResourceWithoutCaching(name, url, contentHash, resourceType); + + const absoluteUrl = toAbsoluteUri(url); + + if (resourceType == "assembly") { + loaderHelpers.loadedAssemblies.push(absoluteUrl); + } + return { name, url: absoluteUrl, response }; +} + +export function logToConsole(): void { + const cacheLoadsEntries = Object.values(cacheLoads); + const networkLoadsEntries = Object.values(networkLoads); + const cacheResponseBytes = countTotalBytes(cacheLoadsEntries); + const networkResponseBytes = countTotalBytes(networkLoadsEntries); + const totalResponseBytes = cacheResponseBytes + networkResponseBytes; + if (totalResponseBytes === 0) { + // We have no perf stats to display, likely because caching is not in use. + return; + } + + const linkerDisabledWarning = loaderHelpers.config.linkerEnabled ? "%c" : "\n%cThis application was built with linking (tree shaking) disabled. Published applications will be significantly smaller."; + // eslint-disable-next-line no-console + console.groupCollapsed(`%cdotnet%c Loaded ${toDataSizeString(totalResponseBytes)} resources${linkerDisabledWarning}`, "background: purple; color: white; padding: 1px 3px; border-radius: 3px;", "font-weight: bold;", "font-weight: normal;"); + + if (cacheLoadsEntries.length) { + // eslint-disable-next-line no-console + console.groupCollapsed(`Loaded ${toDataSizeString(cacheResponseBytes)} resources from cache`); + // eslint-disable-next-line no-console + console.table(cacheLoads); + // eslint-disable-next-line no-console + console.groupEnd(); + } + + if (networkLoadsEntries.length) { + // eslint-disable-next-line no-console + console.groupCollapsed(`Loaded ${toDataSizeString(networkResponseBytes)} resources from network`); + // eslint-disable-next-line no-console + console.table(networkLoads); + // eslint-disable-next-line no-console + console.groupEnd(); + } + + // eslint-disable-next-line no-console + console.groupEnd(); +} + +export async function purgeUnusedCacheEntriesAsync(): Promise { + // We want to keep the cache small because, even though the browser will evict entries if it + // gets too big, we don't want to be considered problematic by the end user viewing storage stats + const cache = cacheIfUsed; + if (cache) { + const cachedRequests = await cache.keys(); + const deletionPromises = cachedRequests.map(async cachedRequest => { + if (!(cachedRequest.url in usedCacheKeys)) { + await cache.delete(cachedRequest); + } + }); + + await Promise.all(deletionPromises); + } +} + +async function loadResourceWithCaching(cache: Cache, name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType) { + // Since we are going to cache the response, we require there to be a content hash for integrity + // checking. We don't want to cache bad responses. There should always be a hash, because the build + // process generates this data. + if (!contentHash || contentHash.length === 0) { + throw new Error("Content hash is required"); + } + + const cacheKey = toAbsoluteUri(`${url}.${contentHash}`); + usedCacheKeys[cacheKey] = true; + + let cachedResponse: Response | undefined; + try { + cachedResponse = await cache.match(cacheKey); + } catch { + // Be tolerant to errors reading from the cache. This is a guard for https://bugs.chromium.org/p/chromium/issues/detail?id=968444 where + // chromium browsers may sometimes throw when working with the cache. + } + + if (cachedResponse) { + // It's in the cache. + const responseBytes = parseInt(cachedResponse.headers.get("content-length") || "0"); + cacheLoads[name] = { responseBytes }; + return cachedResponse; + } else { + // It's not in the cache. Fetch from network. + const networkResponse = await loadResourceWithoutCaching(name, url, contentHash, resourceType); + addToCacheAsync(cache, name, cacheKey, networkResponse); // Don't await - add to cache in background + return networkResponse; + } +} + +function loadResourceWithoutCaching(name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType): Promise { + // Allow developers to override how the resource is loaded + if (loaderHelpers.loadBootResource) { + const customLoadResult = loaderHelpers.loadBootResource(resourceType, name, url, contentHash); + if (customLoadResult instanceof Promise) { + // They are supplying an entire custom response, so just use that + return customLoadResult; + } else if (typeof customLoadResult === "string") { + // They are supplying a custom URL, so use that with the default fetch behavior + url = customLoadResult; + } + } + + // Note that if cacheBootResources was explicitly disabled, we also bypass hash checking + // This is to give developers an easy opt-out from the entire caching/validation flow if + // there's anything they don't like about it. + const fetchOptions: RequestInit = { + cache: networkFetchCacheMode + }; + + if (resourceType === "configuration") { + // Include credentials so the server can allow download / provide user specific file + fetchOptions.credentials = "include"; + } else { + // Any other resource than configuration should provide integrity check + fetchOptions.integrity = cacheIfUsed ? contentHash : undefined; + } + + return loaderHelpers.fetch_like(url, fetchOptions); +} + +async function addToCacheAsync(cache: Cache, name: string, cacheKey: string, response: Response) { + // We have to clone in order to put this in the cache *and* not prevent other code from + // reading the original response stream. + const responseData = await response.clone().arrayBuffer(); + + // Now is an ideal moment to capture the performance stats for the request, since it + // only just completed and is most likely to still be in the buffer. However this is + // only done on a 'best effort' basis. Even if we do receive an entry, some of its + // properties may be blanked out if it was a CORS request. + const performanceEntry = getPerformanceEntry(response.url); + const responseBytes = (performanceEntry && performanceEntry.encodedBodySize) || undefined; + networkLoads[name] = { responseBytes }; + + // Add to cache as a custom response object so we can track extra data such as responseBytes + // We can't rely on the server sending content-length (ASP.NET Core doesn't by default) + const responseToCache = new Response(responseData, { + headers: { + "content-type": response.headers.get("content-type") || "", + "content-length": (responseBytes || response.headers.get("content-length") || "").toString(), + }, + }); + + try { + await cache.put(cacheKey, responseToCache); + } catch { + // Be tolerant to errors writing to the cache. This is a guard for https://bugs.chromium.org/p/chromium/issues/detail?id=968444 where + // chromium browsers may sometimes throw when performing cache operations. + } +} + +export async function initCacheToUseIfEnabled(config: MonoConfig): Promise { + cacheIfUsed = await getCacheToUseIfEnabled(config); +} + +async function getCacheToUseIfEnabled(config: MonoConfig): Promise { + // caches will be undefined if we're running on an insecure origin (secure means https or localhost) + if (!config.cacheBootResources || typeof caches === "undefined") { + return null; + } + + // cache integrity is compromised if the first request has been served over http (except localhost) + // in this case, we want to disable caching and integrity validation + if (window.isSecureContext === false) { + return null; + } + + // Define a separate cache for each base href, so we're isolated from any other + // Blazor application running on the same origin. We need this so that we're free + // to purge from the cache anything we're not using and don't let it keep growing, + // since we don't want to be worst offenders for space usage. + const relativeBaseHref = document.baseURI.substring(document.location.origin.length); + const cacheName = `dotnet-resources-${relativeBaseHref}`; + + try { + // There's a Chromium bug we need to be aware of here: the CacheStorage APIs say that when + // caches.open(name) returns a promise that succeeds, the value is meant to be a Cache instance. + // However, if the browser was launched with a --user-data-dir param that's "too long" in some sense, + // then even through the promise resolves as success, the value given is `undefined`. + // See https://stackoverflow.com/a/46626574 and https://bugs.chromium.org/p/chromium/issues/detail?id=1054541 + // If we see this happening, return "null" to mean "proceed without caching". + return (await caches.open(cacheName)) || null; + } catch { + // There's no known scenario where we should get an exception here, but considering the + // Chromium bug above, let's tolerate it and treat as "proceed without caching". + return null; + } +} + +function countTotalBytes(loads: LoadLogEntry[]) { + return loads.reduce((prev, item) => prev + (item.responseBytes || 0), 0); +} + +function toDataSizeString(byteCount: number) { + return `${(byteCount / (1024 * 1024)).toFixed(2)} MB`; +} + +function getPerformanceEntry(url: string): PerformanceResourceTiming | undefined { + if (typeof performance !== "undefined") { + return performance.getEntriesByName(url)[0] as PerformanceResourceTiming; + } +} + +interface LoadLogEntry { + responseBytes: number | undefined; +} + +export interface LoadingResource { + name: string; + url: string; + response: Promise; +} diff --git a/src/mono/wasm/runtime/satelliteAssemblies.ts b/src/mono/wasm/runtime/satelliteAssemblies.ts index 612d757efe8591..83b93f868fbcd0 100644 --- a/src/mono/wasm/runtime/satelliteAssemblies.ts +++ b/src/mono/wasm/runtime/satelliteAssemblies.ts @@ -1,20 +1,18 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { INTERNAL, loaderHelpers, runtimeHelpers } from "./globals"; -import type { WebAssemblyResourceLoader } from "./loader/blazor/WebAssemblyResourceLoader"; +import { loaderHelpers, runtimeHelpers } from "./globals"; import { LoadingResource } from "./types"; export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise { - const resourceLoader: WebAssemblyResourceLoader = INTERNAL.resourceLoader; - const satelliteResources = resourceLoader.bootConfig.resources.satelliteResources; + const satelliteResources = loaderHelpers.config.resources!.satelliteResources; if (!satelliteResources) { return; } await Promise.all(culturesToLoad! .filter(culture => Object.prototype.hasOwnProperty.call(satelliteResources, culture)) - .map(culture => resourceLoader.loadResources(satelliteResources[culture], fileName => loaderHelpers.locateFile(fileName), "assembly")) + .map(culture => loaderHelpers.loadResources(satelliteResources[culture], fileName => loaderHelpers.locateFile(fileName), "assembly")) .reduce((previous, next) => previous.concat(next), new Array()) .map(async resource => { const response = await resource.response; diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 46f7ac4b8521ac..d271a29b242425 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -271,7 +271,7 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { if (!runtimeHelpers.mono_wasm_runtime_is_ready) mono_wasm_runtime_ready(); if (INTERNAL.resourceLoader) { - if (INTERNAL.resourceLoader.bootConfig.debugBuild && INTERNAL.resourceLoader.bootConfig.cacheBootResources) { + if (loaderHelpers.config.debugLevel !== 0 && loaderHelpers.config.cacheBootResources) { INTERNAL.resourceLoader.logToConsole(); } INTERNAL.resourceLoader.purgeUnusedCacheEntriesAsync(); // Don't await - it's fine to run in background diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 0edc4d3f20a580..9124d7b681b7f5 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -62,6 +62,11 @@ export type MonoConfig = { * debugLevel < 0 enables debugging and disables debug logging. */ debugLevel?: number, + + /** + * Gets a value that determines whether to enable caching of the 'resources' inside a CacheStorage instance within the browser. + */ + cacheBootResources?: boolean, /** * Enables diagnostic log messages during startup */ diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 27cbffb4566e3a..fa291f4bf1161c 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -1,8 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { AssetBehaviours, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, ResourceRequest, RuntimeAPI } from "."; -import type { BootJsonData } from "./blazor"; +import type { AssetBehaviours, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, ResourceList, ResourceRequest, RuntimeAPI, WebAssemblyBootResourceType } from "."; import type { CharPtr, EmscriptenModule, ManagedPointer, NativePointer, VoidPtr, Int32Ptr } from "./emscripten"; export type GCHandle = { @@ -69,6 +68,7 @@ export function coerceNull(ptr: T | nu // when adding new fields, please consider if it should be impacting the snapshot hash. If not, please drop it in the snapshot getCacheKey() export type MonoConfigInternal = MonoConfig & { + linkerEnabled?: boolean, assets?: AssetEntry[], runtimeOptions?: string[], // array of runtime options as strings aotProfilerOptions?: AOTProfilerOptions, // dictionary-style Object. If omitted, aot profiler will not be initialized. @@ -143,7 +143,9 @@ export type LoaderHelpers = { err(message: string): void; getApplicationEnvironment?: (bootConfigResponse: Response) => string | null; - hasDebuggingEnabled(bootConfig: BootJsonData): boolean, + hasDebuggingEnabled(config: MonoConfig): boolean, + loadResources(resources: ResourceList, url: (name: string) => string, resourceType: WebAssemblyBootResourceType): LoadingResource[], + loadResource(name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType): LoadingResource, loadBootResource?: LoadBootResourceCallback; invokeLibraryInitializers: (functionName: string, args: any[]) => Promise, From d114b305a343b8fa3188a29ea72bf860cb776c6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 25 Jul 2023 17:55:55 +0200 Subject: [PATCH 05/65] Fixes --- src/mono/wasm/runtime/loader/blazor/_Polyfill.ts | 3 +++ src/mono/wasm/runtime/loader/config.ts | 1 + src/mono/wasm/runtime/loader/resourceLoader.ts | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/runtime/loader/blazor/_Polyfill.ts b/src/mono/wasm/runtime/loader/blazor/_Polyfill.ts index 07faff6353ab77..3735d26d6e6eaf 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Polyfill.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Polyfill.ts @@ -6,6 +6,9 @@ import { loaderHelpers } from "../globals"; let testAnchor: HTMLAnchorElement; export function toAbsoluteUri(relativeUri: string): string { + if (!globalThis.document) + return loaderHelpers.locateFile(relativeUri); + testAnchor = testAnchor || document.createElement("a"); testAnchor.href = relativeUri; return testAnchor.href; diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index 7127cb60aabdb6..1bad3c65911044 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -90,6 +90,7 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi // If we found boot config schema normalizeConfig(); await initializeBootConfig(loadedAnyConfig, configResponse, loadedAnyConfig, module); + deep_merge_config(loaderHelpers.config, loadedAnyConfig); } else { // Otherwise we found mono config schema const loadedConfig = loadedAnyConfig as MonoConfigInternal; diff --git a/src/mono/wasm/runtime/loader/resourceLoader.ts b/src/mono/wasm/runtime/loader/resourceLoader.ts index ff67e19e93eaac..a98be8d49bc6c5 100644 --- a/src/mono/wasm/runtime/loader/resourceLoader.ts +++ b/src/mono/wasm/runtime/loader/resourceLoader.ts @@ -183,7 +183,7 @@ export async function initCacheToUseIfEnabled(config: MonoConfig): Promise async function getCacheToUseIfEnabled(config: MonoConfig): Promise { // caches will be undefined if we're running on an insecure origin (secure means https or localhost) - if (!config.cacheBootResources || typeof caches === "undefined") { + if (!config.cacheBootResources || typeof globalThis.caches === "undefined" || typeof globalThis.document === "undefined") { return null; } @@ -197,7 +197,7 @@ async function getCacheToUseIfEnabled(config: MonoConfig): Promise // Blazor application running on the same origin. We need this so that we're free // to purge from the cache anything we're not using and don't let it keep growing, // since we don't want to be worst offenders for space usage. - const relativeBaseHref = document.baseURI.substring(document.location.origin.length); + const relativeBaseHref = globalThis.document.baseURI.substring(globalThis.document.location.origin.length); const cacheName = `dotnet-resources-${relativeBaseHref}`; try { From 5e8c36429aa1464c2ec4ac2589c8a74b76583a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 25 Jul 2023 18:21:47 +0200 Subject: [PATCH 06/65] Remove _Polyfill.ts --- .../runtime/loader/blazor/_Integration.ts | 4 +-- .../wasm/runtime/loader/blazor/_Polyfill.ts | 25 ------------------- src/mono/wasm/runtime/loader/config.ts | 10 ++++++++ src/mono/wasm/runtime/loader/globals.ts | 2 +- .../wasm/runtime/loader/resourceLoader.ts | 5 ++-- 5 files changed, 15 insertions(+), 31 deletions(-) delete mode 100644 src/mono/wasm/runtime/loader/blazor/_Polyfill.ts diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 49f0783e2e4878..961f690a745cbd 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -7,9 +7,9 @@ import type { BootJsonData } from "../../types/blazor"; import { ENVIRONMENT_IS_WEB, loaderHelpers } from "../globals"; import { initCacheToUseIfEnabled, loadResource } from "../resourceLoader"; -import { hasDebuggingEnabled } from "./_Polyfill"; import { ICUDataMode } from "../../types/blazor"; import { appendUniqueQuery } from "../assets"; +import { hasDebuggingEnabled } from "../config"; export async function loadBootConfig(config: MonoConfigInternal, module: DotnetModuleInternal) { const defaultBootJsonLocation = "_framework/blazor.boot.json"; @@ -187,7 +187,7 @@ export function mapBootConfigToMonoConfig(config: MonoConfigInternal, bootConfig }; assets.push(asset); } - if (hasDebuggingEnabled(config) && resources.pdb) { + if (config.debugLevel != 0 && resources.pdb) { for (const name in resources.pdb) { const asset: AssetEntry = { name, diff --git a/src/mono/wasm/runtime/loader/blazor/_Polyfill.ts b/src/mono/wasm/runtime/loader/blazor/_Polyfill.ts deleted file mode 100644 index 3735d26d6e6eaf..00000000000000 --- a/src/mono/wasm/runtime/loader/blazor/_Polyfill.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import type { MonoConfig } from "../../types"; -import { loaderHelpers } from "../globals"; - -let testAnchor: HTMLAnchorElement; -export function toAbsoluteUri(relativeUri: string): string { - if (!globalThis.document) - return loaderHelpers.locateFile(relativeUri); - - testAnchor = testAnchor || document.createElement("a"); - testAnchor.href = relativeUri; - return testAnchor.href; -} - -export function hasDebuggingEnabled(config: MonoConfig): boolean { - // Copied from blazor MonoDebugger.ts/attachDebuggerHotkey - if (!globalThis.navigator) { - return false; - } - - const hasReferencedPdbs = !!config.resources!.pdb; - return (hasReferencedPdbs || config.debugLevel != 0) && (loaderHelpers.isChromium || loaderHelpers.isFirefox); -} \ No newline at end of file diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index 1bad3c65911044..683121bdc0325c 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -121,4 +121,14 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi mono_exit(1, new Error(errMessage)); throw err; } +} + +export function hasDebuggingEnabled(config: MonoConfigInternal): boolean { + // Copied from blazor MonoDebugger.ts/attachDebuggerHotkey + if (!globalThis.navigator) { + return false; + } + + const hasReferencedPdbs = !!config.resources!.pdb; + return (hasReferencedPdbs || config.debugLevel != 0) && (loaderHelpers.isChromium || loaderHelpers.isFirefox); } \ No newline at end of file diff --git a/src/mono/wasm/runtime/loader/globals.ts b/src/mono/wasm/runtime/loader/globals.ts index 896531d3adc148..5e2e5a6b1a505b 100644 --- a/src/mono/wasm/runtime/loader/globals.ts +++ b/src/mono/wasm/runtime/loader/globals.ts @@ -7,9 +7,9 @@ import { assert_runtime_running, is_exited, is_runtime_running, mono_exit } from import { assertIsControllablePromise, createPromiseController, getPromiseController } from "./promise-controller"; import { mono_download_assets, resolve_asset_path } from "./assets"; import { mono_log_error, setup_proxy_console } from "./logging"; -import { hasDebuggingEnabled } from "./blazor/_Polyfill"; import { invokeLibraryInitializers } from "./libraryInitializers"; import { loadResource, loadResources } from "./resourceLoader"; +import { hasDebuggingEnabled } from "./config"; export const ENVIRONMENT_IS_NODE = typeof process == "object" && typeof process.versions == "object" && typeof process.versions.node == "string"; export const ENVIRONMENT_IS_WEB = typeof window == "object"; diff --git a/src/mono/wasm/runtime/loader/resourceLoader.ts b/src/mono/wasm/runtime/loader/resourceLoader.ts index a98be8d49bc6c5..010fa7bcc83220 100644 --- a/src/mono/wasm/runtime/loader/resourceLoader.ts +++ b/src/mono/wasm/runtime/loader/resourceLoader.ts @@ -4,7 +4,6 @@ import type { MonoConfig, WebAssemblyBootResourceType } from "../types"; import type { ResourceList } from "../types/blazor"; import { loaderHelpers } from "./globals"; -import { toAbsoluteUri } from "./blazor/_Polyfill"; const networkFetchCacheMode = "no-cache"; const cacheSkipResourceTypes = ["configuration"]; @@ -23,7 +22,7 @@ export function loadResource(name: string, url: string, contentHash: string, res ? loadResourceWithCaching(cacheIfUsed, name, url, contentHash, resourceType) : loadResourceWithoutCaching(name, url, contentHash, resourceType); - const absoluteUrl = toAbsoluteUri(url); + const absoluteUrl = loaderHelpers.locateFile(url); if (resourceType == "assembly") { loaderHelpers.loadedAssemblies.push(absoluteUrl); @@ -92,7 +91,7 @@ async function loadResourceWithCaching(cache: Cache, name: string, url: string, throw new Error("Content hash is required"); } - const cacheKey = toAbsoluteUri(`${url}.${contentHash}`); + const cacheKey = loaderHelpers.locateFile(`${url}.${contentHash}`); usedCacheKeys[cacheKey] = true; let cachedResponse: Response | undefined; From ce5982f12aca9edf2a6c5e924665f39e08727222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 25 Jul 2023 20:07:22 +0200 Subject: [PATCH 07/65] Fix passing wrong instance of monoConfig by remove the function parameter and using loaderHelpers --- .../runtime/loader/blazor/_Integration.ts | 19 +++++++++++-------- src/mono/wasm/runtime/loader/config.ts | 4 ++-- .../wasm/runtime/loader/resourceLoader.ts | 4 ++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 961f690a745cbd..abdf2699ee3fdb 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -11,7 +11,7 @@ import { ICUDataMode } from "../../types/blazor"; import { appendUniqueQuery } from "../assets"; import { hasDebuggingEnabled } from "../config"; -export async function loadBootConfig(config: MonoConfigInternal, module: DotnetModuleInternal) { +export async function loadBootConfig(module: DotnetModuleInternal) { const defaultBootJsonLocation = "_framework/blazor.boot.json"; const loaderResponse = loaderHelpers.loadBootResource !== undefined ? loaderHelpers.loadBootResource("manifest", "blazor.boot.json", defaultBootJsonLocation, "") : @@ -29,7 +29,7 @@ export async function loadBootConfig(config: MonoConfigInternal, module: DotnetM const bootConfig: BootJsonData = await bootConfigResponse.json(); - await initializeBootConfig(config, bootConfigResponse, bootConfig, module); + await initializeBootConfig(bootConfigResponse, bootConfig, module); function defaultLoadBlazorBootJson(url: string): Promise { return fetch(url, { @@ -40,7 +40,9 @@ export async function loadBootConfig(config: MonoConfigInternal, module: DotnetM } } -function readBootConfigResponseHeaders(config: MonoConfigInternal, bootConfigResponse: Response) { +function readBootConfigResponseHeaders(bootConfigResponse: Response) { + const config = loaderHelpers.config; + config.applicationEnvironment = config.applicationEnvironment || (loaderHelpers.getApplicationEnvironment && loaderHelpers.getApplicationEnvironment(bootConfigResponse)) || bootConfigResponse.headers.get("Blazor-Environment") @@ -63,10 +65,10 @@ function readBootConfigResponseHeaders(config: MonoConfigInternal, bootConfigRes } } -export async function initializeBootConfig(config: MonoConfigInternal, bootConfigResponse: Response, bootConfig: BootJsonData, module: DotnetModuleInternal) { - readBootConfigResponseHeaders(config, bootConfigResponse); - await initCacheToUseIfEnabled(config); - mapBootConfigToMonoConfig(config, bootConfig); +export async function initializeBootConfig(bootConfigResponse: Response, bootConfig: BootJsonData, module: DotnetModuleInternal) { + readBootConfigResponseHeaders(bootConfigResponse); + await initCacheToUseIfEnabled(); + mapBootConfigToMonoConfig(bootConfig); hookDownloadResource(module); } @@ -118,7 +120,8 @@ export function hookDownloadResource(module: DotnetModuleInternal) { loaderHelpers.downloadResource = downloadResource; // polyfills were already assigned } -export function mapBootConfigToMonoConfig(config: MonoConfigInternal, bootConfig: BootJsonData) { +export function mapBootConfigToMonoConfig(bootConfig: BootJsonData) { + const config = loaderHelpers.config; const resources = bootConfig.resources; const assets: AssetEntry[] = []; diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index 683121bdc0325c..f4817852527683 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -80,7 +80,7 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi try { if (loaderHelpers.loadBootResource) { // If we have custom loadBootResource - await loadBootConfig(loaderHelpers.config, module); + await loadBootConfig(module); } else { // Otherwise load using fetch_like const resolveSrc = loaderHelpers.locateFile(configFilePath); @@ -89,7 +89,7 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi if (loadedAnyConfig.resources) { // If we found boot config schema normalizeConfig(); - await initializeBootConfig(loadedAnyConfig, configResponse, loadedAnyConfig, module); + await initializeBootConfig(configResponse, loadedAnyConfig, module); deep_merge_config(loaderHelpers.config, loadedAnyConfig); } else { // Otherwise we found mono config schema diff --git a/src/mono/wasm/runtime/loader/resourceLoader.ts b/src/mono/wasm/runtime/loader/resourceLoader.ts index 010fa7bcc83220..0c382f33774ddf 100644 --- a/src/mono/wasm/runtime/loader/resourceLoader.ts +++ b/src/mono/wasm/runtime/loader/resourceLoader.ts @@ -176,8 +176,8 @@ async function addToCacheAsync(cache: Cache, name: string, cacheKey: string, res } } -export async function initCacheToUseIfEnabled(config: MonoConfig): Promise { - cacheIfUsed = await getCacheToUseIfEnabled(config); +export async function initCacheToUseIfEnabled(): Promise { + cacheIfUsed = await getCacheToUseIfEnabled(loaderHelpers.config); } async function getCacheToUseIfEnabled(config: MonoConfig): Promise { From 9b98975e09b25ebd14af9631139f3048ec53d997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 25 Jul 2023 22:26:53 +0200 Subject: [PATCH 08/65] Remove withStartupOptions --- src/mono/wasm/runtime/loader/run.ts | 9 +-------- src/mono/wasm/runtime/types/index.ts | 23 ----------------------- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts index 6d9e0007abd647..e347fdd230c8c4 100644 --- a/src/mono/wasm/runtime/loader/run.ts +++ b/src/mono/wasm/runtime/loader/run.ts @@ -3,7 +3,7 @@ import BuildConfiguration from "consts:configuration"; -import type { MonoConfig, DotnetHostBuilder, DotnetModuleConfig, RuntimeAPI, WebAssemblyStartOptions, LoadBootResourceCallback } from "../types"; +import type { MonoConfig, DotnetHostBuilder, DotnetModuleConfig, RuntimeAPI, LoadBootResourceCallback } from "../types"; import type { MonoConfigInternal, EmscriptenModuleInternal, RuntimeModuleExportsInternal, NativeModuleExportsInternal, } from "../types/internal"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_WEB, exportedRuntimeAPI, globalObjectsRoot, mono_assert } from "./globals"; @@ -316,13 +316,6 @@ export class HostBuilder implements DotnetHostBuilder { } } - withStartupOptions(startupOptions: Partial): DotnetHostBuilder { - return this - .withApplicationEnvironment(startupOptions.environment) - .withApplicationCulture(startupOptions.applicationCulture) - .withResourceLoader(startupOptions.loadBootResource); - } - withApplicationEnvironment(applicationEnvironment?: string): DotnetHostBuilder { try { deep_merge_config(monoConfig, { diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 9124d7b681b7f5..b33f22e22af449 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -347,29 +347,6 @@ export type ModuleAPI = { export type CreateDotnetRuntimeType = (moduleFactory: DotnetModuleConfig | ((api: RuntimeAPI) => DotnetModuleConfig)) => Promise; -export interface WebAssemblyStartOptions { - /** - * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched - * from a custom source, such as an external CDN. - * @param type The type of the resource to be loaded. - * @param name The name of the resource to be loaded. - * @param defaultUri The URI from which the framework would fetch the resource by default. The URI may be relative or absolute. - * @param integrity The integrity string representing the expected content in the response. - * @returns A URI string or a Response promise to override the loading process, or null/undefined to allow the default loading behavior. - */ - loadBootResource(type: WebAssemblyBootResourceType, name: string, defaultUri: string, integrity: string): string | Promise | null | undefined; - - /** - * Override built-in environment setting on start. - */ - environment?: string; - - /** - * Gets the application culture. This is a name specified in the BCP 47 format. See https://tools.ietf.org/html/bcp47 - */ - applicationCulture?: string; -} - // This type doesn't have to align with anything in BootConfig. // Instead, this represents the public API through which certain aspects // of boot resource loading can be customized. From 3df841c64a7538a0f1d08d0d5f0fe83b8a372fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 25 Jul 2023 22:32:02 +0200 Subject: [PATCH 09/65] Remove getApplicationEnvironment --- src/mono/wasm/runtime/dotnet.d.ts | 1 - .../runtime/loader/blazor/_Integration.ts | 1 - src/mono/wasm/runtime/loader/polyfills.ts | 1 - src/mono/wasm/runtime/types/index.ts | 1 - src/mono/wasm/runtime/types/internal.ts | 1 - src/mono/wasm/test-main.js | 22 +++++++++---------- 6 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 59e2519b3ac55b..0f8ca1433b1eb4 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -330,7 +330,6 @@ type DotnetModuleConfig = { onConfigLoaded?: (config: MonoConfig) => void | Promise; onDotnetReady?: () => void | Promise; onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; - getApplicationEnvironment?: (bootConfigResponse: Response) => string | null; imports?: any; exports?: string[]; downloadResource?: (request: ResourceRequest) => LoadingResource | undefined; diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index abdf2699ee3fdb..f0070beca4e820 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -44,7 +44,6 @@ function readBootConfigResponseHeaders(bootConfigResponse: Response) { const config = loaderHelpers.config; config.applicationEnvironment = config.applicationEnvironment - || (loaderHelpers.getApplicationEnvironment && loaderHelpers.getApplicationEnvironment(bootConfigResponse)) || bootConfigResponse.headers.get("Blazor-Environment") || bootConfigResponse.headers.get("DotNet-Environment") || "Production"; diff --git a/src/mono/wasm/runtime/loader/polyfills.ts b/src/mono/wasm/runtime/loader/polyfills.ts index f9aae41f469a56..8f3d1e050f78c8 100644 --- a/src/mono/wasm/runtime/loader/polyfills.ts +++ b/src/mono/wasm/runtime/loader/polyfills.ts @@ -54,7 +54,6 @@ export async function detect_features_and_polyfill(module: DotnetModuleInternal) loaderHelpers.out = console.log; // eslint-disable-next-line no-console loaderHelpers.err = console.error; - loaderHelpers.getApplicationEnvironment = module.getApplicationEnvironment; if (ENVIRONMENT_IS_WEB && globalThis.navigator) { const navigator: any = globalThis.navigator; diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index b33f22e22af449..e898ec354f8e9e 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -269,7 +269,6 @@ export type DotnetModuleConfig = { onConfigLoaded?: (config: MonoConfig) => void | Promise; onDotnetReady?: () => void | Promise; onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; - getApplicationEnvironment?: (bootConfigResponse: Response) => string | null; imports?: any; exports?: string[]; diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index fa291f4bf1161c..bd7fa7ebc84e34 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -141,7 +141,6 @@ export type LoaderHelpers = { downloadResource?: (request: ResourceRequest) => LoadingResource | undefined out(message: string): void; err(message: string): void; - getApplicationEnvironment?: (bootConfigResponse: Response) => string | null; hasDebuggingEnabled(config: MonoConfig): boolean, loadResources(resources: ResourceList, url: (name: string) => string, resourceType: WebAssemblyBootResourceType): LoadingResource[], diff --git a/src/mono/wasm/test-main.js b/src/mono/wasm/test-main.js index 2e729760f6de51..004697c54ddf02 100644 --- a/src/mono/wasm/test-main.js +++ b/src/mono/wasm/test-main.js @@ -280,9 +280,9 @@ function configureRuntime(dotnet, runArgs) { } } if (is_browser) { - if (runArgs.memorySnapshot) { - dotnet.withStartupMemoryCache(true); - } + // if (runArgs.memorySnapshot) { + // dotnet.withStartupMemoryCache(true); + // } dotnet.withEnvironmentVariable("IsWebSocketSupported", "true"); } if (runArgs.runtimeArgs.length > 0) { @@ -330,13 +330,13 @@ async function run() { const runArgs = await getArgs(); console.log("Application arguments: " + runArgs.applicationArguments.join(' ')); - if (is_browser && runArgs.memorySnapshot) { - const dryOk = await dry_run(runArgs); - if (!dryOk) { - mono_exit(1, "Failed during dry run"); - return; - } - } + // if (is_browser && runArgs.memorySnapshot) { + // const dryOk = await dry_run(runArgs); + // if (!dryOk) { + // mono_exit(1, "Failed during dry run"); + // return; + // } + // } // this is subsequent run with the actual tests. It will use whatever was cached in the previous run. // This way, we are testing that the cached version works. @@ -389,7 +389,7 @@ async function run() { const app_args = runArgs.applicationArguments.slice(2); const result = await App.runtime.runMain(main_assembly_name, app_args); console.log(`test-main.js exiting ${app_args.length > 1 ? main_assembly_name + " " + app_args[0] : main_assembly_name} with result ${result}`); - mono_exit(result); + // mono_exit(result); } catch (error) { if (error.name != "ExitStatus") { mono_exit(1, error); From 6e0e5586862c6d5a062893321163a026bd2362be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 25 Jul 2023 22:42:48 +0200 Subject: [PATCH 10/65] Use fetch_like for boot config with loadBootResource --- .../wasm/runtime/loader/blazor/_Integration.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index f0070beca4e820..ec618cf10dc932 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -12,15 +12,14 @@ import { appendUniqueQuery } from "../assets"; import { hasDebuggingEnabled } from "../config"; export async function loadBootConfig(module: DotnetModuleInternal) { - const defaultBootJsonLocation = "_framework/blazor.boot.json"; const loaderResponse = loaderHelpers.loadBootResource !== undefined ? - loaderHelpers.loadBootResource("manifest", "blazor.boot.json", defaultBootJsonLocation, "") : - defaultLoadBlazorBootJson(defaultBootJsonLocation); + loaderHelpers.loadBootResource("manifest", "blazor.boot.json", module.configSrc!, "") : + defaultLoadBlazorBootJson(module.configSrc!); let bootConfigResponse: Response; if (!loaderResponse) { - bootConfigResponse = await defaultLoadBlazorBootJson(defaultBootJsonLocation); + bootConfigResponse = await defaultLoadBlazorBootJson(module.configSrc!); } else if (typeof loaderResponse === "string") { bootConfigResponse = await defaultLoadBlazorBootJson(loaderResponse); } else { @@ -32,7 +31,7 @@ export async function loadBootConfig(module: DotnetModuleInternal) { await initializeBootConfig(bootConfigResponse, bootConfig, module); function defaultLoadBlazorBootJson(url: string): Promise { - return fetch(url, { + return loaderHelpers.fetch_like(url, { method: "GET", credentials: "include", cache: "no-cache", @@ -43,10 +42,9 @@ export async function loadBootConfig(module: DotnetModuleInternal) { function readBootConfigResponseHeaders(bootConfigResponse: Response) { const config = loaderHelpers.config; - config.applicationEnvironment = config.applicationEnvironment - || bootConfigResponse.headers.get("Blazor-Environment") - || bootConfigResponse.headers.get("DotNet-Environment") - || "Production"; + if (!config.applicationEnvironment) { + config.applicationEnvironment = bootConfigResponse.headers.get("Blazor-Environment") || bootConfigResponse.headers.get("DotNet-Environment") || "Production"; + } if (!config.environmentVariables) config.environmentVariables = {}; From 7baa70defff60be1d77292fca610ed67a0ec413b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 25 Jul 2023 22:45:41 +0200 Subject: [PATCH 11/65] Throw error when boot config doesn't contain resources --- src/mono/wasm/runtime/loader/config.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index f4817852527683..f2cc6d2357efa1 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -92,11 +92,7 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi await initializeBootConfig(configResponse, loadedAnyConfig, module); deep_merge_config(loaderHelpers.config, loadedAnyConfig); } else { - // Otherwise we found mono config schema - const loadedConfig = loadedAnyConfig as MonoConfigInternal; - if (loadedConfig.environmentVariables && typeof (loadedConfig.environmentVariables) !== "object") - throw new Error("Expected config.environmentVariables to be unset or a dictionary-style object"); - deep_merge_config(loaderHelpers.config, loadedConfig); + throw new Error("Loaded boot config has invalid schema"); } } From a8f8d8ca7a582dd31b34a1b305caf05e00977c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 25 Jul 2023 22:49:52 +0200 Subject: [PATCH 12/65] Always use loadBootConfig --- .../runtime/loader/blazor/_Integration.ts | 16 ++++++++------- src/mono/wasm/runtime/loader/config.ts | 20 ++----------------- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index ec618cf10dc932..944fcd362ff14c 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -12,14 +12,16 @@ import { appendUniqueQuery } from "../assets"; import { hasDebuggingEnabled } from "../config"; export async function loadBootConfig(module: DotnetModuleInternal) { + const defaultConfigSrc = loaderHelpers.locateFile(module.configSrc!); + const loaderResponse = loaderHelpers.loadBootResource !== undefined ? - loaderHelpers.loadBootResource("manifest", "blazor.boot.json", module.configSrc!, "") : - defaultLoadBlazorBootJson(module.configSrc!); + loaderHelpers.loadBootResource("manifest", "blazor.boot.json", defaultConfigSrc, "") : + defaultLoadBlazorBootJson(defaultConfigSrc); let bootConfigResponse: Response; if (!loaderResponse) { - bootConfigResponse = await defaultLoadBlazorBootJson(module.configSrc!); + bootConfigResponse = await defaultLoadBlazorBootJson(defaultConfigSrc); } else if (typeof loaderResponse === "string") { bootConfigResponse = await defaultLoadBlazorBootJson(loaderResponse); } else { @@ -62,10 +64,10 @@ function readBootConfigResponseHeaders(bootConfigResponse: Response) { } } -export async function initializeBootConfig(bootConfigResponse: Response, bootConfig: BootJsonData, module: DotnetModuleInternal) { +async function initializeBootConfig(bootConfigResponse: Response, bootConfig: BootJsonData, module: DotnetModuleInternal) { readBootConfigResponseHeaders(bootConfigResponse); - await initCacheToUseIfEnabled(); mapBootConfigToMonoConfig(bootConfig); + await initCacheToUseIfEnabled(); hookDownloadResource(module); } @@ -91,7 +93,7 @@ const monoToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType | u "dotnetwasm": "dotnetwasm", }; -export function hookDownloadResource(module: DotnetModuleInternal) { +function hookDownloadResource(module: DotnetModuleInternal) { // it would not `loadResource` on types for which there is no typesMap mapping const downloadResource = (asset: AssetEntry): LoadingResource | undefined => { // GOTCHA: the mapping to blazor asset type may not cover all mono owned asset types in the future in which case: @@ -117,7 +119,7 @@ export function hookDownloadResource(module: DotnetModuleInternal) { loaderHelpers.downloadResource = downloadResource; // polyfills were already assigned } -export function mapBootConfigToMonoConfig(bootConfig: BootJsonData) { +function mapBootConfigToMonoConfig(bootConfig: BootJsonData) { const config = loaderHelpers.config; const resources = bootConfig.resources; diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index f2cc6d2357efa1..2ddeb57c803fd9 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -5,7 +5,7 @@ import BuildConfiguration from "consts:configuration"; import type { DotnetModuleInternal, MonoConfigInternal } from "../types/internal"; import type { DotnetModuleConfig } from "../types"; import { ENVIRONMENT_IS_WEB, exportedRuntimeAPI, loaderHelpers, runtimeHelpers } from "./globals"; -import { initializeBootConfig, loadBootConfig } from "./blazor/_Integration"; +import { loadBootConfig } from "./blazor/_Integration"; import { mono_log_error, mono_log_debug } from "./logging"; import { invokeLibraryInitializers } from "./libraryInitializers"; import { mono_exit } from "./exit"; @@ -78,23 +78,7 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi } mono_log_debug("mono_wasm_load_config"); try { - if (loaderHelpers.loadBootResource) { - // If we have custom loadBootResource - await loadBootConfig(module); - } else { - // Otherwise load using fetch_like - const resolveSrc = loaderHelpers.locateFile(configFilePath); - const configResponse = await loaderHelpers.fetch_like(resolveSrc); - const loadedAnyConfig: any = (await configResponse.json()) || {}; - if (loadedAnyConfig.resources) { - // If we found boot config schema - normalizeConfig(); - await initializeBootConfig(configResponse, loadedAnyConfig, module); - deep_merge_config(loaderHelpers.config, loadedAnyConfig); - } else { - throw new Error("Loaded boot config has invalid schema"); - } - } + await loadBootConfig(module); normalizeConfig(); From ee3418a1ee000ab2e76628a51cf93d4806ab5c8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 25 Jul 2023 22:52:43 +0200 Subject: [PATCH 13/65] Init resource cache after config is ready --- src/mono/wasm/runtime/loader/blazor/_Integration.ts | 3 +-- src/mono/wasm/runtime/loader/run.ts | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 944fcd362ff14c..3db8a2c2e4e495 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -6,7 +6,7 @@ import { GlobalizationMode, type AssetBehaviours, type AssetEntry, type LoadingR import type { BootJsonData } from "../../types/blazor"; import { ENVIRONMENT_IS_WEB, loaderHelpers } from "../globals"; -import { initCacheToUseIfEnabled, loadResource } from "../resourceLoader"; +import { loadResource } from "../resourceLoader"; import { ICUDataMode } from "../../types/blazor"; import { appendUniqueQuery } from "../assets"; import { hasDebuggingEnabled } from "../config"; @@ -67,7 +67,6 @@ function readBootConfigResponseHeaders(bootConfigResponse: Response) { async function initializeBootConfig(bootConfigResponse: Response, bootConfig: BootJsonData, module: DotnetModuleInternal) { readBootConfigResponseHeaders(bootConfigResponse); mapBootConfigToMonoConfig(bootConfig); - await initCacheToUseIfEnabled(); hookDownloadResource(module); } diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts index e347fdd230c8c4..1eaeb5f112f101 100644 --- a/src/mono/wasm/runtime/loader/run.ts +++ b/src/mono/wasm/runtime/loader/run.ts @@ -16,6 +16,7 @@ import { runtimeHelpers, loaderHelpers } from "./globals"; import { init_globalization } from "./icu"; import { setupPreloadChannelToMainThread } from "./worker"; import { invokeLibraryInitializers } from "./libraryInitializers"; +import { initCacheToUseIfEnabled } from "./resourceLoader"; const module = globalObjectsRoot.module; const monoConfig = module.config as MonoConfigInternal; @@ -475,6 +476,8 @@ async function createEmscriptenMain(): Promise { const promises = importModules(); + await initCacheToUseIfEnabled(); + const wasmModuleAsset = resolve_asset_path("dotnetwasm"); start_asset_download(wasmModuleAsset).then(asset => { loaderHelpers.wasmDownloadPromise.promise_control.resolve(asset); From f9125632f4f6a61c48245dada8a3861bef3dbb06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 25 Jul 2023 22:53:10 +0200 Subject: [PATCH 14/65] Revert test-main.js --- src/mono/wasm/test-main.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/mono/wasm/test-main.js b/src/mono/wasm/test-main.js index 004697c54ddf02..2e729760f6de51 100644 --- a/src/mono/wasm/test-main.js +++ b/src/mono/wasm/test-main.js @@ -280,9 +280,9 @@ function configureRuntime(dotnet, runArgs) { } } if (is_browser) { - // if (runArgs.memorySnapshot) { - // dotnet.withStartupMemoryCache(true); - // } + if (runArgs.memorySnapshot) { + dotnet.withStartupMemoryCache(true); + } dotnet.withEnvironmentVariable("IsWebSocketSupported", "true"); } if (runArgs.runtimeArgs.length > 0) { @@ -330,13 +330,13 @@ async function run() { const runArgs = await getArgs(); console.log("Application arguments: " + runArgs.applicationArguments.join(' ')); - // if (is_browser && runArgs.memorySnapshot) { - // const dryOk = await dry_run(runArgs); - // if (!dryOk) { - // mono_exit(1, "Failed during dry run"); - // return; - // } - // } + if (is_browser && runArgs.memorySnapshot) { + const dryOk = await dry_run(runArgs); + if (!dryOk) { + mono_exit(1, "Failed during dry run"); + return; + } + } // this is subsequent run with the actual tests. It will use whatever was cached in the previous run. // This way, we are testing that the cached version works. @@ -389,7 +389,7 @@ async function run() { const app_args = runArgs.applicationArguments.slice(2); const result = await App.runtime.runMain(main_assembly_name, app_args); console.log(`test-main.js exiting ${app_args.length > 1 ? main_assembly_name + " " + app_args[0] : main_assembly_name} with result ${result}`); - // mono_exit(result); + mono_exit(result); } catch (error) { if (error.name != "ExitStatus") { mono_exit(1, error); From 726f4c88f6b60588d43bda75f6b97a3999d45379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 26 Jul 2023 11:35:45 +0200 Subject: [PATCH 15/65] Remove duplicated modifiableAssemblies and aspnetCoreBrowserTools --- src/mono/wasm/runtime/loader/blazor/_Integration.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 3db8a2c2e4e495..72856548c3058a 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -273,16 +273,6 @@ function mapBootConfigToMonoConfig(bootConfig: BootJsonData) { config.globalizationMode = GlobalizationMode.Invariant; } - if (bootConfig.modifiableAssemblies) { - // Configure the app to enable hot reload in Development. - environmentVariables["DOTNET_MODIFIABLE_ASSEMBLIES"] = bootConfig.modifiableAssemblies; - } - - if (bootConfig.aspnetCoreBrowserTools) { - // See https://github.com/dotnet/aspnetcore/issues/37357#issuecomment-941237000 - environmentVariables["__ASPNETCORE_BROWSER_TOOLS"] = bootConfig.aspnetCoreBrowserTools; - } - if (config.applicationCulture) { // If a culture is specified via start options use that to initialize the Emscripten \ .NET culture. environmentVariables["LANG"] = `${config.applicationCulture}.UTF-8`; From 97b8eeaa27bf2dbdd5bafc84021a847be935e95e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 26 Jul 2023 11:45:39 +0200 Subject: [PATCH 16/65] Don't read runtimeAssets --- src/mono/wasm/runtime/loader/blazor/_Integration.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 72856548c3058a..aacb744f425e4e 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -172,13 +172,6 @@ function mapBootConfigToMonoConfig(bootConfig: BootJsonData) { config.runtimeOptions = [...(config.runtimeOptions || []), ...(bootConfig.runtimeOptions || [])]; } - // any runtime owned assets, with proper behavior already set - for (const name in resources.runtimeAssets) { - const asset = resources.runtimeAssets[name] as AssetEntry; - asset.name = name; - asset.resolvedUrl = appendUniqueQuery(loaderHelpers.locateFile(name), asset.behavior); - assets.push(asset); - } for (const name in resources.assembly) { const asset: AssetEntry = { name, @@ -216,8 +209,6 @@ function mapBootConfigToMonoConfig(bootConfig: BootJsonData) { hasIcuData = true; } else if (behavior === "js-module-dotnet") { continue; - } else if (behavior === "dotnetwasm") { - continue; } const resolvedUrl = appendUniqueQuery(loaderHelpers.locateFile(name), behavior); From 6c4bb337e82e711fde5eaa693e41b1f26892dce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 26 Jul 2023 12:07:44 +0200 Subject: [PATCH 17/65] Replace icuDataMode with globalizationMode --- .../wasm/runtime/loader/blazor/_Integration.ts | 9 ++++----- src/mono/wasm/runtime/types/blazor.ts | 3 +++ src/mono/wasm/runtime/types/index.ts | 2 +- .../BootJsonData.cs | 17 +++++++++++++++-- .../GenerateWasmBootJson.cs | 14 +++++++------- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 14 +++++++------- 6 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index aacb744f425e4e..333e5831759c60 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -7,7 +7,6 @@ import type { BootJsonData } from "../../types/blazor"; import { ENVIRONMENT_IS_WEB, loaderHelpers } from "../globals"; import { loadResource } from "../resourceLoader"; -import { ICUDataMode } from "../../types/blazor"; import { appendUniqueQuery } from "../assets"; import { hasDebuggingEnabled } from "../config"; @@ -199,7 +198,7 @@ function mapBootConfigToMonoConfig(bootConfig: BootJsonData) { const behavior = behaviorByName(name) as any; let loadRemote = false; if (behavior === "icu") { - if (bootConfig.icuDataMode === ICUDataMode.Invariant) { + if (bootConfig.globalizationMode === GlobalizationMode.Invariant) { continue; } if (name !== icuDataResourceName) { @@ -279,7 +278,7 @@ function fileName(name: string) { } function getICUResourceName(bootConfig: BootJsonData, moduleConfig: MonoConfigInternal, culture: string | undefined): string { - if (bootConfig.icuDataMode === ICUDataMode.Custom) { + if (bootConfig.globalizationMode === GlobalizationMode.Custom) { const icuFiles = Object .keys(bootConfig.resources.runtime) .filter(n => n.startsWith("icudt") && n.endsWith(".dat")); @@ -290,13 +289,13 @@ function getICUResourceName(bootConfig: BootJsonData, moduleConfig: MonoConfigIn } } - if (bootConfig.icuDataMode === ICUDataMode.Hybrid) { + if (bootConfig.globalizationMode === GlobalizationMode.Hybrid) { moduleConfig.globalizationMode = GlobalizationMode.Hybrid; const reducedICUResourceName = "icudt_hybrid.dat"; return reducedICUResourceName; } - if (!culture || bootConfig.icuDataMode === ICUDataMode.All) { + if (!culture || bootConfig.globalizationMode === GlobalizationMode.All) { moduleConfig.globalizationMode = GlobalizationMode.All; const combinedICUResourceName = "icudt.dat"; return combinedICUResourceName; diff --git a/src/mono/wasm/runtime/types/blazor.ts b/src/mono/wasm/runtime/types/blazor.ts index 205bac41d7e190..b2c19817ee5b99 100644 --- a/src/mono/wasm/runtime/types/blazor.ts +++ b/src/mono/wasm/runtime/types/blazor.ts @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import { GlobalizationMode } from "."; + // Keep in sync with Microsoft.NET.Sdk.WebAssembly.BootJsonData from the WasmSDK export interface BootJsonData { readonly entryAssembly: string; @@ -12,6 +14,7 @@ export interface BootJsonData { readonly cacheBootResources: boolean; readonly config: string[]; readonly icuDataMode: ICUDataMode; + readonly globalizationMode: GlobalizationMode; readonly startupMemoryCache: boolean | undefined; readonly runtimeOptions: string[] | undefined; readonly environmentVariables?: { [name: string]: string }; diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index e898ec354f8e9e..bf3e5308fe1fd2 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -242,7 +242,7 @@ export const enum GlobalizationMode { /** * Load sharded ICU data. */ - Sharded = "sharded", // + Sharded = "sharded", /** * Load all ICU data. */ diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs index 81943d6ce83b00..580fc37ef8546a 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -59,7 +59,19 @@ public class BootJsonData /// /// Gets or sets the that determines how icu files are loaded. /// - public ICUDataMode icuDataMode { get; set; } + /// + /// Deprecated since .NET 8. Use instead. + /// + public GlobalizationMode icuDataMode { get; set; } + + /// + /// Gets or sets the that determines how icu files are loaded. + /// + public string globalizationMode + { + get => icuDataMode.ToString().ToLowerInvariant(); + set { } + } /// /// Gets or sets a value that determines if the caching startup memory is enabled. @@ -171,9 +183,10 @@ public class TypedLibraryStartupModules public ResourceHashesByNameDictionary onRuntimeReady { get; set; } } -public enum ICUDataMode : int +public enum GlobalizationMode : int { // Note that the numeric values are serialized and used in JS code, so don't change them without also updating the JS code + // Note that names are serialized as string and used in JS code /// /// Load optimized icu data file based on the user's locale diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index a423251b4cb584..e4b4cf44985faa 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -96,7 +96,7 @@ public void WriteBootJson(Stream output, string entryAssemblyName) linkerEnabled = LinkerEnabled, resources = new ResourcesData(), config = new List(), - icuDataMode = GetIcuDataMode(), + icuDataMode = GetGlobalizationMode(), startupMemoryCache = ParseOptionalBool(StartupMemoryCache), }; @@ -349,18 +349,18 @@ void AddResourceToList(ITaskItem resource, ResourceHashesByNameDictionary resour } } - private ICUDataMode GetIcuDataMode() + private GlobalizationMode GetGlobalizationMode() { if (string.Equals(InvariantGlobalization, "true", StringComparison.OrdinalIgnoreCase)) - return ICUDataMode.Invariant; + return GlobalizationMode.Invariant; else if (IsHybridGlobalization) - return ICUDataMode.Hybrid; + return GlobalizationMode.Hybrid; else if (LoadAllICUData) - return ICUDataMode.All; + return GlobalizationMode.All; else if (LoadCustomIcuData) - return ICUDataMode.Custom; + return GlobalizationMode.Custom; - return ICUDataMode.Sharded; + return GlobalizationMode.Sharded; } private static bool? ParseOptionalBool(string value) diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 12e7cb3a581c78..e0e71c0e5a727f 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -58,26 +58,26 @@ protected override bool ValidateArguments() return true; } - private ICUDataMode GetICUDataMode() + private GlobalizationMode GetGlobalizationMode() { // Invariant has always precedence if (InvariantGlobalization) - return ICUDataMode.Invariant; + return GlobalizationMode.Invariant; // If user provided a path to a custom ICU data file, use it if (!string.IsNullOrEmpty(WasmIcuDataFileName)) - return ICUDataMode.Custom; + return GlobalizationMode.Custom; // Hybrid mode if (HybridGlobalization) - return ICUDataMode.Hybrid; + return GlobalizationMode.Hybrid; // If user requested to include full ICU data, use it if (WasmIncludeFullIcuData) - return ICUDataMode.All; + return GlobalizationMode.All; // Otherwise, use sharded mode - return ICUDataMode.Sharded; + return GlobalizationMode.Sharded; } protected override bool ExecuteInternal() @@ -97,7 +97,7 @@ protected override bool ExecuteInternal() { config = new(), entryAssembly = MainAssemblyName, - icuDataMode = GetICUDataMode() + icuDataMode = GetGlobalizationMode() }; // Create app From c496c0892f43ddf38a5cbeea9319495d9e64b8b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 26 Jul 2023 12:29:59 +0200 Subject: [PATCH 18/65] Replace assetsHash with resources.hash and compute has in WasmSDK --- src/mono/wasm/runtime/dotnet.d.ts | 4 --- .../runtime/loader/blazor/_Integration.ts | 7 ++-- src/mono/wasm/runtime/snapshot.ts | 15 +-------- src/mono/wasm/runtime/types/index.ts | 4 --- .../GenerateWasmBootJson.cs | 33 +++++++++++++++++++ 5 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 0f8ca1433b1eb4..cdc11e22ccfdcb 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -151,10 +151,6 @@ type MonoConfig = { * If true, the snapshot of runtime's memory will be stored in the browser and used for faster startup next time. Default is false. */ startupMemoryCache?: boolean; - /** - * hash of assets - */ - assetsHash?: string; /** * application environment */ diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 333e5831759c60..1a61b4d3f0ef3f 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -5,7 +5,7 @@ import type { DotnetModuleInternal, MonoConfigInternal } from "../../types/inter import { GlobalizationMode, type AssetBehaviours, type AssetEntry, type LoadingResource, type WebAssemblyBootResourceType } from "../../types"; import type { BootJsonData } from "../../types/blazor"; -import { ENVIRONMENT_IS_WEB, loaderHelpers } from "../globals"; +import { ENVIRONMENT_IS_WEB, loaderHelpers, mono_assert } from "../globals"; import { loadResource } from "../resourceLoader"; import { appendUniqueQuery } from "../assets"; import { hasDebuggingEnabled } from "../config"; @@ -132,7 +132,6 @@ function mapBootConfigToMonoConfig(bootConfig: BootJsonData) { config.cacheBootResources = bootConfig.cacheBootResources; config.linkerEnabled = bootConfig.linkerEnabled; config.remoteSources = (resources as any).remoteSources; - config.assetsHash = bootConfig.resources.hash; config.assets = assets; config.extensions = bootConfig.extensions; config.resources = { @@ -277,7 +276,9 @@ function fileName(name: string) { return name.substring(lastIndexOfSlash); } -function getICUResourceName(bootConfig: BootJsonData, moduleConfig: MonoConfigInternal, culture: string | undefined): string { +function getICUResourceName(bootConfig: MonoConfigInternal, moduleConfig: MonoConfigInternal, culture: string | undefined): string { + mono_assert(bootConfig.resources?.runtime, "Boot config does not contain runtime resources"); + if (bootConfig.globalizationMode === GlobalizationMode.Custom) { const icuFiles = Object .keys(bootConfig.resources.runtime) diff --git a/src/mono/wasm/runtime/snapshot.ts b/src/mono/wasm/runtime/snapshot.ts index 6c206b92489466..7594f4aff3f1ff 100644 --- a/src/mono/wasm/runtime/snapshot.ts +++ b/src/mono/wasm/runtime/snapshot.ts @@ -142,22 +142,9 @@ async function getCacheKey(): Promise { return null; } const inputs = Object.assign({}, runtimeHelpers.config) as any; - // above already has env variables, runtime options, etc - - if (!inputs.assetsHash) { - // this is fallback for blazor which does not have assetsHash yet - inputs.assetsHash = []; - for (const asset of inputs.assets) { - if (!asset.hash) { - // if we don't have hash, we can't use the cache - return null; - } - inputs.assetsHash.push(asset.hash); - } - } - // otherwise config.assetsHash already has hashes for all the assets (DLLs, ICU, .wasms, etc). // Now we remove assets collection from the hash. + inputs.resourcesHash = inputs.resources.hash; delete inputs.assets; delete inputs.resources; // some things are calculated at runtime, so we need to add them to the hash diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index bf3e5308fe1fd2..dacaaf8e55504f 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -85,10 +85,6 @@ export type MonoConfig = { * If true, the snapshot of runtime's memory will be stored in the browser and used for faster startup next time. Default is false. */ startupMemoryCache?: boolean, - /** - * hash of assets - */ - assetsHash?: string, /** * application environment */ diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index e4b4cf44985faa..87cbd81634cb31 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -329,6 +329,8 @@ public void WriteBootJson(Stream output, string entryAssemblyName) } } + ComputeResourcesHash(result); + var serializer = new DataContractJsonSerializer(typeof(BootJsonData), new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true, @@ -349,6 +351,37 @@ void AddResourceToList(ITaskItem resource, ResourceHashesByNameDictionary resour } } + private static void ComputeResourcesHash(BootJsonData bootConfig) + { + var sb = new StringBuilder(); + + static void AddDictionary(StringBuilder sb, Dictionary res) + { + foreach (var asset in res) + sb.Append(asset.Value); + } + + AddDictionary(sb, bootConfig.resources.assembly); + AddDictionary(sb, bootConfig.resources.runtime); + + if (bootConfig.resources.lazyAssembly != null) + AddDictionary(sb, bootConfig.resources.lazyAssembly); + + if (bootConfig.resources.satelliteResources != null) + { + foreach (var culture in bootConfig.resources.satelliteResources) + AddDictionary(sb, culture.Value); + } + + if (bootConfig.resources.vfs != null) + { + foreach (var entry in bootConfig.resources.vfs) + AddDictionary(sb, entry.Value); + } + + bootConfig.resources.hash = Utils.ComputeTextIntegrity(sb.ToString()); + } + private GlobalizationMode GetGlobalizationMode() { if (string.Equals(InvariantGlobalization, "true", StringComparison.OrdinalIgnoreCase)) From cb3188f76fbd6285fe87ffc1f194787d1369729e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 26 Jul 2023 12:44:17 +0200 Subject: [PATCH 19/65] Remove blazor.ts. Add configs to monoConfig. Produce mainAssemblyName in boot config --- src/mono/wasm/runtime/dotnet.d.ts | 4 + .../runtime/loader/blazor/_Integration.ts | 113 +++++++++--------- .../wasm/runtime/loader/resourceLoader.ts | 3 +- src/mono/wasm/runtime/types/blazor.ts | 61 ---------- src/mono/wasm/runtime/types/index.ts | 5 + .../BootJsonData.cs | 9 ++ 6 files changed, 76 insertions(+), 119 deletions(-) delete mode 100644 src/mono/wasm/runtime/types/blazor.ts diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index cdc11e22ccfdcb..a0741ca8097b20 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -163,6 +163,10 @@ type MonoConfig = { * definition of assets to load along with the runtime. */ resources?: ResourceGroups; + /** + * appsettings files to load to VFS + */ + config?: string[]; /** * config extensions declared in MSBuild items @(WasmBootConfigExtension) */ diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 1a61b4d3f0ef3f..d585d98b64951d 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -2,8 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { DotnetModuleInternal, MonoConfigInternal } from "../../types/internal"; -import { GlobalizationMode, type AssetBehaviours, type AssetEntry, type LoadingResource, type WebAssemblyBootResourceType } from "../../types"; -import type { BootJsonData } from "../../types/blazor"; +import { GlobalizationMode, type AssetBehaviours, type AssetEntry, type LoadingResource, type WebAssemblyBootResourceType, MonoConfig } from "../../types"; import { ENVIRONMENT_IS_WEB, loaderHelpers, mono_assert } from "../globals"; import { loadResource } from "../resourceLoader"; @@ -15,23 +14,25 @@ export async function loadBootConfig(module: DotnetModuleInternal) { const loaderResponse = loaderHelpers.loadBootResource !== undefined ? loaderHelpers.loadBootResource("manifest", "blazor.boot.json", defaultConfigSrc, "") : - defaultLoadBlazorBootJson(defaultConfigSrc); + defaultLoadBootConfig(defaultConfigSrc); - let bootConfigResponse: Response; + let loadConfigResponse: Response; if (!loaderResponse) { - bootConfigResponse = await defaultLoadBlazorBootJson(defaultConfigSrc); + loadConfigResponse = await defaultLoadBootConfig(defaultConfigSrc); } else if (typeof loaderResponse === "string") { - bootConfigResponse = await defaultLoadBlazorBootJson(loaderResponse); + loadConfigResponse = await defaultLoadBootConfig(loaderResponse); } else { - bootConfigResponse = await loaderResponse; + loadConfigResponse = await loaderResponse; } - const bootConfig: BootJsonData = await bootConfigResponse.json(); + const loadedConfig: MonoConfig = await loadConfigResponse.json(); - await initializeBootConfig(bootConfigResponse, bootConfig, module); + readBootConfigResponseHeaders(loadConfigResponse); + mapBootConfigToMonoConfig(loadedConfig); + hookDownloadResource(module); - function defaultLoadBlazorBootJson(url: string): Promise { + function defaultLoadBootConfig(url: string): Promise { return loaderHelpers.fetch_like(url, { method: "GET", credentials: "include", @@ -40,35 +41,29 @@ export async function loadBootConfig(module: DotnetModuleInternal) { } } -function readBootConfigResponseHeaders(bootConfigResponse: Response) { +function readBootConfigResponseHeaders(loadConfigResponse: Response) { const config = loaderHelpers.config; if (!config.applicationEnvironment) { - config.applicationEnvironment = bootConfigResponse.headers.get("Blazor-Environment") || bootConfigResponse.headers.get("DotNet-Environment") || "Production"; + config.applicationEnvironment = loadConfigResponse.headers.get("Blazor-Environment") || loadConfigResponse.headers.get("DotNet-Environment") || "Production"; } if (!config.environmentVariables) config.environmentVariables = {}; - const modifiableAssemblies = bootConfigResponse.headers.get("DOTNET-MODIFIABLE-ASSEMBLIES"); + const modifiableAssemblies = loadConfigResponse.headers.get("DOTNET-MODIFIABLE-ASSEMBLIES"); if (modifiableAssemblies) { // Configure the app to enable hot reload in Development. config.environmentVariables["DOTNET_MODIFIABLE_ASSEMBLIES"] = modifiableAssemblies; } - const aspnetCoreBrowserTools = bootConfigResponse.headers.get("ASPNETCORE-BROWSER-TOOLS"); + const aspnetCoreBrowserTools = loadConfigResponse.headers.get("ASPNETCORE-BROWSER-TOOLS"); if (aspnetCoreBrowserTools) { // See https://github.com/dotnet/aspnetcore/issues/37357#issuecomment-941237000 config.environmentVariables["__ASPNETCORE_BROWSER_TOOLS"] = aspnetCoreBrowserTools; } } -async function initializeBootConfig(bootConfigResponse: Response, bootConfig: BootJsonData, module: DotnetModuleInternal) { - readBootConfigResponseHeaders(bootConfigResponse); - mapBootConfigToMonoConfig(bootConfig); - hookDownloadResource(module); -} - let resourcesLoaded = 0; const totalResources = new Set(); @@ -117,23 +112,25 @@ function hookDownloadResource(module: DotnetModuleInternal) { loaderHelpers.downloadResource = downloadResource; // polyfills were already assigned } -function mapBootConfigToMonoConfig(bootConfig: BootJsonData) { +function mapBootConfigToMonoConfig(loadedConfig: MonoConfigInternal) { + mono_assert(loadedConfig.resources, "Loaded config does not contain resources"); + const config = loaderHelpers.config; - const resources = bootConfig.resources; + const resources = loadedConfig.resources; const assets: AssetEntry[] = []; const environmentVariables: any = { // From boot config - ...(bootConfig.environmentVariables || {}), + ...(loadedConfig.environmentVariables || {}), // From JavaScript ...(config.environmentVariables || {}) }; - config.cacheBootResources = bootConfig.cacheBootResources; - config.linkerEnabled = bootConfig.linkerEnabled; + config.cacheBootResources = loadedConfig.cacheBootResources; + config.linkerEnabled = loadedConfig.linkerEnabled; config.remoteSources = (resources as any).remoteSources; config.assets = assets; - config.extensions = bootConfig.extensions; + config.extensions = loadedConfig.extensions; config.resources = { extensions: resources.extensions }; @@ -143,11 +140,11 @@ function mapBootConfigToMonoConfig(bootConfig: BootJsonData) { // - Build (release) => debugBuild=true & debugLevel=0 => 0 // - Publish (debug) => debugBuild=false & debugLevel=-1 => 0 // - Publish (release) => debugBuild=false & debugLevel=0 => 0 - config.debugLevel = hasDebuggingEnabled(config) ? bootConfig.debugLevel : 0; - config.mainAssemblyName = bootConfig.entryAssembly; + config.debugLevel = hasDebuggingEnabled(config) ? loadedConfig.debugLevel : 0; + config.mainAssemblyName = loadedConfig.mainAssemblyName; - const anyBootConfig = (bootConfig as any); - for (const key in bootConfig) { + const anyBootConfig = (loadedConfig as any); + for (const key in loadedConfig) { if (Object.prototype.hasOwnProperty.call(anyBootConfig, key)) { if (anyBootConfig[key] === null) { delete anyBootConfig[key]; @@ -157,17 +154,17 @@ function mapBootConfigToMonoConfig(bootConfig: BootJsonData) { // FIXME this mix of both formats is ugly temporary hack Object.assign(config, { - ...bootConfig, + ...loadedConfig, }); config.environmentVariables = environmentVariables; - if (bootConfig.startupMemoryCache !== undefined) { - config.startupMemoryCache = bootConfig.startupMemoryCache; + if (loadedConfig.startupMemoryCache !== undefined) { + config.startupMemoryCache = loadedConfig.startupMemoryCache; } - if (bootConfig.runtimeOptions) { - config.runtimeOptions = [...(config.runtimeOptions || []), ...(bootConfig.runtimeOptions || [])]; + if (loadedConfig.runtimeOptions) { + config.runtimeOptions = [...(config.runtimeOptions || []), ...(loadedConfig.runtimeOptions || [])]; } for (const name in resources.assembly) { @@ -191,13 +188,13 @@ function mapBootConfigToMonoConfig(bootConfig: BootJsonData) { } } const applicationCulture = config.applicationCulture || (ENVIRONMENT_IS_WEB ? (navigator.languages && navigator.languages[0]) : Intl.DateTimeFormat().resolvedOptions().locale); - const icuDataResourceName = getICUResourceName(bootConfig, config, applicationCulture); + const icuDataResourceName = getICUResourceName(loadedConfig, config, applicationCulture); let hasIcuData = false; for (const name in resources.runtime) { const behavior = behaviorByName(name) as any; let loadRemote = false; if (behavior === "icu") { - if (bootConfig.globalizationMode === GlobalizationMode.Invariant) { + if (loadedConfig.globalizationMode === GlobalizationMode.Invariant) { continue; } if (name !== icuDataResourceName) { @@ -233,28 +230,32 @@ function mapBootConfigToMonoConfig(bootConfig: BootJsonData) { } } - for (let i = 0; i < bootConfig.config.length; i++) { - const configUrl = bootConfig.config[i]; - const configFileName = fileName(configUrl); - if (configFileName === "appsettings.json" || configFileName === `appsettings.${config.applicationEnvironment}.json`) { - assets.push({ - name: configFileName, - resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(configUrl), "vfs"), - behavior: "vfs", - }); + if (loadedConfig.config) { + for (let i = 0; i < loadedConfig.config.length; i++) { + const configUrl = loadedConfig.config[i]; + const configFileName = fileName(configUrl); + if (configFileName === "appsettings.json" || configFileName === `appsettings.${config.applicationEnvironment}.json`) { + assets.push({ + name: configFileName, + resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(configUrl), "vfs"), + behavior: "vfs", + }); + } } } - for (const virtualPath in resources.vfs) { - for (const name in resources.vfs[virtualPath]) { - const asset: AssetEntry = { - name, - resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), "vfs"), - hash: resources.vfs[virtualPath][name], - behavior: "vfs", - virtualPath - }; - assets.push(asset); + if (resources.vfs) { + for (const virtualPath in resources.vfs) { + for (const name in resources.vfs[virtualPath]) { + const asset: AssetEntry = { + name, + resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), "vfs"), + hash: resources.vfs[virtualPath][name], + behavior: "vfs", + virtualPath + }; + assets.push(asset); + } } } diff --git a/src/mono/wasm/runtime/loader/resourceLoader.ts b/src/mono/wasm/runtime/loader/resourceLoader.ts index 0c382f33774ddf..5faab2445c480a 100644 --- a/src/mono/wasm/runtime/loader/resourceLoader.ts +++ b/src/mono/wasm/runtime/loader/resourceLoader.ts @@ -1,8 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { MonoConfig, WebAssemblyBootResourceType } from "../types"; -import type { ResourceList } from "../types/blazor"; +import type { MonoConfig, ResourceList, WebAssemblyBootResourceType } from "../types"; import { loaderHelpers } from "./globals"; const networkFetchCacheMode = "no-cache"; diff --git a/src/mono/wasm/runtime/types/blazor.ts b/src/mono/wasm/runtime/types/blazor.ts deleted file mode 100644 index b2c19817ee5b99..00000000000000 --- a/src/mono/wasm/runtime/types/blazor.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import { GlobalizationMode } from "."; - -// Keep in sync with Microsoft.NET.Sdk.WebAssembly.BootJsonData from the WasmSDK -export interface BootJsonData { - readonly entryAssembly: string; - readonly resources: ResourceGroups; - /** Gets a value that determines if this boot config was produced from a non-published build (i.e. dotnet build or dotnet run) */ - readonly debugBuild: boolean; - readonly debugLevel: number; - readonly linkerEnabled: boolean; - readonly cacheBootResources: boolean; - readonly config: string[]; - readonly icuDataMode: ICUDataMode; - readonly globalizationMode: GlobalizationMode; - readonly startupMemoryCache: boolean | undefined; - readonly runtimeOptions: string[] | undefined; - readonly environmentVariables?: { [name: string]: string }; - readonly diagnosticTracing?: boolean; - readonly pthreadPoolSize: number; - - // These properties are tacked on, and not found in the boot.json file - modifiableAssemblies: string | null; - aspnetCoreBrowserTools: string | null; - - readonly extensions?: { [name: string]: any }; -} - -export type BootJsonDataExtension = { [extensionName: string]: ResourceList }; - -export interface ResourceGroups { - readonly hash?: string; - readonly assembly: ResourceList; - readonly lazyAssembly: ResourceList; - readonly pdb?: ResourceList; - readonly runtime: ResourceList; - readonly satelliteResources?: { [cultureName: string]: ResourceList }; - readonly libraryInitializers?: ResourceList, - readonly libraryStartupModules?: { onRuntimeConfigLoaded: ResourceList, onRuntimeReady: ResourceList }, - readonly extensions?: BootJsonDataExtension - readonly runtimeAssets: ExtendedResourceList; - readonly vfs?: { [virtualPath: string]: ResourceList }; -} - -export type ResourceList = { [name: string]: string }; -export type ExtendedResourceList = { - [name: string]: { - hash: string, - behavior: string - } -}; - -export enum ICUDataMode { - Sharded = 0, - All = 1, - Invariant = 2, - Custom = 3, - Hybrid = 4 -} diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index dacaaf8e55504f..a71540c7469c80 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -100,6 +100,11 @@ export type MonoConfig = { */ resources?: ResourceGroups; + /** + * appsettings files to load to VFS + */ + config?: string[]; + /** * config extensions declared in MSBuild items @(WasmBootConfigExtension) */ diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs index 580fc37ef8546a..35834846517ef9 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -17,8 +17,17 @@ public class BootJsonData /// /// Gets the name of the assembly with the application entry point /// + /// + /// Deprecated in .NET 8. Use + /// public string entryAssembly { get; set; } + public string mainAssemblyName + { + get => entryAssembly; + set => entryAssembly = value; + } + /// /// Gets the set of resources needed to boot the application. This includes the transitive /// closure of .NET assemblies (including the entrypoint assembly), the dotnet.wasm file, From 86500c5d549035e2826bee24d562e3c8cbcf60aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 26 Jul 2023 13:05:04 +0200 Subject: [PATCH 20/65] Replace manual config merging with deep_merge_config --- src/mono/wasm/runtime/dotnet.d.ts | 1 - .../runtime/loader/blazor/_Integration.ts | 137 ++---------------- src/mono/wasm/runtime/loader/config.ts | 76 +++++++++- src/mono/wasm/runtime/types/index.ts | 1 - 4 files changed, 89 insertions(+), 126 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index a0741ca8097b20..d322c3f3c54647 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -186,7 +186,6 @@ interface ResourceGroups { readonly satelliteResources?: { [cultureName: string]: ResourceList; }; - readonly libraryInitializers?: ResourceList; readonly libraryStartupModules?: { readonly onRuntimeConfigLoaded?: ResourceList; readonly onRuntimeReady?: ResourceList; diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index d585d98b64951d..c5e34bf957a2e2 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -1,68 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { DotnetModuleInternal, MonoConfigInternal } from "../../types/internal"; +import type { DotnetModuleInternal } from "../../types/internal"; import { GlobalizationMode, type AssetBehaviours, type AssetEntry, type LoadingResource, type WebAssemblyBootResourceType, MonoConfig } from "../../types"; import { ENVIRONMENT_IS_WEB, loaderHelpers, mono_assert } from "../globals"; import { loadResource } from "../resourceLoader"; import { appendUniqueQuery } from "../assets"; -import { hasDebuggingEnabled } from "../config"; - -export async function loadBootConfig(module: DotnetModuleInternal) { - const defaultConfigSrc = loaderHelpers.locateFile(module.configSrc!); - - const loaderResponse = loaderHelpers.loadBootResource !== undefined ? - loaderHelpers.loadBootResource("manifest", "blazor.boot.json", defaultConfigSrc, "") : - defaultLoadBootConfig(defaultConfigSrc); - - let loadConfigResponse: Response; - - if (!loaderResponse) { - loadConfigResponse = await defaultLoadBootConfig(defaultConfigSrc); - } else if (typeof loaderResponse === "string") { - loadConfigResponse = await defaultLoadBootConfig(loaderResponse); - } else { - loadConfigResponse = await loaderResponse; - } - - const loadedConfig: MonoConfig = await loadConfigResponse.json(); - - readBootConfigResponseHeaders(loadConfigResponse); - mapBootConfigToMonoConfig(loadedConfig); - hookDownloadResource(module); - - function defaultLoadBootConfig(url: string): Promise { - return loaderHelpers.fetch_like(url, { - method: "GET", - credentials: "include", - cache: "no-cache", - }); - } -} - -function readBootConfigResponseHeaders(loadConfigResponse: Response) { - const config = loaderHelpers.config; - - if (!config.applicationEnvironment) { - config.applicationEnvironment = loadConfigResponse.headers.get("Blazor-Environment") || loadConfigResponse.headers.get("DotNet-Environment") || "Production"; - } - - if (!config.environmentVariables) - config.environmentVariables = {}; - - const modifiableAssemblies = loadConfigResponse.headers.get("DOTNET-MODIFIABLE-ASSEMBLIES"); - if (modifiableAssemblies) { - // Configure the app to enable hot reload in Development. - config.environmentVariables["DOTNET_MODIFIABLE_ASSEMBLIES"] = modifiableAssemblies; - } - - const aspnetCoreBrowserTools = loadConfigResponse.headers.get("ASPNETCORE-BROWSER-TOOLS"); - if (aspnetCoreBrowserTools) { - // See https://github.com/dotnet/aspnetcore/issues/37357#issuecomment-941237000 - config.environmentVariables["__ASPNETCORE_BROWSER_TOOLS"] = aspnetCoreBrowserTools; - } -} +import { deep_merge_config } from "../config"; let resourcesLoaded = 0; const totalResources = new Set(); @@ -86,7 +31,7 @@ const monoToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType | u "dotnetwasm": "dotnetwasm", }; -function hookDownloadResource(module: DotnetModuleInternal) { +export function hookDownloadResource(module: DotnetModuleInternal) { // it would not `loadResource` on types for which there is no typesMap mapping const downloadResource = (asset: AssetEntry): LoadingResource | undefined => { // GOTCHA: the mapping to blazor asset type may not cover all mono owned asset types in the future in which case: @@ -112,60 +57,15 @@ function hookDownloadResource(module: DotnetModuleInternal) { loaderHelpers.downloadResource = downloadResource; // polyfills were already assigned } -function mapBootConfigToMonoConfig(loadedConfig: MonoConfigInternal) { +export function mapResourcesToAssets(loadedConfig: MonoConfig) { mono_assert(loadedConfig.resources, "Loaded config does not contain resources"); const config = loaderHelpers.config; const resources = loadedConfig.resources; - const assets: AssetEntry[] = []; - const environmentVariables: any = { - // From boot config - ...(loadedConfig.environmentVariables || {}), - // From JavaScript - ...(config.environmentVariables || {}) - }; - - config.cacheBootResources = loadedConfig.cacheBootResources; - config.linkerEnabled = loadedConfig.linkerEnabled; - config.remoteSources = (resources as any).remoteSources; - config.assets = assets; - config.extensions = loadedConfig.extensions; - config.resources = { - extensions: resources.extensions - }; + deep_merge_config(config, loadedConfig); - // Default values (when WasmDebugLevel is not set) - // - Build (debug) => debugBuild=true & debugLevel=-1 => -1 - // - Build (release) => debugBuild=true & debugLevel=0 => 0 - // - Publish (debug) => debugBuild=false & debugLevel=-1 => 0 - // - Publish (release) => debugBuild=false & debugLevel=0 => 0 - config.debugLevel = hasDebuggingEnabled(config) ? loadedConfig.debugLevel : 0; - config.mainAssemblyName = loadedConfig.mainAssemblyName; - - const anyBootConfig = (loadedConfig as any); - for (const key in loadedConfig) { - if (Object.prototype.hasOwnProperty.call(anyBootConfig, key)) { - if (anyBootConfig[key] === null) { - delete anyBootConfig[key]; - } - } - } - - // FIXME this mix of both formats is ugly temporary hack - Object.assign(config, { - ...loadedConfig, - }); - - config.environmentVariables = environmentVariables; - - if (loadedConfig.startupMemoryCache !== undefined) { - config.startupMemoryCache = loadedConfig.startupMemoryCache; - } - - if (loadedConfig.runtimeOptions) { - config.runtimeOptions = [...(config.runtimeOptions || []), ...(loadedConfig.runtimeOptions || [])]; - } + const assets = config.assets!; for (const name in resources.assembly) { const asset: AssetEntry = { @@ -262,11 +162,6 @@ function mapBootConfigToMonoConfig(loadedConfig: MonoConfigInternal) { if (!hasIcuData) { config.globalizationMode = GlobalizationMode.Invariant; } - - if (config.applicationCulture) { - // If a culture is specified via start options use that to initialize the Emscripten \ .NET culture. - environmentVariables["LANG"] = `${config.applicationCulture}.UTF-8`; - } } function fileName(name: string) { @@ -277,33 +172,33 @@ function fileName(name: string) { return name.substring(lastIndexOfSlash); } -function getICUResourceName(bootConfig: MonoConfigInternal, moduleConfig: MonoConfigInternal, culture: string | undefined): string { - mono_assert(bootConfig.resources?.runtime, "Boot config does not contain runtime resources"); +function getICUResourceName(loadedConfig: MonoConfig, config: MonoConfig, culture: string | undefined): string { + mono_assert(loadedConfig.resources?.runtime, "Boot config does not contain runtime resources"); - if (bootConfig.globalizationMode === GlobalizationMode.Custom) { + if (loadedConfig.globalizationMode === GlobalizationMode.Custom) { const icuFiles = Object - .keys(bootConfig.resources.runtime) + .keys(loadedConfig.resources.runtime) .filter(n => n.startsWith("icudt") && n.endsWith(".dat")); if (icuFiles.length === 1) { - moduleConfig.globalizationMode = GlobalizationMode.Custom; + config.globalizationMode = GlobalizationMode.Custom; const customIcuFile = icuFiles[0]; return customIcuFile; } } - if (bootConfig.globalizationMode === GlobalizationMode.Hybrid) { - moduleConfig.globalizationMode = GlobalizationMode.Hybrid; + if (loadedConfig.globalizationMode === GlobalizationMode.Hybrid) { + config.globalizationMode = GlobalizationMode.Hybrid; const reducedICUResourceName = "icudt_hybrid.dat"; return reducedICUResourceName; } - if (!culture || bootConfig.globalizationMode === GlobalizationMode.All) { - moduleConfig.globalizationMode = GlobalizationMode.All; + if (!culture || loadedConfig.globalizationMode === GlobalizationMode.All) { + config.globalizationMode = GlobalizationMode.All; const combinedICUResourceName = "icudt.dat"; return combinedICUResourceName; } - moduleConfig.globalizationMode = GlobalizationMode.Sharded; + config.globalizationMode = GlobalizationMode.Sharded; const prefix = culture.split("-")[0]; if (prefix === "en" || [ diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index 2ddeb57c803fd9..d9791a060b352b 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -3,9 +3,9 @@ import BuildConfiguration from "consts:configuration"; import type { DotnetModuleInternal, MonoConfigInternal } from "../types/internal"; -import type { DotnetModuleConfig } from "../types"; +import type { DotnetModuleConfig, MonoConfig } from "../types"; import { ENVIRONMENT_IS_WEB, exportedRuntimeAPI, loaderHelpers, runtimeHelpers } from "./globals"; -import { loadBootConfig } from "./blazor/_Integration"; +import { hookDownloadResource, mapResourcesToAssets } from "./blazor/_Integration"; import { mono_log_error, mono_log_debug } from "./logging"; import { invokeLibraryInitializers } from "./libraryInitializers"; import { mono_exit } from "./exit"; @@ -15,6 +15,9 @@ export function deep_merge_config(target: MonoConfigInternal, source: MonoConfig if (providedConfig.assets) { providedConfig.assets = [...(target.assets || []), ...(providedConfig.assets || [])]; } + if (providedConfig.resources) { + providedConfig.resources = { ...(target.resources || {}), ...(providedConfig.resources || {}) }; + } if (providedConfig.environmentVariables) { providedConfig.environmentVariables = { ...(target.environmentVariables || {}), ...(providedConfig.environmentVariables || {}) }; } @@ -46,9 +49,22 @@ export function normalizeConfig() { if (config.debugLevel === undefined && BuildConfiguration === "Debug") { config.debugLevel = -1; } + + // Default values (when WasmDebugLevel is not set) + // - Build (debug) => debugBuild=true & debugLevel=-1 => -1 + // - Build (release) => debugBuild=true & debugLevel=0 => 0 + // - Publish (debug) => debugBuild=false & debugLevel=-1 => 0 + // - Publish (release) => debugBuild=false & debugLevel=0 => 0 + config.debugLevel = hasDebuggingEnabled(config) ? config.debugLevel : 0; + if (config.diagnosticTracing === undefined && BuildConfiguration === "Debug") { config.diagnosticTracing = true; } + if (config.applicationCulture) { + // If a culture is specified via start options use that to initialize the Emscripten \ .NET culture. + config.environmentVariables!["LANG"] = `${config.applicationCulture}.UTF-8`; + } + runtimeHelpers.diagnosticTracing = loaderHelpers.diagnosticTracing = !!config.diagnosticTracing; runtimeHelpers.waitForDebugger = config.waitForDebugger; config.startupMemoryCache = !!config.startupMemoryCache; @@ -60,7 +76,6 @@ export function normalizeConfig() { runtimeHelpers.enablePerfMeasure = !!config.browserProfilerOptions && globalThis.performance && typeof globalThis.performance.measure === "function"; - } let configLoaded = false; @@ -111,4 +126,59 @@ export function hasDebuggingEnabled(config: MonoConfigInternal): boolean { const hasReferencedPdbs = !!config.resources!.pdb; return (hasReferencedPdbs || config.debugLevel != 0) && (loaderHelpers.isChromium || loaderHelpers.isFirefox); +} + +async function loadBootConfig(module: DotnetModuleInternal) { + const defaultConfigSrc = loaderHelpers.locateFile(module.configSrc!); + + const loaderResponse = loaderHelpers.loadBootResource !== undefined ? + loaderHelpers.loadBootResource("manifest", "blazor.boot.json", defaultConfigSrc, "") : + defaultLoadBootConfig(defaultConfigSrc); + + let loadConfigResponse: Response; + + if (!loaderResponse) { + loadConfigResponse = await defaultLoadBootConfig(defaultConfigSrc); + } else if (typeof loaderResponse === "string") { + loadConfigResponse = await defaultLoadBootConfig(loaderResponse); + } else { + loadConfigResponse = await loaderResponse; + } + + const loadedConfig: MonoConfig = await loadConfigResponse.json(); + + readBootConfigResponseHeaders(loadConfigResponse); + mapResourcesToAssets(loadedConfig); + hookDownloadResource(module); + + function defaultLoadBootConfig(url: string): Promise { + return loaderHelpers.fetch_like(url, { + method: "GET", + credentials: "include", + cache: "no-cache", + }); + } +} + +function readBootConfigResponseHeaders(loadConfigResponse: Response) { + const config = loaderHelpers.config; + + if (!config.applicationEnvironment) { + config.applicationEnvironment = loadConfigResponse.headers.get("Blazor-Environment") || loadConfigResponse.headers.get("DotNet-Environment") || "Production"; + } + + if (!config.environmentVariables) + config.environmentVariables = {}; + + const modifiableAssemblies = loadConfigResponse.headers.get("DOTNET-MODIFIABLE-ASSEMBLIES"); + if (modifiableAssemblies) { + // Configure the app to enable hot reload in Development. + config.environmentVariables["DOTNET_MODIFIABLE_ASSEMBLIES"] = modifiableAssemblies; + } + + const aspnetCoreBrowserTools = loadConfigResponse.headers.get("ASPNETCORE-BROWSER-TOOLS"); + if (aspnetCoreBrowserTools) { + // See https://github.com/dotnet/aspnetcore/issues/37357#issuecomment-941237000 + config.environmentVariables["__ASPNETCORE_BROWSER_TOOLS"] = aspnetCoreBrowserTools; + } } \ No newline at end of file diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index a71540c7469c80..75330d0591b700 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -120,7 +120,6 @@ export interface ResourceGroups { readonly pdb?: ResourceList; readonly runtime?: ResourceList; // nullable only temporarily readonly satelliteResources?: { [cultureName: string]: ResourceList }; - readonly libraryInitializers?: ResourceList, readonly libraryStartupModules?: { readonly onRuntimeConfigLoaded?: ResourceList, readonly onRuntimeReady?: ResourceList From fe7f17361326454a02cafc3f8411feda4cea2ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 26 Jul 2023 14:23:29 +0200 Subject: [PATCH 21/65] Ensure assets collection --- src/mono/wasm/runtime/loader/blazor/_Integration.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index c5e34bf957a2e2..2f8aaf5a67820b 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -7,7 +7,7 @@ import { GlobalizationMode, type AssetBehaviours, type AssetEntry, type LoadingR import { ENVIRONMENT_IS_WEB, loaderHelpers, mono_assert } from "../globals"; import { loadResource } from "../resourceLoader"; import { appendUniqueQuery } from "../assets"; -import { deep_merge_config } from "../config"; +import { deep_merge_config, normalizeConfig } from "../config"; let resourcesLoaded = 0; const totalResources = new Set(); @@ -65,7 +65,11 @@ export function mapResourcesToAssets(loadedConfig: MonoConfig) { deep_merge_config(config, loadedConfig); - const assets = config.assets!; + if (!config.assets) { + config.assets = []; + } + + const assets = config.assets; for (const name in resources.assembly) { const asset: AssetEntry = { From f876a6794f48a8b819ff87793141168aa9dd5e3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 26 Jul 2023 15:08:24 +0200 Subject: [PATCH 22/65] Remove unused import --- src/mono/wasm/runtime/loader/blazor/_Integration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 2f8aaf5a67820b..54a235fbc89a42 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -7,7 +7,7 @@ import { GlobalizationMode, type AssetBehaviours, type AssetEntry, type LoadingR import { ENVIRONMENT_IS_WEB, loaderHelpers, mono_assert } from "../globals"; import { loadResource } from "../resourceLoader"; import { appendUniqueQuery } from "../assets"; -import { deep_merge_config, normalizeConfig } from "../config"; +import { deep_merge_config } from "../config"; let resourcesLoaded = 0; const totalResources = new Set(); From 652e0ae2c272eb012f7823992ce4fb78deb7b4cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 26 Jul 2023 20:39:26 +0200 Subject: [PATCH 23/65] Fix deep_merge_config --- src/mono/wasm/runtime/loader/config.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index d9791a060b352b..c3884d6da157ce 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -11,17 +11,18 @@ import { invokeLibraryInitializers } from "./libraryInitializers"; import { mono_exit } from "./exit"; export function deep_merge_config(target: MonoConfigInternal, source: MonoConfigInternal): MonoConfigInternal { + // If source has collection fields set to null (produced by boot config for example), we should maintain the target values const providedConfig: MonoConfigInternal = { ...source }; - if (providedConfig.assets) { + if (providedConfig.assets !== undefined) { providedConfig.assets = [...(target.assets || []), ...(providedConfig.assets || [])]; } - if (providedConfig.resources) { + if (providedConfig.resources !== undefined) { providedConfig.resources = { ...(target.resources || {}), ...(providedConfig.resources || {}) }; } - if (providedConfig.environmentVariables) { + if (providedConfig.environmentVariables !== undefined) { providedConfig.environmentVariables = { ...(target.environmentVariables || {}), ...(providedConfig.environmentVariables || {}) }; } - if (providedConfig.runtimeOptions) { + if (providedConfig.runtimeOptions !== undefined) { providedConfig.runtimeOptions = [...(target.runtimeOptions || []), ...(providedConfig.runtimeOptions || [])]; } return Object.assign(target, providedConfig); From 41f7e38f0c834fbe50e93a4ac6aa7ee6797b53cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 26 Jul 2023 22:58:16 +0200 Subject: [PATCH 24/65] Split runtime resources into groups by behavior in MSBuild --- src/mono/wasm/runtime/dotnet.d.ts | 10 +- .../runtime/loader/blazor/_Integration.ts | 125 ++++++++++++------ src/mono/wasm/runtime/types/index.ts | 11 +- .../BootJsonData.cs | 32 ++++- .../GenerateWasmBootJson.cs | 46 ++++++- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 46 +++++-- 6 files changed, 204 insertions(+), 66 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index d322c3f3c54647..829a1c260d2e4d 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -182,7 +182,7 @@ interface ResourceGroups { readonly assembly?: ResourceList; readonly lazyAssembly?: ResourceList; readonly pdb?: ResourceList; - readonly runtime?: ResourceList; + readonly native?: NativeResources; readonly satelliteResources?: { [cultureName: string]: ResourceList; }; @@ -195,6 +195,14 @@ interface ResourceGroups { [virtualPath: string]: ResourceList; }; } +interface NativeResources { + readonly jsModuleWorker: ResourceList; + readonly jsModuleNative: ResourceList; + readonly jsModuleRuntime: ResourceList; + readonly wasmNative: ResourceList; + readonly symbols: ResourceList; + readonly icu: ResourceList; +} type ResourceList = { [name: string]: string; }; diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 54a235fbc89a42..b2bbc17f98c1bf 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { DotnetModuleInternal } from "../../types/internal"; -import { GlobalizationMode, type AssetBehaviours, type AssetEntry, type LoadingResource, type WebAssemblyBootResourceType, MonoConfig } from "../../types"; +import { GlobalizationMode, type AssetEntry, type LoadingResource, type WebAssemblyBootResourceType, MonoConfig } from "../../types"; import { ENVIRONMENT_IS_WEB, loaderHelpers, mono_assert } from "../globals"; import { loadResource } from "../resourceLoader"; @@ -12,17 +12,6 @@ import { deep_merge_config } from "../config"; let resourcesLoaded = 0; const totalResources = new Set(); -const behaviorByName = (name: string): AssetBehaviours | "other" => { - return name === "dotnet.native.wasm" ? "dotnetwasm" - : (name.startsWith("dotnet.native.worker") && name.endsWith(".js")) ? "js-module-threads" - : (name.startsWith("dotnet.native") && name.endsWith(".js")) ? "js-module-native" - : (name.startsWith("dotnet.runtime") && name.endsWith(".js")) ? "js-module-runtime" - : (name.startsWith("dotnet") && name.endsWith(".js")) ? "js-module-dotnet" - : (name.startsWith("dotnet.native") && name.endsWith(".symbols")) ? "symbols" - : name.startsWith("icudt") ? "icu" - : "other"; -}; - const monoToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType | undefined } = { "assembly": "assembly", "pdb": "pdb", @@ -62,6 +51,7 @@ export function mapResourcesToAssets(loadedConfig: MonoConfig) { const config = loaderHelpers.config; const resources = loadedConfig.resources; + const nativeResources = resources.native!; deep_merge_config(config, loadedConfig); @@ -91,34 +81,70 @@ export function mapResourcesToAssets(loadedConfig: MonoConfig) { assets.push(asset); } } + + for (const name in nativeResources.jsModuleWorker) { + const behavior = "js-module-threads"; + assets.push({ + name, + resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), behavior), + hash: nativeResources.jsModuleWorker[name], + behavior + }); + } + + for (const name in nativeResources.jsModuleNative) { + const behavior = "js-module-native"; + assets.push({ + name, + resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), behavior), + hash: nativeResources.jsModuleNative[name], + behavior + }); + } + + for (const name in nativeResources.jsModuleRuntime) { + const behavior = "js-module-runtime"; + assets.push({ + name, + resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), behavior), + hash: nativeResources.jsModuleRuntime[name], + behavior + }); + } + + for (const name in nativeResources.wasmNative) { + const behavior = "dotnetwasm"; + assets.push({ + name, + resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), behavior), + hash: nativeResources.wasmNative[name], + behavior + }); + } + + for (const name in nativeResources.symbols) { + const behavior = "symbols"; + assets.push({ + name, + resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), behavior), + hash: nativeResources.symbols[name], + behavior + }); + } + const applicationCulture = config.applicationCulture || (ENVIRONMENT_IS_WEB ? (navigator.languages && navigator.languages[0]) : Intl.DateTimeFormat().resolvedOptions().locale); const icuDataResourceName = getICUResourceName(loadedConfig, config, applicationCulture); let hasIcuData = false; - for (const name in resources.runtime) { - const behavior = behaviorByName(name) as any; - let loadRemote = false; - if (behavior === "icu") { - if (loadedConfig.globalizationMode === GlobalizationMode.Invariant) { - continue; - } - if (name !== icuDataResourceName) { - continue; - } - loadRemote = true; - hasIcuData = true; - } else if (behavior === "js-module-dotnet") { - continue; - } - - const resolvedUrl = appendUniqueQuery(loaderHelpers.locateFile(name), behavior); - const asset: AssetEntry = { - name, - resolvedUrl, - hash: resources.runtime[name], + if (icuDataResourceName != null) { + const behavior = "icu"; + hasIcuData = true; + assets.push({ + name: icuDataResourceName, + resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(icuDataResourceName), behavior), + hash: nativeResources.icu[icuDataResourceName], behavior, - loadRemote - }; - assets.push(asset); + loadRemote: true + }); } if (config.loadAllSatelliteResources && resources.satelliteResources) { @@ -176,13 +202,15 @@ function fileName(name: string) { return name.substring(lastIndexOfSlash); } -function getICUResourceName(loadedConfig: MonoConfig, config: MonoConfig, culture: string | undefined): string { - mono_assert(loadedConfig.resources?.runtime, "Boot config does not contain runtime resources"); +function getICUResourceName(loadedConfig: MonoConfig, config: MonoConfig, culture: string | undefined): string | null { + if (!loadedConfig.resources?.native?.icu) { + config.globalizationMode = GlobalizationMode.Invariant; + return null; + } + + const icuFiles = Object.keys(loadedConfig.resources.native.icu); if (loadedConfig.globalizationMode === GlobalizationMode.Custom) { - const icuFiles = Object - .keys(loadedConfig.resources.runtime) - .filter(n => n.startsWith("icudt") && n.endsWith(".dat")); if (icuFiles.length === 1) { config.globalizationMode = GlobalizationMode.Custom; const customIcuFile = icuFiles[0]; @@ -190,6 +218,16 @@ function getICUResourceName(loadedConfig: MonoConfig, config: MonoConfig, cultur } } + const icuFile = getICUResourceNameForCulture(loadedConfig, config, culture); + if (icuFile && icuFiles.includes(icuFile)) { + return icuFile; + } + + config.globalizationMode = GlobalizationMode.Invariant; + return null; +} + +function getICUResourceNameForCulture(loadedConfig: MonoConfig, config: MonoConfig, culture: string | undefined): string | null { if (loadedConfig.globalizationMode === GlobalizationMode.Hybrid) { config.globalizationMode = GlobalizationMode.Hybrid; const reducedICUResourceName = "icudt_hybrid.dat"; @@ -217,6 +255,7 @@ function getICUResourceName(loadedConfig: MonoConfig, config: MonoConfig, cultur ].includes(culture)) { return "icudt_EFIGS.dat"; } + if ([ "zh", "ko", @@ -224,6 +263,6 @@ function getICUResourceName(loadedConfig: MonoConfig, config: MonoConfig, cultur ].includes(prefix)) { return "icudt_CJK.dat"; } - return "icudt_no_CJK.dat"; -} + return "icudt_no_CJK.dat"; +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 75330d0591b700..468733ce5aa21a 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -118,7 +118,7 @@ export interface ResourceGroups { readonly assembly?: ResourceList; // nullable only temporarily readonly lazyAssembly?: ResourceList; // nullable only temporarily readonly pdb?: ResourceList; - readonly runtime?: ResourceList; // nullable only temporarily + readonly native?: NativeResources; // nullable only temporarily readonly satelliteResources?: { [cultureName: string]: ResourceList }; readonly libraryStartupModules?: { readonly onRuntimeConfigLoaded?: ResourceList, @@ -128,6 +128,15 @@ export interface ResourceGroups { readonly vfs?: { [virtualPath: string]: ResourceList }; } +export interface NativeResources { + readonly jsModuleWorker: ResourceList; + readonly jsModuleNative: ResourceList; + readonly jsModuleRuntime: ResourceList; + readonly wasmNative: ResourceList; + readonly symbols: ResourceList; + readonly icu: ResourceList; +} + export type ResourceList = { [name: string]: string }; /** diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs index 35834846517ef9..7ce67eb751a607 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -123,7 +123,15 @@ public class ResourcesData /// /// .NET Wasm runtime resources (dotnet.wasm, dotnet.js) etc. /// - public ResourceHashesByNameDictionary runtime { get; set; } = new ResourceHashesByNameDictionary(); + /// + /// Deprecated in .NET 8, use . + /// + public ResourceHashesByNameDictionary runtime { get; set; } + + /// + /// Native runtime assets (dotnet.wasm, dotnet.js, etc). + /// + public NativeResources native { get; set; } /// /// "assembly" (.dll) resources @@ -182,6 +190,28 @@ public class ResourcesData public List remoteSources { get; set; } } +[DataContract] +public class NativeResources +{ + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary jsModuleWorker { get; set; } + + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary jsModuleNative { get; set; } + + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary jsModuleRuntime { get; set; } + + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary wasmNative { get; set; } + + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary symbols { get; set; } + + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary icu { get; set; } +} + [DataContract] public class TypedLibraryStartupModules { diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index 87cbd81634cb31..5f52e28f307f9a 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -195,12 +195,36 @@ public void WriteBootJson(Stream output, string entryAssemblyName) string.Equals(assetTraitValue, "native", StringComparison.OrdinalIgnoreCase)) { Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as a native application resource.", resource.ItemSpec); - if (fileName.StartsWith("dotnet", StringComparison.OrdinalIgnoreCase) && string.Equals(fileExtension, ".wasm", StringComparison.OrdinalIgnoreCase)) + + if (IsTargeting80OrLater()) { - behavior = "dotnetwasm"; + resourceData.native ??= new(); + if (resourceName.StartsWith("dotnet.native.worker") && resourceName.EndsWith(".js")) + resourceList = resourceData.native.jsModuleWorker ??= new(); + else if (resourceName.StartsWith("dotnet.native") && resourceName.EndsWith(".js")) + resourceList = resourceData.native.jsModuleNative ??= new(); + else if (resourceName.StartsWith("dotnet.runtime") && resourceName.EndsWith(".js")) + resourceList = resourceData.native.jsModuleRuntime ??= new(); + else if (resourceName.StartsWith("dotnet.native") && resourceName.EndsWith(".wasm")) + resourceList = resourceData.native.wasmNative ??= new(); + else if (resourceName.StartsWith("dotnet") && resourceName.EndsWith(".js")) + continue; + else if (resourceName.StartsWith("dotnet.native") && resourceName.EndsWith(".symbols")) + resourceList = resourceData.native.symbols ??= new(); + else if (resourceName.StartsWith("icudt")) + resourceList = resourceData.native.icu ??= new(); + else + Log.LogError($"The resource '{resourceName}' is not recognized as any native asset"); } + else + { + if (fileName.StartsWith("dotnet", StringComparison.OrdinalIgnoreCase) && string.Equals(fileExtension, ".wasm", StringComparison.OrdinalIgnoreCase)) + { + behavior = "dotnetwasm"; + } - resourceList = resourceData.runtime; + resourceList = resourceData.runtime ??= new(); + } } else if (string.Equals("JSModule", assetTraitName, StringComparison.OrdinalIgnoreCase) && string.Equals(assetTraitValue, "JSLibraryModule", StringComparison.OrdinalIgnoreCase)) @@ -355,17 +379,25 @@ private static void ComputeResourcesHash(BootJsonData bootConfig) { var sb = new StringBuilder(); - static void AddDictionary(StringBuilder sb, Dictionary res) + static void AddDictionary(StringBuilder sb, ResourceHashesByNameDictionary res) { + if (res == null) + return; + foreach (var asset in res) sb.Append(asset.Value); } AddDictionary(sb, bootConfig.resources.assembly); - AddDictionary(sb, bootConfig.resources.runtime); - if (bootConfig.resources.lazyAssembly != null) - AddDictionary(sb, bootConfig.resources.lazyAssembly); + AddDictionary(sb, bootConfig.resources.native?.jsModuleWorker); + AddDictionary(sb, bootConfig.resources.native?.jsModuleNative); + AddDictionary(sb, bootConfig.resources.native?.jsModuleRuntime); + AddDictionary(sb, bootConfig.resources.native?.wasmNative); + AddDictionary(sb, bootConfig.resources.native?.symbols); + AddDictionary(sb, bootConfig.resources.native?.icu); + AddDictionary(sb, bootConfig.resources.runtime); + AddDictionary(sb, bootConfig.resources.lazyAssembly); if (bootConfig.resources.satelliteResources != null) { diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index e0e71c0e5a727f..fa4e3db3f179ed 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -158,19 +158,26 @@ protected override bool ExecuteInternal() var itemHash = Utils.ComputeIntegrity(item.ItemSpec); - if (name.StartsWith("dotnet", StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetExtension(name), ".wasm", StringComparison.OrdinalIgnoreCase)) - { - if (bootConfig.resources.runtimeAssets == null) - bootConfig.resources.runtimeAssets = new(); - - bootConfig.resources.runtimeAssets[name] = new() - { - hash = itemHash, - behavior = "dotnetwasm" - }; - } + bootConfig.resources.native ??= new(); + Dictionary? resourceList = null; + + if (name.StartsWith("dotnet.native.worker", StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetExtension(name), ".js", StringComparison.OrdinalIgnoreCase)) + resourceList = bootConfig.resources.native.jsModuleWorker ??= new(); + else if (name.StartsWith("dotnet.native", StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetExtension(name), ".js", StringComparison.OrdinalIgnoreCase)) + resourceList = bootConfig.resources.native.jsModuleNative ??= new(); + else if (name.StartsWith("dotnet.runtime", StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetExtension(name), ".js", StringComparison.OrdinalIgnoreCase)) + resourceList = bootConfig.resources.native.jsModuleRuntime ??= new(); + else if (name.StartsWith("dotnet.native", StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetExtension(name), ".wasm", StringComparison.OrdinalIgnoreCase)) + resourceList = bootConfig.resources.native.wasmNative ??= new(); + else if (name.StartsWith("dotnet", StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetExtension(name), ".js", StringComparison.OrdinalIgnoreCase)) + continue; + else if (name.StartsWith("dotnet.native", StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetExtension(name), ".symbols", StringComparison.OrdinalIgnoreCase)) + resourceList = bootConfig.resources.native.symbols ??= new(); + else if (name.StartsWith("icudt", StringComparison.OrdinalIgnoreCase)) + resourceList = bootConfig.resources.native.icu ??= new(); - bootConfig.resources.runtime[name] = itemHash; + if (resourceList != null) + resourceList[name] = itemHash; } string packageJsonPath = Path.Combine(AppDir, "package.json"); @@ -312,7 +319,9 @@ protected override bool ExecuteInternal() return false; } - bootConfig.resources.runtime[Path.GetFileName(idfn)] = Utils.ComputeIntegrity(idfn); + bootConfig.resources.native ??= new(); + bootConfig.resources.native.icu ??= new(); + bootConfig.resources.native.icu[Path.GetFileName(idfn)] = Utils.ComputeIntegrity(idfn); } } @@ -374,12 +383,23 @@ protected override bool ExecuteInternal() static void AddDictionary(StringBuilder sb, Dictionary res) { + if (res == null) + return; + foreach (var asset in res) sb.Append(asset.Value); } AddDictionary(sb, bootConfig.resources.assembly); + + AddDictionary(sb, bootConfig.resources.native.jsModuleWorker); + AddDictionary(sb, bootConfig.resources.native.jsModuleNative); + AddDictionary(sb, bootConfig.resources.native.jsModuleRuntime); + AddDictionary(sb, bootConfig.resources.native.wasmNative); + AddDictionary(sb, bootConfig.resources.native.symbols); + AddDictionary(sb, bootConfig.resources.native.icu); AddDictionary(sb, bootConfig.resources.runtime); + AddDictionary(sb, bootConfig.resources.lazyAssembly); if (bootConfig.resources.lazyAssembly != null) AddDictionary(sb, bootConfig.resources.lazyAssembly); From 7d52aee07bc4152e1ebe671e3d07e22475a662bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 26 Jul 2023 23:36:07 +0200 Subject: [PATCH 25/65] Use AssetBehaviours in resourceLoader --- .../wasm/runtime/loader/resourceLoader.ts | 52 ++++++++++++------- src/mono/wasm/runtime/satelliteAssemblies.ts | 2 +- src/mono/wasm/runtime/types/internal.ts | 6 +-- 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/mono/wasm/runtime/loader/resourceLoader.ts b/src/mono/wasm/runtime/loader/resourceLoader.ts index 5faab2445c480a..b672df5fdb5581 100644 --- a/src/mono/wasm/runtime/loader/resourceLoader.ts +++ b/src/mono/wasm/runtime/loader/resourceLoader.ts @@ -1,29 +1,38 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { MonoConfig, ResourceList, WebAssemblyBootResourceType } from "../types"; +import type { AssetBehaviours, MonoConfig, ResourceList, WebAssemblyBootResourceType } from "../types"; import { loaderHelpers } from "./globals"; const networkFetchCacheMode = "no-cache"; -const cacheSkipResourceTypes = ["configuration"]; +const cacheSkipResourceTypes: AssetBehaviours[] = ["vfs"]; // Previously on configuration const usedCacheKeys: { [key: string]: boolean } = {}; const networkLoads: { [name: string]: LoadLogEntry } = {}; const cacheLoads: { [name: string]: LoadLogEntry } = {}; let cacheIfUsed: Cache | null; -export function loadResources(resources: ResourceList, url: (name: string) => string, resourceType: WebAssemblyBootResourceType): LoadingResource[] { +const monoToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType | undefined } = { + "resource": "assembly", + "assembly": "assembly", + "pdb": "pdb", + "icu": "globalization", + "vfs": "configuration", + "dotnetwasm": "dotnetwasm", +}; + +export function loadResources(resources: ResourceList, url: (name: string) => string, behavior: AssetBehaviours): LoadingResource[] { return Object.keys(resources) - .map(name => loadResource(name, url(name), resources[name], resourceType)); + .map(name => loadResource(name, url(name), resources[name], behavior)); } -export function loadResource(name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType): LoadingResource { - const response = cacheIfUsed && !cacheSkipResourceTypes.includes(resourceType) - ? loadResourceWithCaching(cacheIfUsed, name, url, contentHash, resourceType) - : loadResourceWithoutCaching(name, url, contentHash, resourceType); +export function loadResource(name: string, url: string, contentHash: string, behavior: AssetBehaviours): LoadingResource { + const response = cacheIfUsed && !cacheSkipResourceTypes.includes(behavior) + ? loadResourceWithCaching(cacheIfUsed, name, url, contentHash, behavior) + : loadResourceWithoutCaching(name, url, contentHash, behavior); const absoluteUrl = loaderHelpers.locateFile(url); - if (resourceType == "assembly") { + if (behavior == "assembly") { loaderHelpers.loadedAssemblies.push(absoluteUrl); } return { name, url: absoluteUrl, response }; @@ -82,7 +91,7 @@ export async function purgeUnusedCacheEntriesAsync(): Promise { } } -async function loadResourceWithCaching(cache: Cache, name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType) { +async function loadResourceWithCaching(cache: Cache, name: string, url: string, contentHash: string, behavior: AssetBehaviours) { // Since we are going to cache the response, we require there to be a content hash for integrity // checking. We don't want to cache bad responses. There should always be a hash, because the build // process generates this data. @@ -108,22 +117,25 @@ async function loadResourceWithCaching(cache: Cache, name: string, url: string, return cachedResponse; } else { // It's not in the cache. Fetch from network. - const networkResponse = await loadResourceWithoutCaching(name, url, contentHash, resourceType); + const networkResponse = await loadResourceWithoutCaching(name, url, contentHash, behavior); addToCacheAsync(cache, name, cacheKey, networkResponse); // Don't await - add to cache in background return networkResponse; } } -function loadResourceWithoutCaching(name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType): Promise { +function loadResourceWithoutCaching(name: string, url: string, contentHash: string, behavior: AssetBehaviours): Promise { // Allow developers to override how the resource is loaded if (loaderHelpers.loadBootResource) { - const customLoadResult = loaderHelpers.loadBootResource(resourceType, name, url, contentHash); - if (customLoadResult instanceof Promise) { - // They are supplying an entire custom response, so just use that - return customLoadResult; - } else if (typeof customLoadResult === "string") { - // They are supplying a custom URL, so use that with the default fetch behavior - url = customLoadResult; + const resourceType = monoToBlazorAssetTypeMap[behavior]; + if (resourceType) { + const customLoadResult = loaderHelpers.loadBootResource(resourceType!, name, url, contentHash); + if (customLoadResult instanceof Promise) { + // They are supplying an entire custom response, so just use that + return customLoadResult; + } else if (typeof customLoadResult === "string") { + // They are supplying a custom URL, so use that with the default fetch behavior + url = customLoadResult; + } } } @@ -134,7 +146,7 @@ function loadResourceWithoutCaching(name: string, url: string, contentHash: stri cache: networkFetchCacheMode }; - if (resourceType === "configuration") { + if (behavior === "vfs") { // Include credentials so the server can allow download / provide user specific file fetchOptions.credentials = "include"; } else { diff --git a/src/mono/wasm/runtime/satelliteAssemblies.ts b/src/mono/wasm/runtime/satelliteAssemblies.ts index 83b93f868fbcd0..ffafb9f95c82ac 100644 --- a/src/mono/wasm/runtime/satelliteAssemblies.ts +++ b/src/mono/wasm/runtime/satelliteAssemblies.ts @@ -12,7 +12,7 @@ export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise await Promise.all(culturesToLoad! .filter(culture => Object.prototype.hasOwnProperty.call(satelliteResources, culture)) - .map(culture => loaderHelpers.loadResources(satelliteResources[culture], fileName => loaderHelpers.locateFile(fileName), "assembly")) + .map(culture => loaderHelpers.loadResources(satelliteResources[culture], fileName => loaderHelpers.locateFile(fileName), "resource")) .reduce((previous, next) => previous.concat(next), new Array()) .map(async resource => { const response = await resource.response; diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index bd7fa7ebc84e34..582379ab81924d 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { AssetBehaviours, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, ResourceList, ResourceRequest, RuntimeAPI, WebAssemblyBootResourceType } from "."; +import type { AssetBehaviours, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, ResourceList, ResourceRequest, RuntimeAPI } from "."; import type { CharPtr, EmscriptenModule, ManagedPointer, NativePointer, VoidPtr, Int32Ptr } from "./emscripten"; export type GCHandle = { @@ -143,8 +143,8 @@ export type LoaderHelpers = { err(message: string): void; hasDebuggingEnabled(config: MonoConfig): boolean, - loadResources(resources: ResourceList, url: (name: string) => string, resourceType: WebAssemblyBootResourceType): LoadingResource[], - loadResource(name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType): LoadingResource, + loadResources(resources: ResourceList, url: (name: string) => string, behavior: AssetBehaviours): LoadingResource[], + loadResource(name: string, url: string, contentHash: string, behavior: AssetBehaviours): LoadingResource, loadBootResource?: LoadBootResourceCallback; invokeLibraryInitializers: (functionName: string, args: any[]) => Promise, From 11774762f40f0e317c7ab828ad4c0189f97d7c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 26 Jul 2023 23:41:30 +0200 Subject: [PATCH 26/65] Use loadResource instead of hooking downloadResource --- src/mono/wasm/runtime/loader/assets.ts | 25 ++++++----- .../runtime/loader/blazor/_Integration.ts | 41 +------------------ src/mono/wasm/runtime/loader/config.ts | 3 +- src/mono/wasm/runtime/loader/polyfills.ts | 2 +- src/mono/wasm/runtime/types/index.ts | 1 - src/mono/wasm/runtime/types/internal.ts | 4 +- 6 files changed, 17 insertions(+), 59 deletions(-) diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index 5cf6ec96ea8a07..a0d4b52f817824 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -7,6 +7,7 @@ import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, loaderHelpers, mono_assert, import { createPromiseController } from "./promise-controller"; import { mono_log_debug } from "./logging"; import { mono_exit } from "./exit"; +import { loadResource } from "./resourceLoader"; let throttlingPromise: PromiseAndController | undefined; @@ -391,22 +392,20 @@ export function appendUniqueQuery(attemptUrl: string, behavior: AssetBehaviours) return attemptUrl; } - +let resourcesLoaded = 0; +const totalResources = new Set(); function download_resource(request: ResourceRequest): LoadingResource { try { - if (typeof loaderHelpers.downloadResource === "function") { - const loading = loaderHelpers.downloadResource(request); - if (loading) return loading; - } - const options: any = {}; - if (request.hash) { - options.integrity = request.hash; - } - const response = loaderHelpers.fetch_like(request.resolvedUrl!, options); - return { - name: request.name, url: request.resolvedUrl!, response - }; + const response = loadResource(request.name, request.resolvedUrl!, request.hash!, request.behavior); + + totalResources.add(request.name!); + response.response.then(() => { + resourcesLoaded++; + if (loaderHelpers.onDownloadResourceProgress) + loaderHelpers.onDownloadResourceProgress(resourcesLoaded, totalResources.size); + }); + return response; } catch (err) { const response = { ok: false, diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index b2bbc17f98c1bf..3c19beeec41f7b 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -1,51 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { DotnetModuleInternal } from "../../types/internal"; -import { GlobalizationMode, type AssetEntry, type LoadingResource, type WebAssemblyBootResourceType, MonoConfig } from "../../types"; +import { GlobalizationMode, type AssetEntry, MonoConfig } from "../../types"; import { ENVIRONMENT_IS_WEB, loaderHelpers, mono_assert } from "../globals"; -import { loadResource } from "../resourceLoader"; import { appendUniqueQuery } from "../assets"; import { deep_merge_config } from "../config"; -let resourcesLoaded = 0; -const totalResources = new Set(); - -const monoToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType | undefined } = { - "assembly": "assembly", - "pdb": "pdb", - "icu": "globalization", - "vfs": "configuration", - "dotnetwasm": "dotnetwasm", -}; - -export function hookDownloadResource(module: DotnetModuleInternal) { - // it would not `loadResource` on types for which there is no typesMap mapping - const downloadResource = (asset: AssetEntry): LoadingResource | undefined => { - // GOTCHA: the mapping to blazor asset type may not cover all mono owned asset types in the future in which case: - // A) we may need to add such asset types to the mapping and to WebAssemblyBootResourceType - // B) or we could add generic "runtime" type to WebAssemblyBootResourceType as fallback - // C) or we could return `undefined` and let the runtime to load the asset. In which case the progress will not be reported on it and blazor will not be able to cache it. - const type = monoToBlazorAssetTypeMap[asset.behavior]; - if (type !== undefined) { - const res = loadResource(asset.name, asset.resolvedUrl!, asset.hash!, type); - - totalResources.add(asset.name!); - res.response.then(() => { - resourcesLoaded++; - if (module.onDownloadResourceProgress) - module.onDownloadResourceProgress(resourcesLoaded, totalResources.size); - }); - - return res; - } - return undefined; - }; - - loaderHelpers.downloadResource = downloadResource; // polyfills were already assigned -} - export function mapResourcesToAssets(loadedConfig: MonoConfig) { mono_assert(loadedConfig.resources, "Loaded config does not contain resources"); diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index c3884d6da157ce..ba087c5a904d6f 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -5,7 +5,7 @@ import BuildConfiguration from "consts:configuration"; import type { DotnetModuleInternal, MonoConfigInternal } from "../types/internal"; import type { DotnetModuleConfig, MonoConfig } from "../types"; import { ENVIRONMENT_IS_WEB, exportedRuntimeAPI, loaderHelpers, runtimeHelpers } from "./globals"; -import { hookDownloadResource, mapResourcesToAssets } from "./blazor/_Integration"; +import { mapResourcesToAssets } from "./blazor/_Integration"; import { mono_log_error, mono_log_debug } from "./logging"; import { invokeLibraryInitializers } from "./libraryInitializers"; import { mono_exit } from "./exit"; @@ -150,7 +150,6 @@ async function loadBootConfig(module: DotnetModuleInternal) { readBootConfigResponseHeaders(loadConfigResponse); mapResourcesToAssets(loadedConfig); - hookDownloadResource(module); function defaultLoadBootConfig(url: string): Promise { return loaderHelpers.fetch_like(url, { diff --git a/src/mono/wasm/runtime/loader/polyfills.ts b/src/mono/wasm/runtime/loader/polyfills.ts index 8f3d1e050f78c8..4fb64371c7c6ba 100644 --- a/src/mono/wasm/runtime/loader/polyfills.ts +++ b/src/mono/wasm/runtime/loader/polyfills.ts @@ -48,12 +48,12 @@ export async function detect_features_and_polyfill(module: DotnetModuleInternal) if (isPathAbsolute(path)) return path; return loaderHelpers.scriptDirectory + path; }; - loaderHelpers.downloadResource = module.downloadResource; loaderHelpers.fetch_like = fetch_like; // eslint-disable-next-line no-console loaderHelpers.out = console.log; // eslint-disable-next-line no-console loaderHelpers.err = console.error; + loaderHelpers.onDownloadResourceProgress = module.onDownloadResourceProgress; if (ENVIRONMENT_IS_WEB && globalThis.navigator) { const navigator: any = globalThis.navigator; diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 468733ce5aa21a..44c0e1f0e415a8 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -281,7 +281,6 @@ export type DotnetModuleConfig = { imports?: any; exports?: string[]; - downloadResource?: (request: ResourceRequest) => LoadingResource | undefined } & Partial export type APIType = { diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 582379ab81924d..b9904874066f52 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { AssetBehaviours, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, ResourceList, ResourceRequest, RuntimeAPI } from "."; +import type { AssetBehaviours, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, ResourceList, RuntimeAPI } from "."; import type { CharPtr, EmscriptenModule, ManagedPointer, NativePointer, VoidPtr, Int32Ptr } from "./emscripten"; export type GCHandle = { @@ -138,13 +138,13 @@ export type LoaderHelpers = { setup_proxy_console: (id: string, console: Console, origin: string) => void fetch_like: (url: string, init?: RequestInit) => Promise; locateFile: (path: string, prefix?: string) => string, - downloadResource?: (request: ResourceRequest) => LoadingResource | undefined out(message: string): void; err(message: string): void; hasDebuggingEnabled(config: MonoConfig): boolean, loadResources(resources: ResourceList, url: (name: string) => string, behavior: AssetBehaviours): LoadingResource[], loadResource(name: string, url: string, contentHash: string, behavior: AssetBehaviours): LoadingResource, + onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; loadBootResource?: LoadBootResourceCallback; invokeLibraryInitializers: (functionName: string, args: any[]) => Promise, From cbed59936e044fd6585ac8c3a7d0df54a451e33e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 26 Jul 2023 23:41:45 +0200 Subject: [PATCH 27/65] Update dotnet.d.ts --- src/mono/wasm/runtime/dotnet.d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 829a1c260d2e4d..a59eadd42c91d2 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -339,7 +339,6 @@ type DotnetModuleConfig = { onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; imports?: any; exports?: string[]; - downloadResource?: (request: ResourceRequest) => LoadingResource | undefined; } & Partial; type APIType = { runMain: (mainAssemblyName: string, args: string[]) => Promise; From dc19914d8f0b1a7cb60fd45480db79824f87b064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 10:44:47 +0200 Subject: [PATCH 28/65] Fix WBT --- src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs | 6 +++++- src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs | 2 -- .../wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs | 2 -- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs index ccdf07d0458e23..cda9b5414ac7ac 100644 --- a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs @@ -390,7 +390,11 @@ public void AssertBootJson(AssertBundleOptionsBase options) Assert.True(File.Exists(bootJsonPath), $"Expected to find {bootJsonPath}"); BootJsonData bootJson = ParseBootData(bootJsonPath); - var bootJsonEntries = bootJson.resources.runtime.Keys.Where(k => k.StartsWith("dotnet.", StringComparison.Ordinal)).ToArray(); + var bootJsonEntries = bootJson.resources.native.jsModuleNative.Keys + .Union(bootJson.resources.native.jsModuleRuntime.Keys) + .Union(bootJson.resources.native.jsModuleWorker.Keys) + .Union(bootJson.resources.native.wasmNative.Keys) + .ToArray(); var expectedEntries = new SortedDictionary>(); IReadOnlySet expected = GetDotNetFilesExpectedSet(options); diff --git a/src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs b/src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs index bc1fa0aa2933f2..4c2735e4088d0e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs @@ -22,8 +22,6 @@ public TestMainJsProjectProvider(ITestOutputHelper _testOutput, string? _project protected override IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(AssertBundleOptionsBase assertOptions) => new SortedDictionary() { - { "dotnet.js", false }, - { "dotnet.js.map", false }, { "dotnet.native.js", false }, { "dotnet.native.js.symbols", false }, { "dotnet.native.wasm", false }, diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs b/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs index 1f70aa46df8fa1..cd05c7d56e2e12 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs @@ -21,8 +21,6 @@ public WasmSdkBasedProjectProvider(ITestOutputHelper _testOutput, string? _proje protected override IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(AssertBundleOptionsBase assertOptions) => new SortedDictionary() { - { "dotnet.js", false }, - { "dotnet.js.map", false }, { "dotnet.native.js", true }, { "dotnet.native.js.symbols", false }, { "dotnet.native.wasm", false }, From 3b58a64e76b29a4be1eb4682397a660dfa21425c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 10:45:10 +0200 Subject: [PATCH 29/65] Move config merge to loadBootConfig --- src/mono/wasm/runtime/loader/blazor/_Integration.ts | 3 --- src/mono/wasm/runtime/loader/config.ts | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 3c19beeec41f7b..1586ce6d492da3 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -5,7 +5,6 @@ import { GlobalizationMode, type AssetEntry, MonoConfig } from "../../types"; import { ENVIRONMENT_IS_WEB, loaderHelpers, mono_assert } from "../globals"; import { appendUniqueQuery } from "../assets"; -import { deep_merge_config } from "../config"; export function mapResourcesToAssets(loadedConfig: MonoConfig) { mono_assert(loadedConfig.resources, "Loaded config does not contain resources"); @@ -14,8 +13,6 @@ export function mapResourcesToAssets(loadedConfig: MonoConfig) { const resources = loadedConfig.resources; const nativeResources = resources.native!; - deep_merge_config(config, loadedConfig); - if (!config.assets) { config.assets = []; } diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index ba087c5a904d6f..9a58c0c3f4104d 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -129,7 +129,7 @@ export function hasDebuggingEnabled(config: MonoConfigInternal): boolean { return (hasReferencedPdbs || config.debugLevel != 0) && (loaderHelpers.isChromium || loaderHelpers.isFirefox); } -async function loadBootConfig(module: DotnetModuleInternal) { +async function loadBootConfig(module: DotnetModuleInternal): Promise { const defaultConfigSrc = loaderHelpers.locateFile(module.configSrc!); const loaderResponse = loaderHelpers.loadBootResource !== undefined ? @@ -149,6 +149,7 @@ async function loadBootConfig(module: DotnetModuleInternal) { const loadedConfig: MonoConfig = await loadConfigResponse.json(); readBootConfigResponseHeaders(loadConfigResponse); + deep_merge_config(loaderHelpers.config, loadedConfig); mapResourcesToAssets(loadedConfig); function defaultLoadBootConfig(url: string): Promise { From 5a82f2517e9b9a5818813ad690e741cfa136e0bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 11:07:03 +0200 Subject: [PATCH 30/65] Drop assemblyRootFolder --- src/mono/wasm/runtime/dotnet.d.ts | 4 ---- src/mono/wasm/runtime/loader/assets.ts | 9 ++------- src/mono/wasm/runtime/snapshot.ts | 1 - src/mono/wasm/runtime/types/index.ts | 4 ---- 4 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index a59eadd42c91d2..0e159d4c35aa1e 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -95,10 +95,6 @@ interface DotnetHostBuilder { run(): Promise; } type MonoConfig = { - /** - * The subfolder containing managed assemblies and pdbs. This is relative to dotnet.js script. - */ - assemblyRootFolder?: string; /** * Additional search locations for assets. */ diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index a0d4b52f817824..299969d7c97b94 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -354,19 +354,14 @@ async function start_asset_download_sources(asset: AssetEntryInternal): Promise< function resolve_path(asset: AssetEntry, sourcePrefix: string): string { mono_assert(sourcePrefix !== null && sourcePrefix !== undefined, () => `sourcePrefix must be provided for ${asset.name}`); let attemptUrl; - const assemblyRootFolder = loaderHelpers.config.assemblyRootFolder; if (!asset.resolvedUrl) { if (sourcePrefix === "") { if (asset.behavior === "assembly" || asset.behavior === "pdb") { - attemptUrl = assemblyRootFolder - ? (assemblyRootFolder + "/" + asset.name) - : asset.name; + attemptUrl = asset.name; } else if (asset.behavior === "resource") { const path = asset.culture && asset.culture !== "" ? `${asset.culture}/${asset.name}` : asset.name; - attemptUrl = assemblyRootFolder - ? (assemblyRootFolder + "/" + path) - : path; + attemptUrl = path; } else { attemptUrl = asset.name; diff --git a/src/mono/wasm/runtime/snapshot.ts b/src/mono/wasm/runtime/snapshot.ts index 7594f4aff3f1ff..23bf16e7086f67 100644 --- a/src/mono/wasm/runtime/snapshot.ts +++ b/src/mono/wasm/runtime/snapshot.ts @@ -160,7 +160,6 @@ async function getCacheKey(): Promise { delete inputs.logExitCode; delete inputs.pthreadPoolSize; delete inputs.asyncFlushOnExit; - delete inputs.assemblyRootFolder; delete inputs.remoteSources; delete inputs.ignorePdbLoadErrors; delete inputs.maxParallelDownloads; diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 44c0e1f0e415a8..0dcce016a7982f 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -28,10 +28,6 @@ export interface DotnetHostBuilder { // when adding new fields, please consider if it should be impacting the snapshot hash. If not, please drop it in the snapshot getCacheKey() export type MonoConfig = { - /** - * The subfolder containing managed assemblies and pdbs. This is relative to dotnet.js script. - */ - assemblyRootFolder?: string, /** * Additional search locations for assets. */ From 19a05d5d612e9b61165f09fb481c437edcab10a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 11:07:34 +0200 Subject: [PATCH 31/65] Read js-modules and wasm from resources --- src/mono/wasm/runtime/loader/assets.ts | 37 ++++++++++++++++- .../runtime/loader/blazor/_Integration.ts | 40 ------------------- 2 files changed, 36 insertions(+), 41 deletions(-) diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index 299969d7c97b94..258a062a111ef6 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { AssetEntryInternal, PromiseAndController } from "../types/internal"; -import type { AssetBehaviours, AssetEntry, LoadingResource, ResourceRequest } from "../types"; +import type { AssetBehaviours, AssetEntry, LoadingResource, ResourceList, ResourceRequest } from "../types"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { createPromiseController } from "./promise-controller"; import { mono_log_debug } from "./logging"; @@ -64,7 +64,42 @@ export function shouldLoadIcuAsset(asset: AssetEntryInternal): boolean { return !(asset.behavior == "icu" && asset.name != loaderHelpers.preferredIcuAsset); } +function getFirstKey(resources: ResourceList | undefined): string | null { + if (resources != null) { + for (const name in resources) { + return name; + } + } + + return null; +} + +function createAssetWithResolvedUrl(resources: ResourceList | undefined, behavior: AssetBehaviours) { + const name = getFirstKey(resources); + mono_assert(name, `Can't find ${behavior} in resources`); + return { + name, + resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), behavior), + hash: resources![name], + behavior + }; +} + export function resolve_asset_path(behavior: AssetBehaviours): AssetEntryInternal { + const nativeResources = loaderHelpers.config.resources?.native; + if (nativeResources) { + switch (behavior) { + case "dotnetwasm": + return createAssetWithResolvedUrl(nativeResources.wasmNative, behavior); + case "js-module-threads": + return createAssetWithResolvedUrl(nativeResources.jsModuleWorker, behavior); + case "js-module-native": + return createAssetWithResolvedUrl(nativeResources.jsModuleNative, behavior); + case "js-module-runtime": + return createAssetWithResolvedUrl(nativeResources.jsModuleRuntime, behavior); + } + } + const asset: AssetEntryInternal | undefined = loaderHelpers.config.assets?.find(a => a.behavior == behavior); mono_assert(asset, () => `Can't find asset for ${behavior}`); if (!asset.resolvedUrl) { diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 1586ce6d492da3..5986fae1f96d78 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -40,46 +40,6 @@ export function mapResourcesToAssets(loadedConfig: MonoConfig) { } } - for (const name in nativeResources.jsModuleWorker) { - const behavior = "js-module-threads"; - assets.push({ - name, - resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), behavior), - hash: nativeResources.jsModuleWorker[name], - behavior - }); - } - - for (const name in nativeResources.jsModuleNative) { - const behavior = "js-module-native"; - assets.push({ - name, - resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), behavior), - hash: nativeResources.jsModuleNative[name], - behavior - }); - } - - for (const name in nativeResources.jsModuleRuntime) { - const behavior = "js-module-runtime"; - assets.push({ - name, - resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), behavior), - hash: nativeResources.jsModuleRuntime[name], - behavior - }); - } - - for (const name in nativeResources.wasmNative) { - const behavior = "dotnetwasm"; - assets.push({ - name, - resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), behavior), - hash: nativeResources.wasmNative[name], - behavior - }); - } - for (const name in nativeResources.symbols) { const behavior = "symbols"; assets.push({ From 06e2de9fdc3bef8e40d8193938b88adbd80d5fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 12:16:00 +0200 Subject: [PATCH 32/65] Read boot json assets in assets.ts --- src/mono/wasm/runtime/loader/assets.ts | 151 +++++++++++--- .../runtime/loader/blazor/_Integration.ts | 186 ------------------ src/mono/wasm/runtime/loader/config.ts | 21 +- src/mono/wasm/runtime/loader/icu.ts | 62 +++--- 4 files changed, 174 insertions(+), 246 deletions(-) delete mode 100644 src/mono/wasm/runtime/loader/blazor/_Integration.ts diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index 258a062a111ef6..83b7448b50ca11 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -8,6 +8,7 @@ import { createPromiseController } from "./promise-controller"; import { mono_log_debug } from "./logging"; import { mono_exit } from "./exit"; import { loadResource } from "./resourceLoader"; +import { getIcuResourceName } from "./icu"; let throttlingPromise: PromiseAndController | undefined; @@ -74,15 +75,18 @@ function getFirstKey(resources: ResourceList | undefined): string | null { return null; } -function createAssetWithResolvedUrl(resources: ResourceList | undefined, behavior: AssetBehaviours) { +function getFirstAssetWithResolvedUrl(resources: ResourceList | undefined, behavior: AssetBehaviours) { const name = getFirstKey(resources); mono_assert(name, `Can't find ${behavior} in resources`); - return { - name, - resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), behavior), - hash: resources![name], - behavior - }; + return ensureAssetResolvedUrl({ name, hash: resources![name], behavior }); +} + +function ensureAssetResolvedUrl(asset: AssetEntry): AssetEntry { + if (!asset.resolvedUrl) { + asset.resolvedUrl = appendUniqueQuery(loaderHelpers.locateFile(asset.name), asset.behavior); + } + + return asset; } export function resolve_asset_path(behavior: AssetBehaviours): AssetEntryInternal { @@ -90,13 +94,13 @@ export function resolve_asset_path(behavior: AssetBehaviours): AssetEntryInterna if (nativeResources) { switch (behavior) { case "dotnetwasm": - return createAssetWithResolvedUrl(nativeResources.wasmNative, behavior); + return getFirstAssetWithResolvedUrl(nativeResources.wasmNative, behavior); case "js-module-threads": - return createAssetWithResolvedUrl(nativeResources.jsModuleWorker, behavior); + return getFirstAssetWithResolvedUrl(nativeResources.jsModuleWorker, behavior); case "js-module-native": - return createAssetWithResolvedUrl(nativeResources.jsModuleNative, behavior); + return getFirstAssetWithResolvedUrl(nativeResources.jsModuleNative, behavior); case "js-module-runtime": - return createAssetWithResolvedUrl(nativeResources.jsModuleRuntime, behavior); + return getFirstAssetWithResolvedUrl(nativeResources.jsModuleRuntime, behavior); } } @@ -116,20 +120,7 @@ export async function mono_download_assets(): Promise { const containedInSnapshotAssets: AssetEntryInternal[] = []; const promises_of_assets: Promise[] = []; - for (const a of loaderHelpers.config.assets!) { - const asset: AssetEntryInternal = a; - mono_assert(typeof asset === "object", "asset must be object"); - mono_assert(typeof asset.behavior === "string", "asset behavior must be known string"); - mono_assert(typeof asset.name === "string", "asset name must be string"); - mono_assert(!asset.resolvedUrl || typeof asset.resolvedUrl === "string", "asset resolvedUrl could be string"); - mono_assert(!asset.hash || typeof asset.hash === "string", "asset resolvedUrl could be string"); - mono_assert(!asset.pendingDownload || typeof asset.pendingDownload === "object", "asset pendingDownload could be object"); - if (containedInSnapshotByAssetTypes[asset.behavior]) { - containedInSnapshotAssets.push(asset); - } else { - alwaysLoadedAssets.push(asset); - } - } + prepareAssets(containedInSnapshotAssets, alwaysLoadedAssets); const countAndStartDownload = (asset: AssetEntryInternal) => { if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { @@ -233,6 +224,108 @@ export async function mono_download_assets(): Promise { } } +function prepareAssets(containedInSnapshotAssets: AssetEntryInternal[], alwaysLoadedAssets: AssetEntryInternal[]) { + const config = loaderHelpers.config; + const resources = loaderHelpers.config.resources; + if (resources) { + for (const name in resources.assembly) { + containedInSnapshotAssets.push(ensureAssetResolvedUrl({ + name, + hash: resources.assembly[name], + behavior: "assembly" + })); + } + + if (config.debugLevel != 0 && resources.pdb) { + for (const name in resources.pdb) { + containedInSnapshotAssets.push(ensureAssetResolvedUrl({ + name, + hash: resources.pdb[name], + behavior: "pdb" + })); + } + } + + if (config.loadAllSatelliteResources && resources.satelliteResources) { + for (const culture in resources.satelliteResources) { + for (const name in resources.satelliteResources[culture]) { + containedInSnapshotAssets.push(ensureAssetResolvedUrl({ + name, + hash: resources.satelliteResources[culture][name], + behavior: "resource", + culture + })); + } + } + } + + if (resources.vfs) { + for (const virtualPath in resources.vfs) { + for (const name in resources.vfs[virtualPath]) { + alwaysLoadedAssets.push(ensureAssetResolvedUrl({ + name, + hash: resources.vfs[virtualPath][name], + behavior: "vfs", + virtualPath + })); + } + } + } + + const nativeResources = resources.native; + + const icuDataResourceName = getIcuResourceName(config); + if (icuDataResourceName && nativeResources?.icu) { + containedInSnapshotAssets.push(ensureAssetResolvedUrl({ + name: icuDataResourceName, + hash: nativeResources.icu[icuDataResourceName], + behavior: "icu", + loadRemote: true + })); + } + + if (nativeResources?.symbols) { + for (const name in nativeResources.symbols) { + alwaysLoadedAssets.push(ensureAssetResolvedUrl({ + name, + hash: nativeResources.symbols[name], + behavior: "symbols" + })); + } + } + } + + if (config.config) { + for (let i = 0; i < config.config.length; i++) { + const configUrl = config.config[i]; + const configFileName = fileName(configUrl); + if (configFileName === "appsettings.json" || configFileName === `appsettings.${config.applicationEnvironment}.json`) { + alwaysLoadedAssets.push(ensureAssetResolvedUrl({ + name: configFileName, + behavior: "vfs" + })); + } + } + } + + if (loaderHelpers.config.assets) { + for (const a of loaderHelpers.config.assets) { + const asset: AssetEntryInternal = a; + mono_assert(typeof asset === "object", "asset must be object"); + mono_assert(typeof asset.behavior === "string", "asset behavior must be known string"); + mono_assert(typeof asset.name === "string", "asset name must be string"); + mono_assert(!asset.resolvedUrl || typeof asset.resolvedUrl === "string", "asset resolvedUrl could be string"); + mono_assert(!asset.hash || typeof asset.hash === "string", "asset resolvedUrl could be string"); + mono_assert(!asset.pendingDownload || typeof asset.pendingDownload === "object", "asset pendingDownload could be object"); + if (containedInSnapshotByAssetTypes[asset.behavior]) { + containedInSnapshotAssets.push(asset); + } else { + alwaysLoadedAssets.push(asset); + } + } + } +} + export function delay(ms: number): Promise { return new Promise(resolve => globalThis.setTimeout(resolve, ms)); } @@ -457,3 +550,11 @@ export function cleanupAsset(asset: AssetEntryInternal) { asset.pendingDownload = null as any; // GC asset.buffer = null as any; // GC } + +function fileName(name: string) { + let lastIndexOfSlash = name.lastIndexOf("/"); + if (lastIndexOfSlash >= 0) { + lastIndexOfSlash++; + } + return name.substring(lastIndexOfSlash); +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts deleted file mode 100644 index 5986fae1f96d78..00000000000000 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ /dev/null @@ -1,186 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import { GlobalizationMode, type AssetEntry, MonoConfig } from "../../types"; - -import { ENVIRONMENT_IS_WEB, loaderHelpers, mono_assert } from "../globals"; -import { appendUniqueQuery } from "../assets"; - -export function mapResourcesToAssets(loadedConfig: MonoConfig) { - mono_assert(loadedConfig.resources, "Loaded config does not contain resources"); - - const config = loaderHelpers.config; - const resources = loadedConfig.resources; - const nativeResources = resources.native!; - - if (!config.assets) { - config.assets = []; - } - - const assets = config.assets; - - for (const name in resources.assembly) { - const asset: AssetEntry = { - name, - resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), "assembly"), - hash: resources.assembly[name], - behavior: "assembly", - }; - assets.push(asset); - } - if (config.debugLevel != 0 && resources.pdb) { - for (const name in resources.pdb) { - const asset: AssetEntry = { - name, - resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), "pdb"), - hash: resources.pdb[name], - behavior: "pdb", - }; - assets.push(asset); - } - } - - for (const name in nativeResources.symbols) { - const behavior = "symbols"; - assets.push({ - name, - resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), behavior), - hash: nativeResources.symbols[name], - behavior - }); - } - - const applicationCulture = config.applicationCulture || (ENVIRONMENT_IS_WEB ? (navigator.languages && navigator.languages[0]) : Intl.DateTimeFormat().resolvedOptions().locale); - const icuDataResourceName = getICUResourceName(loadedConfig, config, applicationCulture); - let hasIcuData = false; - if (icuDataResourceName != null) { - const behavior = "icu"; - hasIcuData = true; - assets.push({ - name: icuDataResourceName, - resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(icuDataResourceName), behavior), - hash: nativeResources.icu[icuDataResourceName], - behavior, - loadRemote: true - }); - } - - if (config.loadAllSatelliteResources && resources.satelliteResources) { - for (const culture in resources.satelliteResources) { - for (const name in resources.satelliteResources[culture]) { - assets.push({ - name, - culture, - behavior: "resource", - hash: resources.satelliteResources[culture][name], - }); - } - } - } - - if (loadedConfig.config) { - for (let i = 0; i < loadedConfig.config.length; i++) { - const configUrl = loadedConfig.config[i]; - const configFileName = fileName(configUrl); - if (configFileName === "appsettings.json" || configFileName === `appsettings.${config.applicationEnvironment}.json`) { - assets.push({ - name: configFileName, - resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(configUrl), "vfs"), - behavior: "vfs", - }); - } - } - } - - if (resources.vfs) { - for (const virtualPath in resources.vfs) { - for (const name in resources.vfs[virtualPath]) { - const asset: AssetEntry = { - name, - resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), "vfs"), - hash: resources.vfs[virtualPath][name], - behavior: "vfs", - virtualPath - }; - assets.push(asset); - } - } - } - - if (!hasIcuData) { - config.globalizationMode = GlobalizationMode.Invariant; - } -} - -function fileName(name: string) { - let lastIndexOfSlash = name.lastIndexOf("/"); - if (lastIndexOfSlash >= 0) { - lastIndexOfSlash++; - } - return name.substring(lastIndexOfSlash); -} - -function getICUResourceName(loadedConfig: MonoConfig, config: MonoConfig, culture: string | undefined): string | null { - if (!loadedConfig.resources?.native?.icu) { - config.globalizationMode = GlobalizationMode.Invariant; - return null; - } - - const icuFiles = Object.keys(loadedConfig.resources.native.icu); - - if (loadedConfig.globalizationMode === GlobalizationMode.Custom) { - if (icuFiles.length === 1) { - config.globalizationMode = GlobalizationMode.Custom; - const customIcuFile = icuFiles[0]; - return customIcuFile; - } - } - - const icuFile = getICUResourceNameForCulture(loadedConfig, config, culture); - if (icuFile && icuFiles.includes(icuFile)) { - return icuFile; - } - - config.globalizationMode = GlobalizationMode.Invariant; - return null; -} - -function getICUResourceNameForCulture(loadedConfig: MonoConfig, config: MonoConfig, culture: string | undefined): string | null { - if (loadedConfig.globalizationMode === GlobalizationMode.Hybrid) { - config.globalizationMode = GlobalizationMode.Hybrid; - const reducedICUResourceName = "icudt_hybrid.dat"; - return reducedICUResourceName; - } - - if (!culture || loadedConfig.globalizationMode === GlobalizationMode.All) { - config.globalizationMode = GlobalizationMode.All; - const combinedICUResourceName = "icudt.dat"; - return combinedICUResourceName; - } - - config.globalizationMode = GlobalizationMode.Sharded; - const prefix = culture.split("-")[0]; - if (prefix === "en" || - [ - "fr", - "fr-FR", - "it", - "it-IT", - "de", - "de-DE", - "es", - "es-ES", - ].includes(culture)) { - return "icudt_EFIGS.dat"; - } - - if ([ - "zh", - "ko", - "ja", - ].includes(prefix)) { - return "icudt_CJK.dat"; - } - - return "icudt_no_CJK.dat"; -} \ No newline at end of file diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index 9a58c0c3f4104d..fba13b94872735 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -5,7 +5,6 @@ import BuildConfiguration from "consts:configuration"; import type { DotnetModuleInternal, MonoConfigInternal } from "../types/internal"; import type { DotnetModuleConfig, MonoConfig } from "../types"; import { ENVIRONMENT_IS_WEB, exportedRuntimeAPI, loaderHelpers, runtimeHelpers } from "./globals"; -import { mapResourcesToAssets } from "./blazor/_Integration"; import { mono_log_error, mono_log_debug } from "./logging"; import { invokeLibraryInitializers } from "./libraryInitializers"; import { mono_exit } from "./exit"; @@ -146,11 +145,8 @@ async function loadBootConfig(module: DotnetModuleInternal): Promise { loadConfigResponse = await loaderResponse; } - const loadedConfig: MonoConfig = await loadConfigResponse.json(); - - readBootConfigResponseHeaders(loadConfigResponse); + const loadedConfig: MonoConfig = await readBootConfigResponse(loadConfigResponse); deep_merge_config(loaderHelpers.config, loadedConfig); - mapResourcesToAssets(loadedConfig); function defaultLoadBootConfig(url: string): Promise { return loaderHelpers.fetch_like(url, { @@ -161,25 +157,28 @@ async function loadBootConfig(module: DotnetModuleInternal): Promise { } } -function readBootConfigResponseHeaders(loadConfigResponse: Response) { +async function readBootConfigResponse(loadConfigResponse: Response): Promise { const config = loaderHelpers.config; + const loadedConfig: MonoConfig = await loadConfigResponse.json(); if (!config.applicationEnvironment) { - config.applicationEnvironment = loadConfigResponse.headers.get("Blazor-Environment") || loadConfigResponse.headers.get("DotNet-Environment") || "Production"; + loadedConfig.applicationEnvironment = loadConfigResponse.headers.get("Blazor-Environment") || loadConfigResponse.headers.get("DotNet-Environment") || "Production"; } - if (!config.environmentVariables) - config.environmentVariables = {}; + if (!loadedConfig.environmentVariables) + loadedConfig.environmentVariables = {}; const modifiableAssemblies = loadConfigResponse.headers.get("DOTNET-MODIFIABLE-ASSEMBLIES"); if (modifiableAssemblies) { // Configure the app to enable hot reload in Development. - config.environmentVariables["DOTNET_MODIFIABLE_ASSEMBLIES"] = modifiableAssemblies; + loadedConfig.environmentVariables["DOTNET_MODIFIABLE_ASSEMBLIES"] = modifiableAssemblies; } const aspnetCoreBrowserTools = loadConfigResponse.headers.get("ASPNETCORE-BROWSER-TOOLS"); if (aspnetCoreBrowserTools) { // See https://github.com/dotnet/aspnetcore/issues/37357#issuecomment-941237000 - config.environmentVariables["__ASPNETCORE_BROWSER_TOOLS"] = aspnetCoreBrowserTools; + loadedConfig.environmentVariables["__ASPNETCORE_BROWSER_TOOLS"] = aspnetCoreBrowserTools; } + + return loadedConfig; } \ No newline at end of file diff --git a/src/mono/wasm/runtime/loader/icu.ts b/src/mono/wasm/runtime/loader/icu.ts index bffc1e3dd13460..a4ebe2ee648da8 100644 --- a/src/mono/wasm/runtime/loader/icu.ts +++ b/src/mono/wasm/runtime/loader/icu.ts @@ -1,13 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { GlobalizationMode } from "../types"; +import { GlobalizationMode, MonoConfig } from "../types"; import { ENVIRONMENT_IS_WEB, loaderHelpers } from "./globals"; import { mono_log_info, mono_log_debug } from "./logging"; export function init_globalization() { + loaderHelpers.preferredIcuAsset = getIcuResourceName(loaderHelpers.config); loaderHelpers.invariantMode = loaderHelpers.config.globalizationMode == GlobalizationMode.Invariant; - loaderHelpers.preferredIcuAsset = get_preferred_icu_asset(); if (!loaderHelpers.invariantMode) { if (loaderHelpers.preferredIcuAsset) { @@ -45,29 +45,43 @@ export function init_globalization() { } } -export function get_preferred_icu_asset(): string | null { - if (!loaderHelpers.config.assets || loaderHelpers.invariantMode) - return null; +export function getIcuResourceName(config: MonoConfig): string | null { + if (config.resources?.native?.icu && config.globalizationMode != GlobalizationMode.Invariant) { + const culture = config.applicationCulture || (ENVIRONMENT_IS_WEB ? (navigator.languages && navigator.languages[0]) : Intl.DateTimeFormat().resolvedOptions().locale); - // By setting user can define what ICU source file they want to load. - // There is no need to check application's culture when is set. - // If it was not set, then we have 3 "icu" assets in config and we should choose - // only one for loading, the one that matches the application's locale. - const icuAssets = loaderHelpers.config.assets.filter(a => a["behavior"] == "icu"); - if (icuAssets.length === 1) - return icuAssets[0].name; + const icuFiles = Object.keys(config.resources.native.icu); - // reads the browsers locale / the OS's locale - const preferredCulture = ENVIRONMENT_IS_WEB ? navigator.language : Intl.DateTimeFormat().resolvedOptions().locale; - const prefix = preferredCulture.split("-")[0]; - const CJK = "icudt_CJK.dat"; - const EFIGS = "icudt_EFIGS.dat"; - const OTHERS = "icudt_no_CJK.dat"; + let icuFile = null; + if (config.globalizationMode === GlobalizationMode.Custom) { + if (icuFiles.length === 1) { + icuFile = icuFiles[0]; + } + } else if (config.globalizationMode === GlobalizationMode.Hybrid) { + icuFile = "icudt_hybrid.dat"; + } else if (!culture || config.globalizationMode === GlobalizationMode.All) { + icuFile = "icudt.dat"; + } else if (config.globalizationMode === GlobalizationMode.Sharded) { + icuFile = getShardedIcuResourceName(culture); + } + + if (icuFile && icuFiles.includes(icuFile)) { + return icuFile; + } + } + + config.globalizationMode = GlobalizationMode.Invariant; + return null; +} + +function getShardedIcuResourceName(culture: string): string { + const prefix = culture.split("-")[0]; + if (prefix === "en" || ["fr", "fr-FR", "it", "it-IT", "de", "de-DE", "es", "es-ES"].includes(culture)) { + return "icudt_EFIGS.dat"; + } + + if (["zh", "ko", "ja"].includes(prefix)) { + return "icudt_CJK.dat"; + } - // not all "fr-*", "it-*", "de-*", "es-*" are in EFIGS, only the one that is mostly used - if (prefix == "en" || ["fr", "fr-FR", "it", "it-IT", "de", "de-DE", "es", "es-ES"].includes(preferredCulture)) - return EFIGS; - if (["zh", "ko", "ja"].includes(prefix)) - return CJK; - return OTHERS; + return "icudt_no_CJK.dat"; } From fd3afb55e8eeb3950fa85efd399250cf1b1a823f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 12:22:24 +0200 Subject: [PATCH 33/65] Fix wait_for_all_assets --- src/mono/wasm/runtime/loader/assets.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index 83b7448b50ca11..80e79d2a9da3c5 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -78,7 +78,11 @@ function getFirstKey(resources: ResourceList | undefined): string | null { function getFirstAssetWithResolvedUrl(resources: ResourceList | undefined, behavior: AssetBehaviours) { const name = getFirstKey(resources); mono_assert(name, `Can't find ${behavior} in resources`); - return ensureAssetResolvedUrl({ name, hash: resources![name], behavior }); + return ensureAssetResolvedUrl({ + name, + hash: resources![name], + behavior + }); } function ensureAssetResolvedUrl(asset: AssetEntry): AssetEntry { @@ -308,6 +312,8 @@ function prepareAssets(containedInSnapshotAssets: AssetEntryInternal[], alwaysLo } } + const newAssets = [...containedInSnapshotAssets, ...alwaysLoadedAssets]; + if (loaderHelpers.config.assets) { for (const a of loaderHelpers.config.assets) { const asset: AssetEntryInternal = a; @@ -324,6 +330,13 @@ function prepareAssets(containedInSnapshotAssets: AssetEntryInternal[], alwaysLo } } } + + if (!loaderHelpers.config.assets) { + loaderHelpers.config.assets = []; + } + + loaderHelpers.config.assets = [...loaderHelpers.config.assets, ...newAssets]; + } export function delay(ms: number): Promise { From ea3daec7b379e7ef846c7bb2ae38ffff5a1b597f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 12:30:20 +0200 Subject: [PATCH 34/65] Split AssetBehaviors for single and all --- src/mono/wasm/runtime/dotnet.d.ts | 41 ++++++++--------- src/mono/wasm/runtime/loader/assets.ts | 39 +++++++--------- .../wasm/runtime/loader/resourceLoader.ts | 12 ++--- src/mono/wasm/runtime/types/index.ts | 44 +++++++++---------- src/mono/wasm/runtime/types/internal.ts | 8 ++-- 5 files changed, 66 insertions(+), 78 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 0e159d4c35aa1e..60b563e2b7be1a 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -214,7 +214,7 @@ type ResourceList = { type LoadBootResourceCallback = (type: WebAssemblyBootResourceType, name: string, defaultUri: string, integrity: string) => string | Promise | null | undefined; interface ResourceRequest { name: string; - behavior: AssetBehaviours; + behavior: AssetBehaviors; resolvedUrl?: string; hash?: string; } @@ -251,7 +251,24 @@ interface AssetEntry extends ResourceRequest { */ pendingDownload?: LoadingResource; } -type AssetBehaviours = +type SingleAssetBehaviors = +/** + * The binary of the dotnet runtime. + */ +"dotnetwasm" +/** + * The javascript module for threads. + */ + | "js-module-threads" +/** + * The javascript module for threads. + */ + | "js-module-runtime" +/** + * The javascript module for threads. + */ + | "js-module-native"; +type AssetBehaviors = SingleAssetBehaviors | /** * Load asset as a managed resource assembly. */ @@ -276,26 +293,6 @@ type AssetBehaviours = * Load asset into the virtual filesystem (for fopen, File.Open, etc). */ | "vfs" -/** - * The binary of the dotnet runtime. - */ - | "dotnetwasm" -/** - * The javascript module for threads. - */ - | "js-module-threads" -/** - * The javascript module for threads. - */ - | "js-module-runtime" -/** - * The javascript module for threads. - */ - | "js-module-dotnet" -/** - * The javascript module for threads. - */ - | "js-module-native" /** * The javascript module that came from nuget package . */ diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index 80e79d2a9da3c5..53bdd23cbce441 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { AssetEntryInternal, PromiseAndController } from "../types/internal"; -import type { AssetBehaviours, AssetEntry, LoadingResource, ResourceList, ResourceRequest } from "../types"; +import type { AssetBehaviors, AssetEntry, LoadingResource, ResourceList, ResourceRequest, SingleAssetBehaviors as SingleAssetBehaviors } from "../types"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { createPromiseController } from "./promise-controller"; import { mono_log_debug } from "./logging"; @@ -21,7 +21,6 @@ const jsModulesAssetTypes: { "js-module-threads": true, "js-module-runtime": true, "js-module-native": true, - "js-module-dotnet": true, }; // don't `fetch` javaScript and wasm files @@ -75,7 +74,7 @@ function getFirstKey(resources: ResourceList | undefined): string | null { return null; } -function getFirstAssetWithResolvedUrl(resources: ResourceList | undefined, behavior: AssetBehaviours) { +function getFirstAssetWithResolvedUrl(resources: ResourceList | undefined, behavior: AssetBehaviors) { const name = getFirstKey(resources); mono_assert(name, `Can't find ${behavior} in resources`); return ensureAssetResolvedUrl({ @@ -93,28 +92,22 @@ function ensureAssetResolvedUrl(asset: AssetEntry): AssetEntry { return asset; } -export function resolve_asset_path(behavior: AssetBehaviours): AssetEntryInternal { +export function resolve_asset_path(behavior: SingleAssetBehaviors): AssetEntryInternal { const nativeResources = loaderHelpers.config.resources?.native; - if (nativeResources) { - switch (behavior) { - case "dotnetwasm": - return getFirstAssetWithResolvedUrl(nativeResources.wasmNative, behavior); - case "js-module-threads": - return getFirstAssetWithResolvedUrl(nativeResources.jsModuleWorker, behavior); - case "js-module-native": - return getFirstAssetWithResolvedUrl(nativeResources.jsModuleNative, behavior); - case "js-module-runtime": - return getFirstAssetWithResolvedUrl(nativeResources.jsModuleRuntime, behavior); - } - } - - const asset: AssetEntryInternal | undefined = loaderHelpers.config.assets?.find(a => a.behavior == behavior); - mono_assert(asset, () => `Can't find asset for ${behavior}`); - if (!asset.resolvedUrl) { - asset.resolvedUrl = resolve_path(asset, ""); + mono_assert(nativeResources, () => "Can't find native resources in config"); + + switch (behavior) { + case "dotnetwasm": + return getFirstAssetWithResolvedUrl(nativeResources.wasmNative, behavior); + case "js-module-threads": + return getFirstAssetWithResolvedUrl(nativeResources.jsModuleWorker, behavior); + case "js-module-native": + return getFirstAssetWithResolvedUrl(nativeResources.jsModuleNative, behavior); + case "js-module-runtime": + return getFirstAssetWithResolvedUrl(nativeResources.jsModuleRuntime, behavior); } - return asset; } + export async function mono_download_assets(): Promise { mono_log_debug("mono_download_assets"); loaderHelpers.maxParallelDownloads = loaderHelpers.config.maxParallelDownloads || loaderHelpers.maxParallelDownloads; @@ -519,7 +512,7 @@ function resolve_path(asset: AssetEntry, sourcePrefix: string): string { return attemptUrl; } -export function appendUniqueQuery(attemptUrl: string, behavior: AssetBehaviours): string { +export function appendUniqueQuery(attemptUrl: string, behavior: AssetBehaviors): string { // apply unique query to js modules to make the module state independent of the other runtime instances if (loaderHelpers.modulesUniqueQuery && jsModulesAssetTypes[behavior]) { attemptUrl = attemptUrl + loaderHelpers.modulesUniqueQuery; diff --git a/src/mono/wasm/runtime/loader/resourceLoader.ts b/src/mono/wasm/runtime/loader/resourceLoader.ts index b672df5fdb5581..21e9d6c685f8c5 100644 --- a/src/mono/wasm/runtime/loader/resourceLoader.ts +++ b/src/mono/wasm/runtime/loader/resourceLoader.ts @@ -1,11 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { AssetBehaviours, MonoConfig, ResourceList, WebAssemblyBootResourceType } from "../types"; +import type { AssetBehaviors, MonoConfig, ResourceList, WebAssemblyBootResourceType } from "../types"; import { loaderHelpers } from "./globals"; const networkFetchCacheMode = "no-cache"; -const cacheSkipResourceTypes: AssetBehaviours[] = ["vfs"]; // Previously on configuration +const cacheSkipResourceTypes: AssetBehaviors[] = ["vfs"]; // Previously on configuration const usedCacheKeys: { [key: string]: boolean } = {}; const networkLoads: { [name: string]: LoadLogEntry } = {}; const cacheLoads: { [name: string]: LoadLogEntry } = {}; @@ -20,12 +20,12 @@ const monoToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType | u "dotnetwasm": "dotnetwasm", }; -export function loadResources(resources: ResourceList, url: (name: string) => string, behavior: AssetBehaviours): LoadingResource[] { +export function loadResources(resources: ResourceList, url: (name: string) => string, behavior: AssetBehaviors): LoadingResource[] { return Object.keys(resources) .map(name => loadResource(name, url(name), resources[name], behavior)); } -export function loadResource(name: string, url: string, contentHash: string, behavior: AssetBehaviours): LoadingResource { +export function loadResource(name: string, url: string, contentHash: string, behavior: AssetBehaviors): LoadingResource { const response = cacheIfUsed && !cacheSkipResourceTypes.includes(behavior) ? loadResourceWithCaching(cacheIfUsed, name, url, contentHash, behavior) : loadResourceWithoutCaching(name, url, contentHash, behavior); @@ -91,7 +91,7 @@ export async function purgeUnusedCacheEntriesAsync(): Promise { } } -async function loadResourceWithCaching(cache: Cache, name: string, url: string, contentHash: string, behavior: AssetBehaviours) { +async function loadResourceWithCaching(cache: Cache, name: string, url: string, contentHash: string, behavior: AssetBehaviors) { // Since we are going to cache the response, we require there to be a content hash for integrity // checking. We don't want to cache bad responses. There should always be a hash, because the build // process generates this data. @@ -123,7 +123,7 @@ async function loadResourceWithCaching(cache: Cache, name: string, url: string, } } -function loadResourceWithoutCaching(name: string, url: string, contentHash: string, behavior: AssetBehaviours): Promise { +function loadResourceWithoutCaching(name: string, url: string, contentHash: string, behavior: AssetBehaviors): Promise { // Allow developers to override how the resource is loaded if (loaderHelpers.loadBootResource) { const resourceType = monoToBlazorAssetTypeMap[behavior]; diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 0dcce016a7982f..4e5769141c3cf1 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -148,7 +148,7 @@ export type LoadBootResourceCallback = (type: WebAssemblyBootResourceType, name: export interface ResourceRequest { name: string, // the name of the asset, including extension. - behavior: AssetBehaviours, // determines how the asset will be handled once loaded + behavior: AssetBehaviors, // determines how the asset will be handled once loaded resolvedUrl?: string; // this should be absolute url to the asset hash?: string; } @@ -189,7 +189,25 @@ export interface AssetEntry extends ResourceRequest { pendingDownload?: LoadingResource } -export type AssetBehaviours = +export type SingleAssetBehaviors = + /** + * The binary of the dotnet runtime. + */ + | "dotnetwasm" + /** + * The javascript module for threads. + */ + | "js-module-threads" + /** + * The javascript module for threads. + */ + | "js-module-runtime" + /** + * The javascript module for threads. + */ + | "js-module-native"; + +export type AssetBehaviors = SingleAssetBehaviors | /** * Load asset as a managed resource assembly. */ @@ -214,26 +232,6 @@ export type AssetBehaviours = * Load asset into the virtual filesystem (for fopen, File.Open, etc). */ | "vfs" - /** - * The binary of the dotnet runtime. - */ - | "dotnetwasm" - /** - * The javascript module for threads. - */ - | "js-module-threads" - /** - * The javascript module for threads. - */ - | "js-module-runtime" - /** - * The javascript module for threads. - */ - | "js-module-dotnet" - /** - * The javascript module for threads. - */ - | "js-module-native" /** * The javascript module that came from nuget package . */ @@ -241,7 +239,7 @@ export type AssetBehaviours = /** * The javascript module for threads. */ - | "symbols" // + | "symbols" export const enum GlobalizationMode { /** diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 49c66b4495f859..b33bc3fe1b0290 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { AssetBehaviours, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, ResourceList, RuntimeAPI } from "."; +import type { AssetBehaviors, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, ResourceList, RuntimeAPI } from "."; import type { CharPtr, EmscriptenModule, ManagedPointer, NativePointer, VoidPtr, Int32Ptr } from "./emscripten"; export type GCHandle = { @@ -134,7 +134,7 @@ export type LoaderHelpers = { getPromiseController: (promise: ControllablePromise) => PromiseController, assertIsControllablePromise: (promise: Promise) => asserts promise is ControllablePromise, mono_download_assets: () => Promise, - resolve_asset_path: (behavior: AssetBehaviours) => AssetEntryInternal, + resolve_asset_path: (behavior: AssetBehaviors) => AssetEntryInternal, setup_proxy_console: (id: string, console: Console, origin: string) => void fetch_like: (url: string, init?: RequestInit) => Promise; locateFile: (path: string, prefix?: string) => string, @@ -142,8 +142,8 @@ export type LoaderHelpers = { err(message: string): void; hasDebuggingEnabled(config: MonoConfig): boolean, - loadResources(resources: ResourceList, url: (name: string) => string, behavior: AssetBehaviours): LoadingResource[], - loadResource(name: string, url: string, contentHash: string, behavior: AssetBehaviours): LoadingResource, + loadResources(resources: ResourceList, url: (name: string) => string, behavior: AssetBehaviors): LoadingResource[], + loadResource(name: string, url: string, contentHash: string, behavior: AssetBehaviors): LoadingResource, onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; loadBootResource?: LoadBootResourceCallback; From 4d687619afcfd1e26dc2a246213663b735c02d40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 14:06:28 +0200 Subject: [PATCH 35/65] Make hash optional (+ compact schema) --- src/mono/wasm/runtime/dotnet.d.ts | 30 ++--- src/mono/wasm/runtime/lazyLoading.ts | 15 ++- src/mono/wasm/runtime/loader/assets.ts | 107 +++++++++++++----- src/mono/wasm/runtime/loader/globals.ts | 8 +- .../wasm/runtime/loader/resourceLoader.ts | 7 +- src/mono/wasm/runtime/satelliteAssemblies.ts | 15 ++- src/mono/wasm/runtime/types/index.ts | 30 ++--- src/mono/wasm/runtime/types/internal.ts | 6 +- src/mono/wasm/test-main.js | 22 ++-- 9 files changed, 155 insertions(+), 85 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 60b563e2b7be1a..e2c57b633b4d7f 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -171,37 +171,39 @@ type MonoConfig = { }; }; type ResourceExtensions = { - [extensionName: string]: ResourceList; + [extensionName: string]: ResourceListOrArray; }; interface ResourceGroups { readonly hash?: string; - readonly assembly?: ResourceList; - readonly lazyAssembly?: ResourceList; - readonly pdb?: ResourceList; + readonly assembly?: ResourceListOrArray; + readonly lazyAssembly?: ResourceListOrArray; + readonly pdb?: ResourceListOrArray; readonly native?: NativeResources; readonly satelliteResources?: { - [cultureName: string]: ResourceList; + [cultureName: string]: ResourceListOrArray; }; readonly libraryStartupModules?: { - readonly onRuntimeConfigLoaded?: ResourceList; - readonly onRuntimeReady?: ResourceList; + readonly onRuntimeConfigLoaded?: ResourceListOrArray; + readonly onRuntimeReady?: ResourceListOrArray; }; readonly extensions?: ResourceExtensions; readonly vfs?: { - [virtualPath: string]: ResourceList; + [virtualPath: string]: ResourceListOrArray; }; } interface NativeResources { - readonly jsModuleWorker: ResourceList; - readonly jsModuleNative: ResourceList; - readonly jsModuleRuntime: ResourceList; - readonly wasmNative: ResourceList; - readonly symbols: ResourceList; - readonly icu: ResourceList; + readonly jsModuleWorker?: ResourceListOrString; + readonly jsModuleNative: ResourceListOrString; + readonly jsModuleRuntime: ResourceListOrString; + readonly wasmNative: ResourceListOrString; + readonly symbols?: ResourceListOrArray; + readonly icu?: ResourceListOrArray; } type ResourceList = { [name: string]: string; }; +type ResourceListOrString = ResourceList | string; +type ResourceListOrArray = ResourceList | string[]; /** * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched * from a custom source, such as an external CDN. diff --git a/src/mono/wasm/runtime/lazyLoading.ts b/src/mono/wasm/runtime/lazyLoading.ts index cfcaa8df18efed..451cfde9f214e9 100644 --- a/src/mono/wasm/runtime/lazyLoading.ts +++ b/src/mono/wasm/runtime/lazyLoading.ts @@ -10,8 +10,9 @@ export async function loadLazyAssembly(assemblyNameToLoad: string): Promise response.arrayBuffer()); + const dllBytesPromise = loaderHelpers.loadResource(dllNameToLoad, assemblyAsset.resolvedUrl!, assemblyAsset.hash ?? "", assemblyAsset.behavior).response.then(response => response.arrayBuffer()); let dll = null; let pdb = null; if (shouldLoadPdb) { - const pdbBytesPromise = await loaderHelpers.loadResource(pdbNameToLoad, loaderHelpers.locateFile(pdbNameToLoad), lazyAssemblies[pdbNameToLoad], "pdb").response.then(response => response.arrayBuffer()); + const pdbAsset = loaderHelpers.getAssetByNameWithResolvedUrl(lazyAssemblies, "pdb", assemblyNameToLoad); + const pdbBytesPromise = pdbAsset + ? await loaderHelpers.loadResource(pdbNameToLoad, pdbAsset.resolvedUrl!, pdbAsset.hash ?? "", pdbAsset.behavior).response.then(response => response.arrayBuffer()) + : Promise.resolve(null); + const [dllBytes, pdbBytes] = await Promise.all([dllBytesPromise, pdbBytesPromise]); dll = new Uint8Array(dllBytes); - pdb = new Uint8Array(pdbBytes); + pdb = pdbBytes ? new Uint8Array(pdbBytes) : null; } else { const dllBytes = await dllBytesPromise; dll = new Uint8Array(dllBytes); diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index 53bdd23cbce441..808047d1386081 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { AssetEntryInternal, PromiseAndController } from "../types/internal"; -import type { AssetBehaviors, AssetEntry, LoadingResource, ResourceList, ResourceRequest, SingleAssetBehaviors as SingleAssetBehaviors } from "../types"; +import type { AssetBehaviors, AssetEntry, LoadingResource, ResourceList, ResourceListOrArray, ResourceListOrString, ResourceRequest, SingleAssetBehaviors as SingleAssetBehaviors } from "../types"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { createPromiseController } from "./promise-controller"; import { mono_log_debug } from "./logging"; @@ -74,17 +74,43 @@ function getFirstKey(resources: ResourceList | undefined): string | null { return null; } -function getFirstAssetWithResolvedUrl(resources: ResourceList | undefined, behavior: AssetBehaviors) { - const name = getFirstKey(resources); +function getFirstAssetWithResolvedUrl(resources: ResourceListOrString | undefined, behavior: SingleAssetBehaviors): AssetEntry { + const isString = typeof (resources) === "string"; + const name = isString ? resources : getFirstKey(resources); mono_assert(name, `Can't find ${behavior} in resources`); return ensureAssetResolvedUrl({ name, - hash: resources![name], + hash: isString ? undefined : resources![name], behavior }); } -function ensureAssetResolvedUrl(asset: AssetEntry): AssetEntry { +export function getAssetByNameWithResolvedUrl(resources: ResourceListOrArray | undefined, behavior: AssetBehaviors, requestedName: string): AssetEntry | undefined { + if (!resources) { + return undefined; + } + + let foundName = undefined; + let foundHash = undefined; + enumerateResources(resources, (name, hash) => { + if (name == requestedName) { + foundName = name; + foundHash = hash; + } + }); + + if (!foundName) { + return undefined; + } + + return ensureAssetResolvedUrl({ + name: foundName, + hash: foundHash, + behavior + }); +} + +export function ensureAssetResolvedUrl(asset: AssetEntry): AssetEntry { if (!asset.resolvedUrl) { asset.resolvedUrl = appendUniqueQuery(loaderHelpers.locateFile(asset.name), asset.behavior); } @@ -92,6 +118,23 @@ function ensureAssetResolvedUrl(asset: AssetEntry): AssetEntry { return asset; } +export function enumerateResources(resources: ResourceListOrArray, itemHandler: (name: string, hash: string | undefined) => void): void { + const stringArray = resources as string[]; + if (stringArray) { + for (let i = 0; i < stringArray.length; i++) { + itemHandler(stringArray[i], undefined); + } + // for (const index in resources) { + // itemHandler(stringArray[index], undefined); + // } + } else { + const objectWithHashes = resources as ResourceList; + for (const name in objectWithHashes) { + itemHandler(name, objectWithHashes[name]); + } + } +} + export function resolve_asset_path(behavior: SingleAssetBehaviors): AssetEntryInternal { const nativeResources = loaderHelpers.config.resources?.native; mono_assert(nativeResources, () => "Can't find native resources in config"); @@ -225,47 +268,49 @@ function prepareAssets(containedInSnapshotAssets: AssetEntryInternal[], alwaysLo const config = loaderHelpers.config; const resources = loaderHelpers.config.resources; if (resources) { - for (const name in resources.assembly) { - containedInSnapshotAssets.push(ensureAssetResolvedUrl({ - name, - hash: resources.assembly[name], - behavior: "assembly" - })); + if (resources.assembly) { + enumerateResources(resources.assembly, (name, hash) => { + containedInSnapshotAssets.push(ensureAssetResolvedUrl({ + name, + hash, + behavior: "assembly" + })); + }); } if (config.debugLevel != 0 && resources.pdb) { - for (const name in resources.pdb) { + enumerateResources(resources.pdb, (name, hash) => { containedInSnapshotAssets.push(ensureAssetResolvedUrl({ name, - hash: resources.pdb[name], + hash, behavior: "pdb" })); - } + }); } if (config.loadAllSatelliteResources && resources.satelliteResources) { for (const culture in resources.satelliteResources) { - for (const name in resources.satelliteResources[culture]) { + enumerateResources(resources.satelliteResources[culture], (name, hash) => { containedInSnapshotAssets.push(ensureAssetResolvedUrl({ name, - hash: resources.satelliteResources[culture][name], + hash, behavior: "resource", culture })); - } + }); } } if (resources.vfs) { for (const virtualPath in resources.vfs) { - for (const name in resources.vfs[virtualPath]) { + enumerateResources(resources.vfs[virtualPath], (name, hash) => { alwaysLoadedAssets.push(ensureAssetResolvedUrl({ name, - hash: resources.vfs[virtualPath][name], + hash, behavior: "vfs", virtualPath })); - } + }); } } @@ -273,22 +318,26 @@ function prepareAssets(containedInSnapshotAssets: AssetEntryInternal[], alwaysLo const icuDataResourceName = getIcuResourceName(config); if (icuDataResourceName && nativeResources?.icu) { - containedInSnapshotAssets.push(ensureAssetResolvedUrl({ - name: icuDataResourceName, - hash: nativeResources.icu[icuDataResourceName], - behavior: "icu", - loadRemote: true - })); + enumerateResources(nativeResources.icu, (name, hash) => { + if (name === icuDataResourceName) { + containedInSnapshotAssets.push(ensureAssetResolvedUrl({ + name, + hash, + behavior: "icu", + loadRemote: true + })); + } + }); } if (nativeResources?.symbols) { - for (const name in nativeResources.symbols) { + enumerateResources(nativeResources.symbols, (name, hash) => { alwaysLoadedAssets.push(ensureAssetResolvedUrl({ name, - hash: nativeResources.symbols[name], + hash, behavior: "symbols" })); - } + }); } } diff --git a/src/mono/wasm/runtime/loader/globals.ts b/src/mono/wasm/runtime/loader/globals.ts index 70ae23e56f0b01..1005b5645c1a72 100644 --- a/src/mono/wasm/runtime/loader/globals.ts +++ b/src/mono/wasm/runtime/loader/globals.ts @@ -7,10 +7,10 @@ import type { AssetEntryInternal, GlobalObjects, LoaderHelpers, RuntimeHelpers } import type { MonoConfig, RuntimeAPI } from "../types"; import { assert_runtime_running, is_exited, is_runtime_running, mono_exit } from "./exit"; import { assertIsControllablePromise, createPromiseController, getPromiseController } from "./promise-controller"; -import { mono_download_assets, resolve_asset_path } from "./assets"; +import { ensureAssetResolvedUrl, enumerateResources, getAssetByNameWithResolvedUrl, mono_download_assets, resolve_asset_path } from "./assets"; import { mono_log_error, setup_proxy_console } from "./logging"; import { invokeLibraryInitializers } from "./libraryInitializers"; -import { loadResource, loadResources } from "./resourceLoader"; +import { loadResource } from "./resourceLoader"; import { hasDebuggingEnabled } from "./config"; export const ENVIRONMENT_IS_NODE = typeof process == "object" && typeof process.versions == "object" && typeof process.versions.node == "string"; @@ -95,8 +95,10 @@ export function setLoaderGlobals( setup_proxy_console, hasDebuggingEnabled, + enumerateResources, + ensureAssetResolvedUrl, + getAssetByNameWithResolvedUrl, loadResource, - loadResources, invokeLibraryInitializers, // from wasm-feature-detect npm package diff --git a/src/mono/wasm/runtime/loader/resourceLoader.ts b/src/mono/wasm/runtime/loader/resourceLoader.ts index 21e9d6c685f8c5..6db17fc482873b 100644 --- a/src/mono/wasm/runtime/loader/resourceLoader.ts +++ b/src/mono/wasm/runtime/loader/resourceLoader.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { AssetBehaviors, MonoConfig, ResourceList, WebAssemblyBootResourceType } from "../types"; +import type { AssetBehaviors, MonoConfig, WebAssemblyBootResourceType } from "../types"; import { loaderHelpers } from "./globals"; const networkFetchCacheMode = "no-cache"; @@ -20,11 +20,6 @@ const monoToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType | u "dotnetwasm": "dotnetwasm", }; -export function loadResources(resources: ResourceList, url: (name: string) => string, behavior: AssetBehaviors): LoadingResource[] { - return Object.keys(resources) - .map(name => loadResource(name, url(name), resources[name], behavior)); -} - export function loadResource(name: string, url: string, contentHash: string, behavior: AssetBehaviors): LoadingResource { const response = cacheIfUsed && !cacheSkipResourceTypes.includes(behavior) ? loadResourceWithCaching(cacheIfUsed, name, url, contentHash, behavior) diff --git a/src/mono/wasm/runtime/satelliteAssemblies.ts b/src/mono/wasm/runtime/satelliteAssemblies.ts index ffafb9f95c82ac..8e972def5fae91 100644 --- a/src/mono/wasm/runtime/satelliteAssemblies.ts +++ b/src/mono/wasm/runtime/satelliteAssemblies.ts @@ -12,7 +12,20 @@ export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise await Promise.all(culturesToLoad! .filter(culture => Object.prototype.hasOwnProperty.call(satelliteResources, culture)) - .map(culture => loaderHelpers.loadResources(satelliteResources[culture], fileName => loaderHelpers.locateFile(fileName), "resource")) + .map(culture => { + const promises: LoadingResource[] = []; + loaderHelpers.enumerateResources(satelliteResources[culture], (name, hash) => { + const asset = loaderHelpers.ensureAssetResolvedUrl({ + name, + hash, + behavior: "resource", + culture + }); + + promises.push(loaderHelpers.loadResource(asset.name, asset.resolvedUrl!, asset.hash ?? "", asset.behavior)); + }); + return promises; + }) .reduce((previous, next) => previous.concat(next), new Array()) .map(async resource => { const response = await resource.response; diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 4e5769141c3cf1..4d7a521be05c71 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -107,33 +107,35 @@ export type MonoConfig = { extensions?: { [name: string]: any }; }; -export type ResourceExtensions = { [extensionName: string]: ResourceList }; +export type ResourceExtensions = { [extensionName: string]: ResourceListOrArray }; export interface ResourceGroups { readonly hash?: string; - readonly assembly?: ResourceList; // nullable only temporarily - readonly lazyAssembly?: ResourceList; // nullable only temporarily - readonly pdb?: ResourceList; + readonly assembly?: ResourceListOrArray; // nullable only temporarily + readonly lazyAssembly?: ResourceListOrArray; // nullable only temporarily + readonly pdb?: ResourceListOrArray; readonly native?: NativeResources; // nullable only temporarily - readonly satelliteResources?: { [cultureName: string]: ResourceList }; + readonly satelliteResources?: { [cultureName: string]: ResourceListOrArray }; readonly libraryStartupModules?: { - readonly onRuntimeConfigLoaded?: ResourceList, - readonly onRuntimeReady?: ResourceList + readonly onRuntimeConfigLoaded?: ResourceListOrArray, + readonly onRuntimeReady?: ResourceListOrArray }, readonly extensions?: ResourceExtensions - readonly vfs?: { [virtualPath: string]: ResourceList }; + readonly vfs?: { [virtualPath: string]: ResourceListOrArray }; } export interface NativeResources { - readonly jsModuleWorker: ResourceList; - readonly jsModuleNative: ResourceList; - readonly jsModuleRuntime: ResourceList; - readonly wasmNative: ResourceList; - readonly symbols: ResourceList; - readonly icu: ResourceList; + readonly jsModuleWorker?: ResourceListOrString; + readonly jsModuleNative: ResourceListOrString; + readonly jsModuleRuntime: ResourceListOrString; + readonly wasmNative: ResourceListOrString; + readonly symbols?: ResourceListOrArray; + readonly icu?: ResourceListOrArray; } export type ResourceList = { [name: string]: string }; +export type ResourceListOrString = ResourceList | string; +export type ResourceListOrArray = ResourceList | string[]; /** * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index b33bc3fe1b0290..5283752271db3f 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { AssetBehaviors, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, ResourceList, RuntimeAPI } from "."; +import type { AssetBehaviors, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, ResourceListOrArray, RuntimeAPI } from "."; import type { CharPtr, EmscriptenModule, ManagedPointer, NativePointer, VoidPtr, Int32Ptr } from "./emscripten"; export type GCHandle = { @@ -142,10 +142,12 @@ export type LoaderHelpers = { err(message: string): void; hasDebuggingEnabled(config: MonoConfig): boolean, - loadResources(resources: ResourceList, url: (name: string) => string, behavior: AssetBehaviors): LoadingResource[], loadResource(name: string, url: string, contentHash: string, behavior: AssetBehaviors): LoadingResource, onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; + enumerateResources(resources: ResourceListOrArray, itemHandler: (name: string, hash: string | undefined) => void): void; + ensureAssetResolvedUrl(asset: AssetEntry): AssetEntry; + getAssetByNameWithResolvedUrl(resources: ResourceListOrArray | undefined, behavior: AssetBehaviors, requestedName: string): AssetEntry | undefined; loadBootResource?: LoadBootResourceCallback; invokeLibraryInitializers: (functionName: string, args: any[]) => Promise, libraryInitializers?: { scriptName: string, exports: any }[]; diff --git a/src/mono/wasm/test-main.js b/src/mono/wasm/test-main.js index 2e729760f6de51..004697c54ddf02 100644 --- a/src/mono/wasm/test-main.js +++ b/src/mono/wasm/test-main.js @@ -280,9 +280,9 @@ function configureRuntime(dotnet, runArgs) { } } if (is_browser) { - if (runArgs.memorySnapshot) { - dotnet.withStartupMemoryCache(true); - } + // if (runArgs.memorySnapshot) { + // dotnet.withStartupMemoryCache(true); + // } dotnet.withEnvironmentVariable("IsWebSocketSupported", "true"); } if (runArgs.runtimeArgs.length > 0) { @@ -330,13 +330,13 @@ async function run() { const runArgs = await getArgs(); console.log("Application arguments: " + runArgs.applicationArguments.join(' ')); - if (is_browser && runArgs.memorySnapshot) { - const dryOk = await dry_run(runArgs); - if (!dryOk) { - mono_exit(1, "Failed during dry run"); - return; - } - } + // if (is_browser && runArgs.memorySnapshot) { + // const dryOk = await dry_run(runArgs); + // if (!dryOk) { + // mono_exit(1, "Failed during dry run"); + // return; + // } + // } // this is subsequent run with the actual tests. It will use whatever was cached in the previous run. // This way, we are testing that the cached version works. @@ -389,7 +389,7 @@ async function run() { const app_args = runArgs.applicationArguments.slice(2); const result = await App.runtime.runMain(main_assembly_name, app_args); console.log(`test-main.js exiting ${app_args.length > 1 ? main_assembly_name + " " + app_args[0] : main_assembly_name} with result ${result}`); - mono_exit(result); + // mono_exit(result); } catch (error) { if (error.name != "ExitStatus") { mono_exit(1, error); From cfba71d07fbec0b93fd44b26dcb9d83811e05942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 14:09:19 +0200 Subject: [PATCH 36/65] Revert test-main.js --- src/mono/wasm/test-main.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/mono/wasm/test-main.js b/src/mono/wasm/test-main.js index 004697c54ddf02..2e729760f6de51 100644 --- a/src/mono/wasm/test-main.js +++ b/src/mono/wasm/test-main.js @@ -280,9 +280,9 @@ function configureRuntime(dotnet, runArgs) { } } if (is_browser) { - // if (runArgs.memorySnapshot) { - // dotnet.withStartupMemoryCache(true); - // } + if (runArgs.memorySnapshot) { + dotnet.withStartupMemoryCache(true); + } dotnet.withEnvironmentVariable("IsWebSocketSupported", "true"); } if (runArgs.runtimeArgs.length > 0) { @@ -330,13 +330,13 @@ async function run() { const runArgs = await getArgs(); console.log("Application arguments: " + runArgs.applicationArguments.join(' ')); - // if (is_browser && runArgs.memorySnapshot) { - // const dryOk = await dry_run(runArgs); - // if (!dryOk) { - // mono_exit(1, "Failed during dry run"); - // return; - // } - // } + if (is_browser && runArgs.memorySnapshot) { + const dryOk = await dry_run(runArgs); + if (!dryOk) { + mono_exit(1, "Failed during dry run"); + return; + } + } // this is subsequent run with the actual tests. It will use whatever was cached in the previous run. // This way, we are testing that the cached version works. @@ -389,7 +389,7 @@ async function run() { const app_args = runArgs.applicationArguments.slice(2); const result = await App.runtime.runMain(main_assembly_name, app_args); console.log(`test-main.js exiting ${app_args.length > 1 ? main_assembly_name + " " + app_args[0] : main_assembly_name} with result ${result}`); - // mono_exit(result); + mono_exit(result); } catch (error) { if (error.name != "ExitStatus") { mono_exit(1, error); From dfcbf4b4272dbc0b5987836da89529e448f29ef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 14:16:35 +0200 Subject: [PATCH 37/65] Fix appsettings --- src/mono/wasm/runtime/loader/assets.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index 808047d1386081..78b994551ed7b0 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -348,6 +348,7 @@ function prepareAssets(containedInSnapshotAssets: AssetEntryInternal[], alwaysLo if (configFileName === "appsettings.json" || configFileName === `appsettings.${config.applicationEnvironment}.json`) { alwaysLoadedAssets.push(ensureAssetResolvedUrl({ name: configFileName, + resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(configUrl), "vfs"), behavior: "vfs" })); } From 5bafb73b822be6547ee5f0b6a4b9a4943d1017a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 14:22:34 +0200 Subject: [PATCH 38/65] Fix enumerateResources --- src/mono/wasm/runtime/loader/assets.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index 78b994551ed7b0..2f7d506a65f90d 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -119,14 +119,11 @@ export function ensureAssetResolvedUrl(asset: AssetEntry): AssetEntry { } export function enumerateResources(resources: ResourceListOrArray, itemHandler: (name: string, hash: string | undefined) => void): void { - const stringArray = resources as string[]; - if (stringArray) { + if (resources.length) { + const stringArray = resources as string[]; for (let i = 0; i < stringArray.length; i++) { itemHandler(stringArray[i], undefined); } - // for (const index in resources) { - // itemHandler(stringArray[index], undefined); - // } } else { const objectWithHashes = resources as ResourceList; for (const name in objectWithHashes) { From 1082f1eef7d315142c8fc0f93f01652723218f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 14:35:14 +0200 Subject: [PATCH 39/65] Renames --- src/mono/wasm/runtime/loader/resourceLoader.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/mono/wasm/runtime/loader/resourceLoader.ts b/src/mono/wasm/runtime/loader/resourceLoader.ts index 6db17fc482873b..9be8d7798a347d 100644 --- a/src/mono/wasm/runtime/loader/resourceLoader.ts +++ b/src/mono/wasm/runtime/loader/resourceLoader.ts @@ -5,7 +5,8 @@ import type { AssetBehaviors, MonoConfig, WebAssemblyBootResourceType } from ".. import { loaderHelpers } from "./globals"; const networkFetchCacheMode = "no-cache"; -const cacheSkipResourceTypes: AssetBehaviors[] = ["vfs"]; // Previously on configuration +const cacheSkipAssetBehaviors: AssetBehaviors[] = ["vfs"]; // Previously only configuration +const credentialsIncludeAssetBehaviors: AssetBehaviors[] = ["vfs"]; // Previously only configuration const usedCacheKeys: { [key: string]: boolean } = {}; const networkLoads: { [name: string]: LoadLogEntry } = {}; const cacheLoads: { [name: string]: LoadLogEntry } = {}; @@ -21,7 +22,7 @@ const monoToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType | u }; export function loadResource(name: string, url: string, contentHash: string, behavior: AssetBehaviors): LoadingResource { - const response = cacheIfUsed && !cacheSkipResourceTypes.includes(behavior) + const response = cacheIfUsed && !cacheSkipAssetBehaviors.includes(behavior) ? loadResourceWithCaching(cacheIfUsed, name, url, contentHash, behavior) : loadResourceWithoutCaching(name, url, contentHash, behavior); @@ -141,7 +142,7 @@ function loadResourceWithoutCaching(name: string, url: string, contentHash: stri cache: networkFetchCacheMode }; - if (behavior === "vfs") { + if (credentialsIncludeAssetBehaviors.includes(behavior)) { // Include credentials so the server can allow download / provide user specific file fetchOptions.credentials = "include"; } else { From 30136b2ee08b6c4d609a231d11431ef8ac221a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 14:35:49 +0200 Subject: [PATCH 40/65] Refactor hash computation and native resource distribution in SDK tasks --- .../BootJsonBuilderHelper.cs | 78 +++++++++++++++++++ .../GenerateWasmBootJson.cs | 62 ++------------- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 60 +------------- .../WasmAppBuilder/WasmAppBuilder.csproj | 1 + 4 files changed, 88 insertions(+), 113 deletions(-) create mode 100644 src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs new file mode 100644 index 00000000000000..8099a0f7a9c9eb --- /dev/null +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Microsoft.Build.Utilities; + +namespace Microsoft.NET.Sdk.WebAssembly +{ + public class BootJsonBuilderHelper(TaskLoggingHelper Log) + { + public void ComputeResourcesHash(BootJsonData bootConfig) + { + var sb = new StringBuilder(); + + static void AddDictionary(StringBuilder sb, Dictionary? res) + { + if (res == null) + return; + + foreach (var asset in res) + sb.Append(asset.Value); + } + + AddDictionary(sb, bootConfig.resources.assembly); + + AddDictionary(sb, bootConfig.resources.native?.jsModuleWorker); + AddDictionary(sb, bootConfig.resources.native?.jsModuleNative); + AddDictionary(sb, bootConfig.resources.native?.jsModuleRuntime); + AddDictionary(sb, bootConfig.resources.native?.wasmNative); + AddDictionary(sb, bootConfig.resources.native?.symbols); + AddDictionary(sb, bootConfig.resources.native?.icu); + AddDictionary(sb, bootConfig.resources.runtime); + AddDictionary(sb, bootConfig.resources.lazyAssembly); + + if (bootConfig.resources.satelliteResources != null) + { + foreach (var culture in bootConfig.resources.satelliteResources) + AddDictionary(sb, culture.Value); + } + + if (bootConfig.resources.vfs != null) + { + foreach (var entry in bootConfig.resources.vfs) + AddDictionary(sb, entry.Value); + } + + bootConfig.resources.hash = Utils.ComputeTextIntegrity(sb.ToString()); + } + + public Dictionary? GetNativeResourceTargetInBootConfig(BootJsonData bootConfig, string resourceName) + { + bootConfig.resources.native ??= new(); + + string resourceExtension = Path.GetExtension(resourceName); + if (resourceName.StartsWith("dotnet.native.worker", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase)) + return bootConfig.resources.native.jsModuleWorker ??= new(); + else if (resourceName.StartsWith("dotnet.native", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase)) + return bootConfig.resources.native.jsModuleNative ??= new(); + else if (resourceName.StartsWith("dotnet.runtime", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase)) + return bootConfig.resources.native.jsModuleRuntime ??= new(); + else if (resourceName.StartsWith("dotnet.native", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".wasm", StringComparison.OrdinalIgnoreCase)) + return bootConfig.resources.native.wasmNative ??= new(); + else if (resourceName.StartsWith("dotnet", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase)) + return null; + else if (resourceName.StartsWith("dotnet.native", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".symbols", StringComparison.OrdinalIgnoreCase)) + return bootConfig.resources.native.symbols ??= new(); + else if (resourceName.StartsWith("icudt", StringComparison.OrdinalIgnoreCase)) + return bootConfig.resources.native.icu ??= new(); + else + Log.LogError($"The resource '{resourceName}' is not recognized as any native asset"); + + return null; + } + } +} diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index 5f52e28f307f9a..3e4d24283da833 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -10,6 +10,7 @@ using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Text; +using System.Xml.Linq; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using ResourceHashesByNameDictionary = System.Collections.Generic.Dictionary; @@ -87,6 +88,8 @@ public override bool Execute() // Internal for tests public void WriteBootJson(Stream output, string entryAssemblyName) { + var helper = new BootJsonBuilderHelper(Log); + var result = new BootJsonData { entryAssembly = entryAssemblyName, @@ -198,23 +201,7 @@ public void WriteBootJson(Stream output, string entryAssemblyName) if (IsTargeting80OrLater()) { - resourceData.native ??= new(); - if (resourceName.StartsWith("dotnet.native.worker") && resourceName.EndsWith(".js")) - resourceList = resourceData.native.jsModuleWorker ??= new(); - else if (resourceName.StartsWith("dotnet.native") && resourceName.EndsWith(".js")) - resourceList = resourceData.native.jsModuleNative ??= new(); - else if (resourceName.StartsWith("dotnet.runtime") && resourceName.EndsWith(".js")) - resourceList = resourceData.native.jsModuleRuntime ??= new(); - else if (resourceName.StartsWith("dotnet.native") && resourceName.EndsWith(".wasm")) - resourceList = resourceData.native.wasmNative ??= new(); - else if (resourceName.StartsWith("dotnet") && resourceName.EndsWith(".js")) - continue; - else if (resourceName.StartsWith("dotnet.native") && resourceName.EndsWith(".symbols")) - resourceList = resourceData.native.symbols ??= new(); - else if (resourceName.StartsWith("icudt")) - resourceList = resourceData.native.icu ??= new(); - else - Log.LogError($"The resource '{resourceName}' is not recognized as any native asset"); + resourceList = helper.GetNativeResourceTargetInBootConfig(result, resourceName); } else { @@ -353,7 +340,7 @@ public void WriteBootJson(Stream output, string entryAssemblyName) } } - ComputeResourcesHash(result); + helper.ComputeResourcesHash(result); var serializer = new DataContractJsonSerializer(typeof(BootJsonData), new DataContractJsonSerializerSettings { @@ -375,45 +362,6 @@ void AddResourceToList(ITaskItem resource, ResourceHashesByNameDictionary resour } } - private static void ComputeResourcesHash(BootJsonData bootConfig) - { - var sb = new StringBuilder(); - - static void AddDictionary(StringBuilder sb, ResourceHashesByNameDictionary res) - { - if (res == null) - return; - - foreach (var asset in res) - sb.Append(asset.Value); - } - - AddDictionary(sb, bootConfig.resources.assembly); - - AddDictionary(sb, bootConfig.resources.native?.jsModuleWorker); - AddDictionary(sb, bootConfig.resources.native?.jsModuleNative); - AddDictionary(sb, bootConfig.resources.native?.jsModuleRuntime); - AddDictionary(sb, bootConfig.resources.native?.wasmNative); - AddDictionary(sb, bootConfig.resources.native?.symbols); - AddDictionary(sb, bootConfig.resources.native?.icu); - AddDictionary(sb, bootConfig.resources.runtime); - AddDictionary(sb, bootConfig.resources.lazyAssembly); - - if (bootConfig.resources.satelliteResources != null) - { - foreach (var culture in bootConfig.resources.satelliteResources) - AddDictionary(sb, culture.Value); - } - - if (bootConfig.resources.vfs != null) - { - foreach (var entry in bootConfig.resources.vfs) - AddDictionary(sb, entry.Value); - } - - bootConfig.resources.hash = Utils.ComputeTextIntegrity(sb.ToString()); - } - private GlobalizationMode GetGlobalizationMode() { if (string.Equals(InvariantGlobalization, "true", StringComparison.OrdinalIgnoreCase)) diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index fa4e3db3f179ed..9f203bbc34f553 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -82,6 +82,8 @@ private GlobalizationMode GetGlobalizationMode() protected override bool ExecuteInternal() { + var helper = new BootJsonBuilderHelper(Log); + if (!ValidateArguments()) return false; @@ -158,24 +160,7 @@ protected override bool ExecuteInternal() var itemHash = Utils.ComputeIntegrity(item.ItemSpec); - bootConfig.resources.native ??= new(); - Dictionary? resourceList = null; - - if (name.StartsWith("dotnet.native.worker", StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetExtension(name), ".js", StringComparison.OrdinalIgnoreCase)) - resourceList = bootConfig.resources.native.jsModuleWorker ??= new(); - else if (name.StartsWith("dotnet.native", StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetExtension(name), ".js", StringComparison.OrdinalIgnoreCase)) - resourceList = bootConfig.resources.native.jsModuleNative ??= new(); - else if (name.StartsWith("dotnet.runtime", StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetExtension(name), ".js", StringComparison.OrdinalIgnoreCase)) - resourceList = bootConfig.resources.native.jsModuleRuntime ??= new(); - else if (name.StartsWith("dotnet.native", StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetExtension(name), ".wasm", StringComparison.OrdinalIgnoreCase)) - resourceList = bootConfig.resources.native.wasmNative ??= new(); - else if (name.StartsWith("dotnet", StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetExtension(name), ".js", StringComparison.OrdinalIgnoreCase)) - continue; - else if (name.StartsWith("dotnet.native", StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetExtension(name), ".symbols", StringComparison.OrdinalIgnoreCase)) - resourceList = bootConfig.resources.native.symbols ??= new(); - else if (name.StartsWith("icudt", StringComparison.OrdinalIgnoreCase)) - resourceList = bootConfig.resources.native.icu ??= new(); - + Dictionary? resourceList = helper.GetNativeResourceTargetInBootConfig(bootConfig, name); if (resourceList != null) resourceList[name] = itemHash; } @@ -379,44 +364,7 @@ protected override bool ExecuteInternal() string tmpMonoConfigPath = Path.GetTempFileName(); using (var sw = File.CreateText(tmpMonoConfigPath)) { - var sb = new StringBuilder(); - - static void AddDictionary(StringBuilder sb, Dictionary res) - { - if (res == null) - return; - - foreach (var asset in res) - sb.Append(asset.Value); - } - - AddDictionary(sb, bootConfig.resources.assembly); - - AddDictionary(sb, bootConfig.resources.native.jsModuleWorker); - AddDictionary(sb, bootConfig.resources.native.jsModuleNative); - AddDictionary(sb, bootConfig.resources.native.jsModuleRuntime); - AddDictionary(sb, bootConfig.resources.native.wasmNative); - AddDictionary(sb, bootConfig.resources.native.symbols); - AddDictionary(sb, bootConfig.resources.native.icu); - AddDictionary(sb, bootConfig.resources.runtime); - AddDictionary(sb, bootConfig.resources.lazyAssembly); - - if (bootConfig.resources.lazyAssembly != null) - AddDictionary(sb, bootConfig.resources.lazyAssembly); - - if (bootConfig.resources.satelliteResources != null) - { - foreach (var culture in bootConfig.resources.satelliteResources) - AddDictionary(sb, culture.Value); - } - - if (bootConfig.resources.vfs != null) - { - foreach (var entry in bootConfig.resources.vfs) - AddDictionary(sb, entry.Value); - } - - bootConfig.resources.hash = Utils.ComputeTextIntegrity(sb.ToString()); + helper.ComputeResourcesHash(bootConfig); var json = JsonSerializer.Serialize(bootConfig, new JsonSerializerOptions { WriteIndented = true }); sw.Write(json); diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj index a873b4d69f214e..67473b9df67834 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj @@ -20,6 +20,7 @@ + From c32f6027cbae3d61c8fb2902bb530209c6629b7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 14:44:48 +0200 Subject: [PATCH 41/65] Drop commented code --- src/mono/wasm/runtime/lazyLoading.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mono/wasm/runtime/lazyLoading.ts b/src/mono/wasm/runtime/lazyLoading.ts index 451cfde9f214e9..937c7881450e77 100644 --- a/src/mono/wasm/runtime/lazyLoading.ts +++ b/src/mono/wasm/runtime/lazyLoading.ts @@ -11,7 +11,6 @@ export async function loadLazyAssembly(assemblyNameToLoad: string): Promise Date: Thu, 27 Jul 2023 15:49:07 +0200 Subject: [PATCH 42/65] Flatten structure of native resources and startup modules --- ...rosoft.NET.Sdk.WebAssembly.Browser.targets | 8 +-- src/mono/wasm/runtime/dotnet.d.ts | 21 +++--- src/mono/wasm/runtime/loader/assets.ts | 22 +++---- src/mono/wasm/runtime/loader/config.ts | 2 +- src/mono/wasm/runtime/loader/icu.ts | 4 +- .../runtime/loader/libraryInitializers.ts | 10 +-- src/mono/wasm/runtime/loader/run.ts | 2 +- src/mono/wasm/runtime/types/index.ts | 20 +++--- .../BootJsonBuilderHelper.cs | 26 ++++---- .../BootJsonData.cs | 65 +++++++------------ .../GenerateWasmBootJson.cs | 25 ++++--- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 5 +- 12 files changed, 88 insertions(+), 122 deletions(-) diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets index eb16d0272fa5c3..7b3c878a5c17b9 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets @@ -360,8 +360,8 @@ Copyright (c) .NET Foundation. All rights reserved. RuntimeOptions="$(_BlazorWebAssemblyRuntimeOptions)" Extensions="@(WasmBootConfigExtension)" TargetFrameworkVersion="$(TargetFrameworkVersion)" - LibraryInitializerOnRuntimeConfigLoaded="@(WasmLibraryInitializerOnRuntimeConfigLoaded)" - LibraryInitializerOnRuntimeReady="@(WasmLibraryInitializerOnRuntimeReady)" /> + ModuleAfterConfigLoaded="@(WasmModuleAfterConfigLoaded)" + ModuleAfterRuntimeReady="@(WasmModuleAfterRuntimeReady)" /> @@ -551,8 +551,8 @@ Copyright (c) .NET Foundation. All rights reserved. RuntimeOptions="$(_BlazorWebAssemblyRuntimeOptions)" Extensions="@(WasmBootConfigExtension)" TargetFrameworkVersion="$(TargetFrameworkVersion)" - LibraryInitializerOnRuntimeConfigLoaded="@(WasmLibraryInitializerOnRuntimeConfigLoaded)" - LibraryInitializerOnRuntimeReady="@(WasmLibraryInitializerOnRuntimeReady)" /> + ModuleAfterConfigLoaded="@(WasmModuleAfterConfigLoaded)" + ModuleAfterRuntimeReady="@(WasmModuleAfterRuntimeReady)" /> diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index e2c57b633b4d7f..854472c91f9a14 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -178,27 +178,22 @@ interface ResourceGroups { readonly assembly?: ResourceListOrArray; readonly lazyAssembly?: ResourceListOrArray; readonly pdb?: ResourceListOrArray; - readonly native?: NativeResources; + readonly jsModuleWorker?: ResourceListOrString; + readonly jsModuleNative: ResourceListOrString; + readonly jsModuleRuntime: ResourceListOrString; + readonly jsSymbols?: ResourceListOrArray; + readonly wasmNative: ResourceListOrString; + readonly icu?: ResourceListOrArray; readonly satelliteResources?: { [cultureName: string]: ResourceListOrArray; }; - readonly libraryStartupModules?: { - readonly onRuntimeConfigLoaded?: ResourceListOrArray; - readonly onRuntimeReady?: ResourceListOrArray; - }; + readonly modulesAfterConfigLoaded?: ResourceListOrArray; + readonly modulesAfterRuntimeReady?: ResourceListOrArray; readonly extensions?: ResourceExtensions; readonly vfs?: { [virtualPath: string]: ResourceListOrArray; }; } -interface NativeResources { - readonly jsModuleWorker?: ResourceListOrString; - readonly jsModuleNative: ResourceListOrString; - readonly jsModuleRuntime: ResourceListOrString; - readonly wasmNative: ResourceListOrString; - readonly symbols?: ResourceListOrArray; - readonly icu?: ResourceListOrArray; -} type ResourceList = { [name: string]: string; }; diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index 2f7d506a65f90d..2ed7e46ac486ef 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -133,18 +133,18 @@ export function enumerateResources(resources: ResourceListOrArray, itemHandler: } export function resolve_asset_path(behavior: SingleAssetBehaviors): AssetEntryInternal { - const nativeResources = loaderHelpers.config.resources?.native; - mono_assert(nativeResources, () => "Can't find native resources in config"); + const resources = loaderHelpers.config.resources; + mono_assert(resources, () => "Can't find native resources in config"); switch (behavior) { case "dotnetwasm": - return getFirstAssetWithResolvedUrl(nativeResources.wasmNative, behavior); + return getFirstAssetWithResolvedUrl(resources.wasmNative, behavior); case "js-module-threads": - return getFirstAssetWithResolvedUrl(nativeResources.jsModuleWorker, behavior); + return getFirstAssetWithResolvedUrl(resources.jsModuleWorker, behavior); case "js-module-native": - return getFirstAssetWithResolvedUrl(nativeResources.jsModuleNative, behavior); + return getFirstAssetWithResolvedUrl(resources.jsModuleNative, behavior); case "js-module-runtime": - return getFirstAssetWithResolvedUrl(nativeResources.jsModuleRuntime, behavior); + return getFirstAssetWithResolvedUrl(resources.jsModuleRuntime, behavior); } } @@ -311,11 +311,9 @@ function prepareAssets(containedInSnapshotAssets: AssetEntryInternal[], alwaysLo } } - const nativeResources = resources.native; - const icuDataResourceName = getIcuResourceName(config); - if (icuDataResourceName && nativeResources?.icu) { - enumerateResources(nativeResources.icu, (name, hash) => { + if (icuDataResourceName && resources.icu) { + enumerateResources(resources.icu, (name, hash) => { if (name === icuDataResourceName) { containedInSnapshotAssets.push(ensureAssetResolvedUrl({ name, @@ -327,8 +325,8 @@ function prepareAssets(containedInSnapshotAssets: AssetEntryInternal[], alwaysLo }); } - if (nativeResources?.symbols) { - enumerateResources(nativeResources.symbols, (name, hash) => { + if (resources.jsSymbols) { + enumerateResources(resources.jsSymbols, (name, hash) => { alwaysLoadedAssets.push(ensureAssetResolvedUrl({ name, hash, diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index fba13b94872735..56f294cbe454af 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -97,7 +97,7 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi normalizeConfig(); - await invokeLibraryInitializers("onRuntimeConfigLoaded", [loaderHelpers.config], "onRuntimeConfigLoaded"); + await invokeLibraryInitializers("onRuntimeConfigLoaded", [loaderHelpers.config], "modulesAfterConfigLoaded"); if (module.onConfigLoaded) { try { diff --git a/src/mono/wasm/runtime/loader/icu.ts b/src/mono/wasm/runtime/loader/icu.ts index a4ebe2ee648da8..927672f5db99d6 100644 --- a/src/mono/wasm/runtime/loader/icu.ts +++ b/src/mono/wasm/runtime/loader/icu.ts @@ -46,10 +46,10 @@ export function init_globalization() { } export function getIcuResourceName(config: MonoConfig): string | null { - if (config.resources?.native?.icu && config.globalizationMode != GlobalizationMode.Invariant) { + if (config.resources?.icu && config.globalizationMode != GlobalizationMode.Invariant) { const culture = config.applicationCulture || (ENVIRONMENT_IS_WEB ? (navigator.languages && navigator.languages[0]) : Intl.DateTimeFormat().resolvedOptions().locale); - const icuFiles = Object.keys(config.resources.native.icu); + const icuFiles = Object.keys(config.resources.icu); let icuFile = null; if (config.globalizationMode === GlobalizationMode.Custom) { diff --git a/src/mono/wasm/runtime/loader/libraryInitializers.ts b/src/mono/wasm/runtime/loader/libraryInitializers.ts index 87007e35e98cd7..999d847e2755a2 100644 --- a/src/mono/wasm/runtime/loader/libraryInitializers.ts +++ b/src/mono/wasm/runtime/loader/libraryInitializers.ts @@ -8,17 +8,17 @@ import { loaderHelpers } from "./globals"; import { mono_exit } from "./exit"; export type LibraryInitializerTypes = - "onRuntimeConfigLoaded" - | "onRuntimeReady"; + "modulesAfterConfigLoaded" + | "modulesAfterRuntimeReady"; async function fetchLibraryInitializers(config: MonoConfig, type: LibraryInitializerTypes): Promise { if (!loaderHelpers.libraryInitializers) { loaderHelpers.libraryInitializers = []; } - const libraryInitializers = type == "onRuntimeConfigLoaded" - ? config.resources?.libraryStartupModules?.onRuntimeConfigLoaded - : config.resources?.libraryStartupModules?.onRuntimeReady; + const libraryInitializers = type == "modulesAfterConfigLoaded" + ? config.resources?.modulesAfterConfigLoaded + : config.resources?.modulesAfterRuntimeReady; if (!libraryInitializers) { return; diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts index 5e8e45629566eb..7912be2197c942 100644 --- a/src/mono/wasm/runtime/loader/run.ts +++ b/src/mono/wasm/runtime/loader/run.ts @@ -483,7 +483,7 @@ async function createEmscriptenMain(): Promise { await runtimeHelpers.dotnetReady.promise; - await invokeLibraryInitializers("onRuntimeReady", [globalObjectsRoot.api], "onRuntimeReady"); + await invokeLibraryInitializers("onRuntimeReady", [globalObjectsRoot.api], "modulesAfterRuntimeReady"); return exportedRuntimeAPI; } diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 4d7a521be05c71..c207502c5bf1e6 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -114,23 +114,21 @@ export interface ResourceGroups { readonly assembly?: ResourceListOrArray; // nullable only temporarily readonly lazyAssembly?: ResourceListOrArray; // nullable only temporarily readonly pdb?: ResourceListOrArray; - readonly native?: NativeResources; // nullable only temporarily - readonly satelliteResources?: { [cultureName: string]: ResourceListOrArray }; - readonly libraryStartupModules?: { - readonly onRuntimeConfigLoaded?: ResourceListOrArray, - readonly onRuntimeReady?: ResourceListOrArray - }, - readonly extensions?: ResourceExtensions - readonly vfs?: { [virtualPath: string]: ResourceListOrArray }; -} -export interface NativeResources { readonly jsModuleWorker?: ResourceListOrString; readonly jsModuleNative: ResourceListOrString; readonly jsModuleRuntime: ResourceListOrString; + readonly jsSymbols?: ResourceListOrArray; readonly wasmNative: ResourceListOrString; - readonly symbols?: ResourceListOrArray; readonly icu?: ResourceListOrArray; + + readonly satelliteResources?: { [cultureName: string]: ResourceListOrArray }; + + readonly modulesAfterConfigLoaded?: ResourceListOrArray, + readonly modulesAfterRuntimeReady?: ResourceListOrArray + + readonly extensions?: ResourceExtensions + readonly vfs?: { [virtualPath: string]: ResourceListOrArray }; } export type ResourceList = { [name: string]: string }; diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs index 8099a0f7a9c9eb..bf223c64b65236 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs @@ -26,12 +26,12 @@ static void AddDictionary(StringBuilder sb, Dictionary? res) AddDictionary(sb, bootConfig.resources.assembly); - AddDictionary(sb, bootConfig.resources.native?.jsModuleWorker); - AddDictionary(sb, bootConfig.resources.native?.jsModuleNative); - AddDictionary(sb, bootConfig.resources.native?.jsModuleRuntime); - AddDictionary(sb, bootConfig.resources.native?.wasmNative); - AddDictionary(sb, bootConfig.resources.native?.symbols); - AddDictionary(sb, bootConfig.resources.native?.icu); + AddDictionary(sb, bootConfig.resources.jsModuleWorker); + AddDictionary(sb, bootConfig.resources.jsModuleNative); + AddDictionary(sb, bootConfig.resources.jsModuleRuntime); + AddDictionary(sb, bootConfig.resources.wasmNative); + AddDictionary(sb, bootConfig.resources.jsSymbols); + AddDictionary(sb, bootConfig.resources.icu); AddDictionary(sb, bootConfig.resources.runtime); AddDictionary(sb, bootConfig.resources.lazyAssembly); @@ -52,23 +52,21 @@ static void AddDictionary(StringBuilder sb, Dictionary? res) public Dictionary? GetNativeResourceTargetInBootConfig(BootJsonData bootConfig, string resourceName) { - bootConfig.resources.native ??= new(); - string resourceExtension = Path.GetExtension(resourceName); if (resourceName.StartsWith("dotnet.native.worker", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase)) - return bootConfig.resources.native.jsModuleWorker ??= new(); + return bootConfig.resources.jsModuleWorker ??= new(); else if (resourceName.StartsWith("dotnet.native", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase)) - return bootConfig.resources.native.jsModuleNative ??= new(); + return bootConfig.resources.jsModuleNative ??= new(); else if (resourceName.StartsWith("dotnet.runtime", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase)) - return bootConfig.resources.native.jsModuleRuntime ??= new(); + return bootConfig.resources.jsModuleRuntime ??= new(); else if (resourceName.StartsWith("dotnet.native", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".wasm", StringComparison.OrdinalIgnoreCase)) - return bootConfig.resources.native.wasmNative ??= new(); + return bootConfig.resources.wasmNative ??= new(); else if (resourceName.StartsWith("dotnet", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase)) return null; else if (resourceName.StartsWith("dotnet.native", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".symbols", StringComparison.OrdinalIgnoreCase)) - return bootConfig.resources.native.symbols ??= new(); + return bootConfig.resources.jsSymbols ??= new(); else if (resourceName.StartsWith("icudt", StringComparison.OrdinalIgnoreCase)) - return bootConfig.resources.native.icu ??= new(); + return bootConfig.resources.icu ??= new(); else Log.LogError($"The resource '{resourceName}' is not recognized as any native asset"); diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs index 7ce67eb751a607..34b852872862d6 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -124,14 +124,28 @@ public class ResourcesData /// .NET Wasm runtime resources (dotnet.wasm, dotnet.js) etc. /// /// - /// Deprecated in .NET 8, use . + /// Deprecated in .NET 8, use , , , , , . /// + [DataMember(EmitDefaultValue = false)] public ResourceHashesByNameDictionary runtime { get; set; } - /// - /// Native runtime assets (dotnet.wasm, dotnet.js, etc). - /// - public NativeResources native { get; set; } + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary jsModuleWorker { get; set; } + + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary jsModuleNative { get; set; } + + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary jsModuleRuntime { get; set; } + + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary wasmNative { get; set; } + + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary jsSymbols { get; set; } + + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary icu { get; set; } /// /// "assembly" (.dll) resources @@ -163,12 +177,11 @@ public class ResourcesData [DataMember(EmitDefaultValue = false)] public ResourceHashesByNameDictionary libraryInitializers { get; set; } - /// - /// JavaScript module initializers that runtime will be in charge of loading. - /// Used in .NET >= 8 - /// [DataMember(EmitDefaultValue = false)] - public TypedLibraryStartupModules libraryStartupModules { get; set; } + public ResourceHashesByNameDictionary modulesAfterConfigLoaded { get; set; } + + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary modulesAfterRuntimeReady { get; set; } /// /// Extensions created by users customizing the initialization process. The format of the file(s) @@ -190,38 +203,6 @@ public class ResourcesData public List remoteSources { get; set; } } -[DataContract] -public class NativeResources -{ - [DataMember(EmitDefaultValue = false)] - public ResourceHashesByNameDictionary jsModuleWorker { get; set; } - - [DataMember(EmitDefaultValue = false)] - public ResourceHashesByNameDictionary jsModuleNative { get; set; } - - [DataMember(EmitDefaultValue = false)] - public ResourceHashesByNameDictionary jsModuleRuntime { get; set; } - - [DataMember(EmitDefaultValue = false)] - public ResourceHashesByNameDictionary wasmNative { get; set; } - - [DataMember(EmitDefaultValue = false)] - public ResourceHashesByNameDictionary symbols { get; set; } - - [DataMember(EmitDefaultValue = false)] - public ResourceHashesByNameDictionary icu { get; set; } -} - -[DataContract] -public class TypedLibraryStartupModules -{ - [DataMember(EmitDefaultValue = false)] - public ResourceHashesByNameDictionary onRuntimeConfigLoaded { get; set; } - - [DataMember(EmitDefaultValue = false)] - public ResourceHashesByNameDictionary onRuntimeReady { get; set; } -} - public enum GlobalizationMode : int { // Note that the numeric values are serialized and used in JS code, so don't change them without also updating the JS code diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index 3e4d24283da833..3201dd1d1c5781 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -59,9 +59,9 @@ public class GenerateWasmBootJson : Task [Required] public string TargetFrameworkVersion { get; set; } - public ITaskItem[] LibraryInitializerOnRuntimeConfigLoaded { get; set; } + public ITaskItem[] ModuleAfterConfigLoaded { get; set; } - public ITaskItem[] LibraryInitializerOnRuntimeReady { get; set; } + public ITaskItem[] ModuleAfterRuntimeReady { get; set; } [Required] public string OutputPath { get; set; } @@ -130,8 +130,8 @@ public void WriteBootJson(Stream output, string entryAssemblyName) result.runtimeOptions = runtimeOptions.ToArray(); } - string[] libraryInitializerOnRuntimeConfigLoadedFullPaths = LibraryInitializerOnRuntimeConfigLoaded?.Select(s => s.GetMetadata("FullPath")).ToArray() ?? Array.Empty(); - string[] libraryInitializerOnRuntimeReadyFullPath = LibraryInitializerOnRuntimeReady?.Select(s => s.GetMetadata("FullPath")).ToArray() ?? Array.Empty(); + string[] moduleAfterConfigLoadedFullPaths = ModuleAfterConfigLoaded?.Select(s => s.GetMetadata("FullPath")).ToArray() ?? Array.Empty(); + string[] moduleAfterRuntimeReadyFullPaths = ModuleAfterRuntimeReady?.Select(s => s.GetMetadata("FullPath")).ToArray() ?? Array.Empty(); // Build a two-level dictionary of the form: // - assembly: @@ -226,27 +226,25 @@ public void WriteBootJson(Stream output, string entryAssemblyName) if (IsTargeting80OrLater()) { - var libraryStartupModules = resourceData.libraryStartupModules ??= new TypedLibraryStartupModules(); - - if (libraryInitializerOnRuntimeConfigLoadedFullPaths.Contains(resource.ItemSpec)) + if (moduleAfterConfigLoadedFullPaths.Contains(resource.ItemSpec)) { - resourceList = libraryStartupModules.onRuntimeConfigLoaded ??= new(); + resourceList = resourceData.modulesAfterConfigLoaded ??= new(); } - else if (libraryInitializerOnRuntimeReadyFullPath.Contains(resource.ItemSpec)) + else if (moduleAfterRuntimeReadyFullPaths.Contains(resource.ItemSpec)) { - resourceList = libraryStartupModules.onRuntimeReady ??= new(); + resourceList = resourceData.modulesAfterRuntimeReady ??= new(); } else if (File.Exists(resource.ItemSpec)) { string fileContent = File.ReadAllText(resource.ItemSpec); if (fileContent.Contains("onRuntimeConfigLoaded") || fileContent.Contains("beforeStart") || fileContent.Contains("afterStarted")) - resourceList = libraryStartupModules.onRuntimeConfigLoaded ??= new(); + resourceList = resourceData.modulesAfterConfigLoaded ??= new(); else - resourceList = libraryStartupModules.onRuntimeReady ??= new(); + resourceList = resourceData.modulesAfterRuntimeReady ??= new(); } else { - resourceList = libraryStartupModules.onRuntimeConfigLoaded ??= new(); + resourceList = resourceData.modulesAfterConfigLoaded ??= new(); } string newTargetPath = "../" + targetPath; // This needs condition once WasmRuntimeAssetsLocation is supported in Wasm SDK @@ -345,7 +343,6 @@ public void WriteBootJson(Stream output, string entryAssemblyName) var serializer = new DataContractJsonSerializer(typeof(BootJsonData), new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true, - KnownTypes = new[] { typeof(TypedLibraryStartupModules) }, EmitTypeInformation = EmitTypeInformation.Never }); diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 9f203bbc34f553..a651150f0112f2 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -304,9 +304,8 @@ protected override bool ExecuteInternal() return false; } - bootConfig.resources.native ??= new(); - bootConfig.resources.native.icu ??= new(); - bootConfig.resources.native.icu[Path.GetFileName(idfn)] = Utils.ComputeIntegrity(idfn); + bootConfig.resources.icu ??= new(); + bootConfig.resources.icu[Path.GetFileName(idfn)] = Utils.ComputeIntegrity(idfn); } } From 1f67e01dfa84aba1fb26f1e398eeaf923075598e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 16:00:35 +0200 Subject: [PATCH 43/65] Remove compact resource schema --- src/mono/wasm/runtime/dotnet.d.ts | 30 ++++---- src/mono/wasm/runtime/loader/assets.ts | 75 +++++++------------- src/mono/wasm/runtime/loader/globals.ts | 3 +- src/mono/wasm/runtime/satelliteAssemblies.ts | 7 +- src/mono/wasm/runtime/types/index.ts | 30 ++++---- src/mono/wasm/runtime/types/internal.ts | 5 +- 6 files changed, 59 insertions(+), 91 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 854472c91f9a14..1c73fb9ee69c6e 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -171,34 +171,32 @@ type MonoConfig = { }; }; type ResourceExtensions = { - [extensionName: string]: ResourceListOrArray; + [extensionName: string]: ResourceList; }; interface ResourceGroups { readonly hash?: string; - readonly assembly?: ResourceListOrArray; - readonly lazyAssembly?: ResourceListOrArray; - readonly pdb?: ResourceListOrArray; - readonly jsModuleWorker?: ResourceListOrString; - readonly jsModuleNative: ResourceListOrString; - readonly jsModuleRuntime: ResourceListOrString; - readonly jsSymbols?: ResourceListOrArray; - readonly wasmNative: ResourceListOrString; - readonly icu?: ResourceListOrArray; + readonly assembly?: ResourceList; + readonly lazyAssembly?: ResourceList; + readonly pdb?: ResourceList; + readonly jsModuleWorker?: ResourceList; + readonly jsModuleNative: ResourceList; + readonly jsModuleRuntime: ResourceList; + readonly jsSymbols?: ResourceList; + readonly wasmNative: ResourceList; + readonly icu?: ResourceList; readonly satelliteResources?: { - [cultureName: string]: ResourceListOrArray; + [cultureName: string]: ResourceList; }; - readonly modulesAfterConfigLoaded?: ResourceListOrArray; - readonly modulesAfterRuntimeReady?: ResourceListOrArray; + readonly modulesAfterConfigLoaded?: ResourceList; + readonly modulesAfterRuntimeReady?: ResourceList; readonly extensions?: ResourceExtensions; readonly vfs?: { - [virtualPath: string]: ResourceListOrArray; + [virtualPath: string]: ResourceList; }; } type ResourceList = { [name: string]: string; }; -type ResourceListOrString = ResourceList | string; -type ResourceListOrArray = ResourceList | string[]; /** * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched * from a custom source, such as an external CDN. diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index 2ed7e46ac486ef..a31ad8fceec1a9 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { AssetEntryInternal, PromiseAndController } from "../types/internal"; -import type { AssetBehaviors, AssetEntry, LoadingResource, ResourceList, ResourceListOrArray, ResourceListOrString, ResourceRequest, SingleAssetBehaviors as SingleAssetBehaviors } from "../types"; +import type { AssetBehaviors, AssetEntry, LoadingResource, ResourceList, ResourceRequest, SingleAssetBehaviors as SingleAssetBehaviors } from "../types"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { createPromiseController } from "./promise-controller"; import { mono_log_debug } from "./logging"; @@ -74,7 +74,7 @@ function getFirstKey(resources: ResourceList | undefined): string | null { return null; } -function getFirstAssetWithResolvedUrl(resources: ResourceListOrString | undefined, behavior: SingleAssetBehaviors): AssetEntry { +function getFirstAssetWithResolvedUrl(resources: ResourceList | undefined, behavior: SingleAssetBehaviors): AssetEntry { const isString = typeof (resources) === "string"; const name = isString ? resources : getFirstKey(resources); mono_assert(name, `Can't find ${behavior} in resources`); @@ -85,27 +85,14 @@ function getFirstAssetWithResolvedUrl(resources: ResourceListOrString | undefine }); } -export function getAssetByNameWithResolvedUrl(resources: ResourceListOrArray | undefined, behavior: AssetBehaviors, requestedName: string): AssetEntry | undefined { - if (!resources) { - return undefined; - } - - let foundName = undefined; - let foundHash = undefined; - enumerateResources(resources, (name, hash) => { - if (name == requestedName) { - foundName = name; - foundHash = hash; - } - }); - - if (!foundName) { +export function getAssetByNameWithResolvedUrl(resources: ResourceList | undefined, behavior: AssetBehaviors, name: string): AssetEntry | undefined { + if (!resources || !resources[name]) { return undefined; } return ensureAssetResolvedUrl({ - name: foundName, - hash: foundHash, + name: name, + hash: resources[name], behavior }); } @@ -118,20 +105,6 @@ export function ensureAssetResolvedUrl(asset: AssetEntry): AssetEntry { return asset; } -export function enumerateResources(resources: ResourceListOrArray, itemHandler: (name: string, hash: string | undefined) => void): void { - if (resources.length) { - const stringArray = resources as string[]; - for (let i = 0; i < stringArray.length; i++) { - itemHandler(stringArray[i], undefined); - } - } else { - const objectWithHashes = resources as ResourceList; - for (const name in objectWithHashes) { - itemHandler(name, objectWithHashes[name]); - } - } -} - export function resolve_asset_path(behavior: SingleAssetBehaviors): AssetEntryInternal { const resources = loaderHelpers.config.resources; mono_assert(resources, () => "Can't find native resources in config"); @@ -266,73 +239,73 @@ function prepareAssets(containedInSnapshotAssets: AssetEntryInternal[], alwaysLo const resources = loaderHelpers.config.resources; if (resources) { if (resources.assembly) { - enumerateResources(resources.assembly, (name, hash) => { + for (const name in resources.assembly) { containedInSnapshotAssets.push(ensureAssetResolvedUrl({ name, - hash, + hash: resources.assembly[name], behavior: "assembly" })); - }); + } } if (config.debugLevel != 0 && resources.pdb) { - enumerateResources(resources.pdb, (name, hash) => { + for (const name in resources.pdb) { containedInSnapshotAssets.push(ensureAssetResolvedUrl({ name, - hash, + hash: resources.pdb[name], behavior: "pdb" })); - }); + } } if (config.loadAllSatelliteResources && resources.satelliteResources) { for (const culture in resources.satelliteResources) { - enumerateResources(resources.satelliteResources[culture], (name, hash) => { + for (const name in resources.satelliteResources[culture]) { containedInSnapshotAssets.push(ensureAssetResolvedUrl({ name, - hash, + hash: resources.satelliteResources[culture][name], behavior: "resource", culture })); - }); + } } } if (resources.vfs) { for (const virtualPath in resources.vfs) { - enumerateResources(resources.vfs[virtualPath], (name, hash) => { + for (const name in resources.vfs[virtualPath]) { alwaysLoadedAssets.push(ensureAssetResolvedUrl({ name, - hash, + hash: resources.vfs[virtualPath][name], behavior: "vfs", virtualPath })); - }); + } } } const icuDataResourceName = getIcuResourceName(config); if (icuDataResourceName && resources.icu) { - enumerateResources(resources.icu, (name, hash) => { + for (const name in resources.icu) { if (name === icuDataResourceName) { containedInSnapshotAssets.push(ensureAssetResolvedUrl({ name, - hash, + hash: resources.icu[name], behavior: "icu", loadRemote: true })); } - }); + } } if (resources.jsSymbols) { - enumerateResources(resources.jsSymbols, (name, hash) => { + for (const name in resources.jsSymbols) { alwaysLoadedAssets.push(ensureAssetResolvedUrl({ name, - hash, + hash: resources.jsSymbols[name], behavior: "symbols" })); - }); + } } } diff --git a/src/mono/wasm/runtime/loader/globals.ts b/src/mono/wasm/runtime/loader/globals.ts index 1005b5645c1a72..b315d0205bbb1b 100644 --- a/src/mono/wasm/runtime/loader/globals.ts +++ b/src/mono/wasm/runtime/loader/globals.ts @@ -7,7 +7,7 @@ import type { AssetEntryInternal, GlobalObjects, LoaderHelpers, RuntimeHelpers } import type { MonoConfig, RuntimeAPI } from "../types"; import { assert_runtime_running, is_exited, is_runtime_running, mono_exit } from "./exit"; import { assertIsControllablePromise, createPromiseController, getPromiseController } from "./promise-controller"; -import { ensureAssetResolvedUrl, enumerateResources, getAssetByNameWithResolvedUrl, mono_download_assets, resolve_asset_path } from "./assets"; +import { ensureAssetResolvedUrl, getAssetByNameWithResolvedUrl, mono_download_assets, resolve_asset_path } from "./assets"; import { mono_log_error, setup_proxy_console } from "./logging"; import { invokeLibraryInitializers } from "./libraryInitializers"; import { loadResource } from "./resourceLoader"; @@ -95,7 +95,6 @@ export function setLoaderGlobals( setup_proxy_console, hasDebuggingEnabled, - enumerateResources, ensureAssetResolvedUrl, getAssetByNameWithResolvedUrl, loadResource, diff --git a/src/mono/wasm/runtime/satelliteAssemblies.ts b/src/mono/wasm/runtime/satelliteAssemblies.ts index 8e972def5fae91..1ce469973bccaf 100644 --- a/src/mono/wasm/runtime/satelliteAssemblies.ts +++ b/src/mono/wasm/runtime/satelliteAssemblies.ts @@ -14,16 +14,17 @@ export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise .filter(culture => Object.prototype.hasOwnProperty.call(satelliteResources, culture)) .map(culture => { const promises: LoadingResource[] = []; - loaderHelpers.enumerateResources(satelliteResources[culture], (name, hash) => { + for (const name in satelliteResources[culture]) { const asset = loaderHelpers.ensureAssetResolvedUrl({ name, - hash, + hash: satelliteResources[culture][name], behavior: "resource", culture }); promises.push(loaderHelpers.loadResource(asset.name, asset.resolvedUrl!, asset.hash ?? "", asset.behavior)); - }); + } + return promises; }) .reduce((previous, next) => previous.concat(next), new Array()) diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index c207502c5bf1e6..9523f51fa69628 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -107,33 +107,31 @@ export type MonoConfig = { extensions?: { [name: string]: any }; }; -export type ResourceExtensions = { [extensionName: string]: ResourceListOrArray }; +export type ResourceExtensions = { [extensionName: string]: ResourceList }; export interface ResourceGroups { readonly hash?: string; - readonly assembly?: ResourceListOrArray; // nullable only temporarily - readonly lazyAssembly?: ResourceListOrArray; // nullable only temporarily - readonly pdb?: ResourceListOrArray; + readonly assembly?: ResourceList; // nullable only temporarily + readonly lazyAssembly?: ResourceList; // nullable only temporarily + readonly pdb?: ResourceList; - readonly jsModuleWorker?: ResourceListOrString; - readonly jsModuleNative: ResourceListOrString; - readonly jsModuleRuntime: ResourceListOrString; - readonly jsSymbols?: ResourceListOrArray; - readonly wasmNative: ResourceListOrString; - readonly icu?: ResourceListOrArray; + readonly jsModuleWorker?: ResourceList; + readonly jsModuleNative: ResourceList; + readonly jsModuleRuntime: ResourceList; + readonly jsSymbols?: ResourceList; + readonly wasmNative: ResourceList; + readonly icu?: ResourceList; - readonly satelliteResources?: { [cultureName: string]: ResourceListOrArray }; + readonly satelliteResources?: { [cultureName: string]: ResourceList }; - readonly modulesAfterConfigLoaded?: ResourceListOrArray, - readonly modulesAfterRuntimeReady?: ResourceListOrArray + readonly modulesAfterConfigLoaded?: ResourceList, + readonly modulesAfterRuntimeReady?: ResourceList readonly extensions?: ResourceExtensions - readonly vfs?: { [virtualPath: string]: ResourceListOrArray }; + readonly vfs?: { [virtualPath: string]: ResourceList }; } export type ResourceList = { [name: string]: string }; -export type ResourceListOrString = ResourceList | string; -export type ResourceListOrArray = ResourceList | string[]; /** * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 5283752271db3f..75703dfd8dfd8e 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { AssetBehaviors, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, ResourceListOrArray, RuntimeAPI } from "."; +import type { AssetBehaviors, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, ResourceList, RuntimeAPI } from "."; import type { CharPtr, EmscriptenModule, ManagedPointer, NativePointer, VoidPtr, Int32Ptr } from "./emscripten"; export type GCHandle = { @@ -145,9 +145,8 @@ export type LoaderHelpers = { loadResource(name: string, url: string, contentHash: string, behavior: AssetBehaviors): LoadingResource, onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; - enumerateResources(resources: ResourceListOrArray, itemHandler: (name: string, hash: string | undefined) => void): void; ensureAssetResolvedUrl(asset: AssetEntry): AssetEntry; - getAssetByNameWithResolvedUrl(resources: ResourceListOrArray | undefined, behavior: AssetBehaviors, requestedName: string): AssetEntry | undefined; + getAssetByNameWithResolvedUrl(resources: ResourceList | undefined, behavior: AssetBehaviors, requestedName: string): AssetEntry | undefined; loadBootResource?: LoadBootResourceCallback; invokeLibraryInitializers: (functionName: string, args: any[]) => Promise, libraryInitializers?: { scriptName: string, exports: any }[]; From b30cb93a5d5887cb08f9d78ac1d287da051c581b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 16:11:06 +0200 Subject: [PATCH 44/65] Use ResourceRequest in loadResource --- src/mono/wasm/runtime/lazyLoading.ts | 7 ++-- src/mono/wasm/runtime/loader/assets.ts | 2 +- .../wasm/runtime/loader/resourceLoader.ts | 42 ++++++++++--------- src/mono/wasm/runtime/satelliteAssemblies.ts | 2 +- src/mono/wasm/runtime/types/internal.ts | 4 +- 5 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/mono/wasm/runtime/lazyLoading.ts b/src/mono/wasm/runtime/lazyLoading.ts index 937c7881450e77..0d2c3629b3dec0 100644 --- a/src/mono/wasm/runtime/lazyLoading.ts +++ b/src/mono/wasm/runtime/lazyLoading.ts @@ -19,18 +19,17 @@ export async function loadLazyAssembly(assemblyNameToLoad: string): Promise response.arrayBuffer()); + const dllBytesPromise = loaderHelpers.loadResource(assemblyAsset).response.then(response => response.arrayBuffer()); let dll = null; let pdb = null; if (shouldLoadPdb) { const pdbAsset = loaderHelpers.getAssetByNameWithResolvedUrl(lazyAssemblies, "pdb", assemblyNameToLoad); const pdbBytesPromise = pdbAsset - ? await loaderHelpers.loadResource(pdbNameToLoad, pdbAsset.resolvedUrl!, pdbAsset.hash ?? "", pdbAsset.behavior).response.then(response => response.arrayBuffer()) + ? await loaderHelpers.loadResource(pdbAsset).response.then(response => response.arrayBuffer()) : Promise.resolve(null); const [dllBytes, pdbBytes] = await Promise.all([dllBytesPromise, pdbBytesPromise]); diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index a31ad8fceec1a9..787f1872d378d3 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -544,7 +544,7 @@ const totalResources = new Set(); function download_resource(request: ResourceRequest): LoadingResource { try { - const response = loadResource(request.name, request.resolvedUrl!, request.hash!, request.behavior); + const response = loadResource(request); totalResources.add(request.name!); response.response.then(() => { diff --git a/src/mono/wasm/runtime/loader/resourceLoader.ts b/src/mono/wasm/runtime/loader/resourceLoader.ts index 9be8d7798a347d..14bbfc57e6e33d 100644 --- a/src/mono/wasm/runtime/loader/resourceLoader.ts +++ b/src/mono/wasm/runtime/loader/resourceLoader.ts @@ -1,7 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { AssetBehaviors, MonoConfig, WebAssemblyBootResourceType } from "../types"; +import { mono_assert } from "./globals"; +import type { AssetBehaviors, MonoConfig, ResourceRequest, WebAssemblyBootResourceType } from "../types"; import { loaderHelpers } from "./globals"; const networkFetchCacheMode = "no-cache"; @@ -21,17 +22,19 @@ const monoToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType | u "dotnetwasm": "dotnetwasm", }; -export function loadResource(name: string, url: string, contentHash: string, behavior: AssetBehaviors): LoadingResource { - const response = cacheIfUsed && !cacheSkipAssetBehaviors.includes(behavior) - ? loadResourceWithCaching(cacheIfUsed, name, url, contentHash, behavior) - : loadResourceWithoutCaching(name, url, contentHash, behavior); +export function loadResource(request: ResourceRequest): LoadingResource { + mono_assert(request.resolvedUrl, "Request's resolvedUrl must be set"); - const absoluteUrl = loaderHelpers.locateFile(url); + const response = cacheIfUsed && !cacheSkipAssetBehaviors.includes(request.behavior) + ? loadResourceWithCaching(cacheIfUsed, request) + : loadResourceWithoutCaching(request); - if (behavior == "assembly") { + const absoluteUrl = loaderHelpers.locateFile(request.resolvedUrl); + + if (request.behavior == "assembly") { loaderHelpers.loadedAssemblies.push(absoluteUrl); } - return { name, url: absoluteUrl, response }; + return { name: request.name, url: absoluteUrl, response }; } export function logToConsole(): void { @@ -87,15 +90,15 @@ export async function purgeUnusedCacheEntriesAsync(): Promise { } } -async function loadResourceWithCaching(cache: Cache, name: string, url: string, contentHash: string, behavior: AssetBehaviors) { +async function loadResourceWithCaching(cache: Cache, request: ResourceRequest) { // Since we are going to cache the response, we require there to be a content hash for integrity // checking. We don't want to cache bad responses. There should always be a hash, because the build // process generates this data. - if (!contentHash || contentHash.length === 0) { + if (!request.hash || request.hash.length === 0) { throw new Error("Content hash is required"); } - const cacheKey = loaderHelpers.locateFile(`${url}.${contentHash}`); + const cacheKey = loaderHelpers.locateFile(`${request.resolvedUrl}.${request.hash}`); usedCacheKeys[cacheKey] = true; let cachedResponse: Response | undefined; @@ -109,22 +112,23 @@ async function loadResourceWithCaching(cache: Cache, name: string, url: string, if (cachedResponse) { // It's in the cache. const responseBytes = parseInt(cachedResponse.headers.get("content-length") || "0"); - cacheLoads[name] = { responseBytes }; + cacheLoads[request.name] = { responseBytes }; return cachedResponse; } else { // It's not in the cache. Fetch from network. - const networkResponse = await loadResourceWithoutCaching(name, url, contentHash, behavior); - addToCacheAsync(cache, name, cacheKey, networkResponse); // Don't await - add to cache in background + const networkResponse = await loadResourceWithoutCaching(request); + addToCacheAsync(cache, request.name, cacheKey, networkResponse); // Don't await - add to cache in background return networkResponse; } } -function loadResourceWithoutCaching(name: string, url: string, contentHash: string, behavior: AssetBehaviors): Promise { +function loadResourceWithoutCaching(request: ResourceRequest): Promise { // Allow developers to override how the resource is loaded + let url = request.resolvedUrl!; if (loaderHelpers.loadBootResource) { - const resourceType = monoToBlazorAssetTypeMap[behavior]; + const resourceType = monoToBlazorAssetTypeMap[request.behavior]; if (resourceType) { - const customLoadResult = loaderHelpers.loadBootResource(resourceType!, name, url, contentHash); + const customLoadResult = loaderHelpers.loadBootResource(resourceType!, request.name, url, request.hash ?? ""); if (customLoadResult instanceof Promise) { // They are supplying an entire custom response, so just use that return customLoadResult; @@ -142,12 +146,12 @@ function loadResourceWithoutCaching(name: string, url: string, contentHash: stri cache: networkFetchCacheMode }; - if (credentialsIncludeAssetBehaviors.includes(behavior)) { + if (credentialsIncludeAssetBehaviors.includes(request.behavior)) { // Include credentials so the server can allow download / provide user specific file fetchOptions.credentials = "include"; } else { // Any other resource than configuration should provide integrity check - fetchOptions.integrity = cacheIfUsed ? contentHash : undefined; + fetchOptions.integrity = cacheIfUsed ? (request.hash ?? "") : undefined; } return loaderHelpers.fetch_like(url, fetchOptions); diff --git a/src/mono/wasm/runtime/satelliteAssemblies.ts b/src/mono/wasm/runtime/satelliteAssemblies.ts index 1ce469973bccaf..899f5cbc6f0583 100644 --- a/src/mono/wasm/runtime/satelliteAssemblies.ts +++ b/src/mono/wasm/runtime/satelliteAssemblies.ts @@ -22,7 +22,7 @@ export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise culture }); - promises.push(loaderHelpers.loadResource(asset.name, asset.resolvedUrl!, asset.hash ?? "", asset.behavior)); + promises.push(loaderHelpers.loadResource(asset)); } return promises; diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 75703dfd8dfd8e..9da21d5a1ac309 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { AssetBehaviors, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, ResourceList, RuntimeAPI } from "."; +import type { AssetBehaviors, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, ResourceList, ResourceRequest, RuntimeAPI } from "."; import type { CharPtr, EmscriptenModule, ManagedPointer, NativePointer, VoidPtr, Int32Ptr } from "./emscripten"; export type GCHandle = { @@ -142,7 +142,7 @@ export type LoaderHelpers = { err(message: string): void; hasDebuggingEnabled(config: MonoConfig): boolean, - loadResource(name: string, url: string, contentHash: string, behavior: AssetBehaviors): LoadingResource, + loadResource(request: ResourceRequest): LoadingResource, onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; ensureAssetResolvedUrl(asset: AssetEntry): AssetEntry; From 317a96c037abab4854cdd23d2e0482b6f73e3a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 17:28:49 +0200 Subject: [PATCH 45/65] Fix WBT --- src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs index cda9b5414ac7ac..d197f0aca2ee34 100644 --- a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs @@ -390,10 +390,10 @@ public void AssertBootJson(AssertBundleOptionsBase options) Assert.True(File.Exists(bootJsonPath), $"Expected to find {bootJsonPath}"); BootJsonData bootJson = ParseBootData(bootJsonPath); - var bootJsonEntries = bootJson.resources.native.jsModuleNative.Keys - .Union(bootJson.resources.native.jsModuleRuntime.Keys) - .Union(bootJson.resources.native.jsModuleWorker.Keys) - .Union(bootJson.resources.native.wasmNative.Keys) + var bootJsonEntries = bootJson.resources.jsModuleNative.Keys + .Union(bootJson.resources.jsModuleRuntime.Keys) + .Union(bootJson.resources.jsModuleWorker.Keys) + .Union(bootJson.resources.wasmNative.Keys) .ToArray(); var expectedEntries = new SortedDictionary>(); From 1054b1912b710040b6af852698fe71e7e8be8b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 17:29:01 +0200 Subject: [PATCH 46/65] Drop unused isString --- src/mono/wasm/runtime/loader/assets.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index 787f1872d378d3..3bad49458e0d04 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -75,12 +75,11 @@ function getFirstKey(resources: ResourceList | undefined): string | null { } function getFirstAssetWithResolvedUrl(resources: ResourceList | undefined, behavior: SingleAssetBehaviors): AssetEntry { - const isString = typeof (resources) === "string"; - const name = isString ? resources : getFirstKey(resources); + const name = getFirstKey(resources); mono_assert(name, `Can't find ${behavior} in resources`); return ensureAssetResolvedUrl({ name, - hash: isString ? undefined : resources![name], + hash: resources![name], behavior }); } From 8d1eec89e7025171c46cb7ad93eae5a547ca648b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 17:54:13 +0200 Subject: [PATCH 47/65] Fix resource assemblies --- src/mono/wasm/runtime/loader/assets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index 3bad49458e0d04..88b98cfba4c028 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -98,7 +98,7 @@ export function getAssetByNameWithResolvedUrl(resources: ResourceList | undefine export function ensureAssetResolvedUrl(asset: AssetEntry): AssetEntry { if (!asset.resolvedUrl) { - asset.resolvedUrl = appendUniqueQuery(loaderHelpers.locateFile(asset.name), asset.behavior); + asset.resolvedUrl = appendUniqueQuery(loaderHelpers.locateFile(asset.behavior === "resource" ? `${asset.culture}/${asset.name}` : asset.name), asset.behavior); } return asset; From de7f74b4abaf4dfe4b71fa049856fd0453803c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 21:42:37 +0200 Subject: [PATCH 48/65] Rename config to appsettings --- src/mono/wasm/runtime/dotnet.d.ts | 2 +- src/mono/wasm/runtime/loader/assets.ts | 6 +++--- src/mono/wasm/runtime/types/index.ts | 2 +- .../BootJsonData.cs | 8 ++++++++ .../GenerateWasmBootJson.cs | 15 ++++++++++++--- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 2 +- 6 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 1c73fb9ee69c6e..2b46f16c8a206e 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -162,7 +162,7 @@ type MonoConfig = { /** * appsettings files to load to VFS */ - config?: string[]; + appsettings?: string[]; /** * config extensions declared in MSBuild items @(WasmBootConfigExtension) */ diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index 88b98cfba4c028..abeae847050560 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -308,9 +308,9 @@ function prepareAssets(containedInSnapshotAssets: AssetEntryInternal[], alwaysLo } } - if (config.config) { - for (let i = 0; i < config.config.length; i++) { - const configUrl = config.config[i]; + if (config.appsettings) { + for (let i = 0; i < config.appsettings.length; i++) { + const configUrl = config.appsettings[i]; const configFileName = fileName(configUrl); if (configFileName === "appsettings.json" || configFileName === `appsettings.${config.applicationEnvironment}.json`) { alwaysLoadedAssets.push(ensureAssetResolvedUrl({ diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 9523f51fa69628..d69d111fc4bd7e 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -99,7 +99,7 @@ export type MonoConfig = { /** * appsettings files to load to VFS */ - config?: string[]; + appsettings?: string[]; /** * config extensions declared in MSBuild items @(WasmBootConfigExtension) diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs index 34b852872862d6..0629ba9ccc0f68 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -63,8 +63,16 @@ public string mainAssemblyName /// /// Config files for the application /// + /// + /// Deprecated in .NET 8, use + /// public List config { get; set; } + /// + /// Config files for the application + /// + public List appsettings { get; set; } + /// /// Gets or sets the that determines how icu files are loaded. /// diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index 3201dd1d1c5781..b8f236a8ac6b16 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -98,11 +98,15 @@ public void WriteBootJson(Stream output, string entryAssemblyName) debugLevel = ParseOptionalInt(DebugLevel) ?? (DebugBuild ? 1 : 0), linkerEnabled = LinkerEnabled, resources = new ResourcesData(), - config = new List(), icuDataMode = GetGlobalizationMode(), startupMemoryCache = ParseOptionalBool(StartupMemoryCache), }; + if (IsTargeting80OrLater()) + result.config = new List(); + else + result.appsettings = new List(); + if (!string.IsNullOrEmpty(RuntimeOptions)) { string[] runtimeOptions = RuntimeOptions.Split(' '); @@ -315,9 +319,14 @@ public void WriteBootJson(Stream output, string entryAssemblyName) { string configUrl = Path.GetFileName(configFile.ItemSpec); if (IsTargeting80OrLater()) + { configUrl = "../" + configUrl; // This needs condition once WasmRuntimeAssetsLocation is supported in Wasm SDK - - result.config.Add(configUrl); + result.appsettings.Add(configUrl); + } + else + { + result.config.Add(configUrl); + } } } diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index a651150f0112f2..341623034616e3 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -97,7 +97,7 @@ protected override bool ExecuteInternal() var bootConfig = new BootJsonData() { - config = new(), + appsettings = new(), entryAssembly = MainAssemblyName, icuDataMode = GetGlobalizationMode() }; From e855052c84c639a39afd8ef01fa09c5a5b0e087c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 27 Jul 2023 22:45:31 +0200 Subject: [PATCH 49/65] Use more standard way to get URL and download asset --- src/mono/wasm/runtime/lazyLoading.ts | 25 ++++-- src/mono/wasm/runtime/loader/assets.ts | 87 ++++++++------------ src/mono/wasm/runtime/loader/globals.ts | 7 +- src/mono/wasm/runtime/satelliteAssemblies.ts | 16 ++-- src/mono/wasm/runtime/types/internal.ts | 6 +- 5 files changed, 62 insertions(+), 79 deletions(-) diff --git a/src/mono/wasm/runtime/lazyLoading.ts b/src/mono/wasm/runtime/lazyLoading.ts index 0d2c3629b3dec0..5ebdb61511888d 100644 --- a/src/mono/wasm/runtime/lazyLoading.ts +++ b/src/mono/wasm/runtime/lazyLoading.ts @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { loaderHelpers, runtimeHelpers } from "./globals"; +import { AssetEntry } from "./types"; export async function loadLazyAssembly(assemblyNameToLoad: string): Promise { const resources = loaderHelpers.config.resources!; @@ -10,26 +11,34 @@ export async function loadLazyAssembly(assemblyNameToLoad: string): Promise f.includes(assemblyNameToLoad))) { return false; } - const pdbNameToLoad = changeExtension(assemblyAsset.name, ".pdb"); - const shouldLoadPdb = loaderHelpers.hasDebuggingEnabled(loaderHelpers.config) && resources.pdb && Object.prototype.hasOwnProperty.call(lazyAssemblies, pdbNameToLoad); + const pdbNameToLoad = changeExtension(dllAsset.name, ".pdb"); + const shouldLoadPdb = loaderHelpers.hasDebuggingEnabled(loaderHelpers.config) && Object.prototype.hasOwnProperty.call(lazyAssemblies, pdbNameToLoad); - const dllBytesPromise = loaderHelpers.loadResource(assemblyAsset).response.then(response => response.arrayBuffer()); + const dllBytesPromise = loaderHelpers.retrieve_asset_download(dllAsset).then(response => response.arrayBuffer()); let dll = null; let pdb = null; if (shouldLoadPdb) { - const pdbAsset = loaderHelpers.getAssetByNameWithResolvedUrl(lazyAssemblies, "pdb", assemblyNameToLoad); - const pdbBytesPromise = pdbAsset - ? await loaderHelpers.loadResource(pdbAsset).response.then(response => response.arrayBuffer()) + const pdbBytesPromise = lazyAssemblies[pdbNameToLoad] + ? loaderHelpers.retrieve_asset_download({ + name: pdbNameToLoad, + hash: lazyAssemblies[pdbNameToLoad], + behavior: "pdb" + }).then(response => response.arrayBuffer()) : Promise.resolve(null); const [dllBytes, pdbBytes] = await Promise.all([dllBytesPromise, pdbBytesPromise]); diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index abeae847050560..671ab503b4fada 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -64,59 +64,32 @@ export function shouldLoadIcuAsset(asset: AssetEntryInternal): boolean { return !(asset.behavior == "icu" && asset.name != loaderHelpers.preferredIcuAsset); } -function getFirstKey(resources: ResourceList | undefined): string | null { - if (resources != null) { - for (const name in resources) { - return name; - } - } - - return null; -} +function getSingleAssetWithResolvedUrl(resources: ResourceList | undefined, behavior: SingleAssetBehaviors): AssetEntry { + const keys = Object.keys(resources || {}); + mono_assert(keys.length == 1, `Expect to have one ${behavior} asset in resources`); -function getFirstAssetWithResolvedUrl(resources: ResourceList | undefined, behavior: SingleAssetBehaviors): AssetEntry { - const name = getFirstKey(resources); - mono_assert(name, `Can't find ${behavior} in resources`); - return ensureAssetResolvedUrl({ + const name = keys[0]; + return { name, hash: resources![name], - behavior - }); -} - -export function getAssetByNameWithResolvedUrl(resources: ResourceList | undefined, behavior: AssetBehaviors, name: string): AssetEntry | undefined { - if (!resources || !resources[name]) { - return undefined; - } - - return ensureAssetResolvedUrl({ - name: name, - hash: resources[name], - behavior - }); -} - -export function ensureAssetResolvedUrl(asset: AssetEntry): AssetEntry { - if (!asset.resolvedUrl) { - asset.resolvedUrl = appendUniqueQuery(loaderHelpers.locateFile(asset.behavior === "resource" ? `${asset.culture}/${asset.name}` : asset.name), asset.behavior); - } - - return asset; + behavior, + resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), behavior) + }; } export function resolve_asset_path(behavior: SingleAssetBehaviors): AssetEntryInternal { const resources = loaderHelpers.config.resources; - mono_assert(resources, () => "Can't find native resources in config"); + mono_assert(resources, "Can't find resources in config"); switch (behavior) { case "dotnetwasm": - return getFirstAssetWithResolvedUrl(resources.wasmNative, behavior); + return getSingleAssetWithResolvedUrl(resources.wasmNative, behavior); case "js-module-threads": - return getFirstAssetWithResolvedUrl(resources.jsModuleWorker, behavior); + return getSingleAssetWithResolvedUrl(resources.jsModuleWorker, behavior); case "js-module-native": - return getFirstAssetWithResolvedUrl(resources.jsModuleNative, behavior); + return getSingleAssetWithResolvedUrl(resources.jsModuleNative, behavior); case "js-module-runtime": - return getFirstAssetWithResolvedUrl(resources.jsModuleRuntime, behavior); + return getSingleAssetWithResolvedUrl(resources.jsModuleRuntime, behavior); } } @@ -239,33 +212,33 @@ function prepareAssets(containedInSnapshotAssets: AssetEntryInternal[], alwaysLo if (resources) { if (resources.assembly) { for (const name in resources.assembly) { - containedInSnapshotAssets.push(ensureAssetResolvedUrl({ + containedInSnapshotAssets.push({ name, hash: resources.assembly[name], behavior: "assembly" - })); + }); } } if (config.debugLevel != 0 && resources.pdb) { for (const name in resources.pdb) { - containedInSnapshotAssets.push(ensureAssetResolvedUrl({ + containedInSnapshotAssets.push({ name, hash: resources.pdb[name], behavior: "pdb" - })); + }); } } if (config.loadAllSatelliteResources && resources.satelliteResources) { for (const culture in resources.satelliteResources) { for (const name in resources.satelliteResources[culture]) { - containedInSnapshotAssets.push(ensureAssetResolvedUrl({ + containedInSnapshotAssets.push({ name, hash: resources.satelliteResources[culture][name], behavior: "resource", culture - })); + }); } } } @@ -273,12 +246,12 @@ function prepareAssets(containedInSnapshotAssets: AssetEntryInternal[], alwaysLo if (resources.vfs) { for (const virtualPath in resources.vfs) { for (const name in resources.vfs[virtualPath]) { - alwaysLoadedAssets.push(ensureAssetResolvedUrl({ + alwaysLoadedAssets.push({ name, hash: resources.vfs[virtualPath][name], behavior: "vfs", virtualPath - })); + }); } } } @@ -287,23 +260,23 @@ function prepareAssets(containedInSnapshotAssets: AssetEntryInternal[], alwaysLo if (icuDataResourceName && resources.icu) { for (const name in resources.icu) { if (name === icuDataResourceName) { - containedInSnapshotAssets.push(ensureAssetResolvedUrl({ + containedInSnapshotAssets.push({ name, hash: resources.icu[name], behavior: "icu", loadRemote: true - })); + }); } } } if (resources.jsSymbols) { for (const name in resources.jsSymbols) { - alwaysLoadedAssets.push(ensureAssetResolvedUrl({ + alwaysLoadedAssets.push({ name, hash: resources.jsSymbols[name], behavior: "symbols" - })); + }); } } } @@ -313,11 +286,11 @@ function prepareAssets(containedInSnapshotAssets: AssetEntryInternal[], alwaysLo const configUrl = config.appsettings[i]; const configFileName = fileName(configUrl); if (configFileName === "appsettings.json" || configFileName === `appsettings.${config.applicationEnvironment}.json`) { - alwaysLoadedAssets.push(ensureAssetResolvedUrl({ + alwaysLoadedAssets.push({ name: configFileName, resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(configUrl), "vfs"), behavior: "vfs" - })); + }); } } } @@ -353,6 +326,12 @@ export function delay(ms: number): Promise { return new Promise(resolve => globalThis.setTimeout(resolve, ms)); } +export async function retrieve_asset_download(asset: AssetEntry): Promise { + const pendingAsset = await start_asset_download(asset); + const assetResponse = await pendingAsset.pendingDownload!.response; + return assetResponse; +} + // FIXME: Connection reset is probably the only good one for which we should retry export async function start_asset_download(asset: AssetEntryInternal): Promise { try { diff --git a/src/mono/wasm/runtime/loader/globals.ts b/src/mono/wasm/runtime/loader/globals.ts index b315d0205bbb1b..3652adb53420d1 100644 --- a/src/mono/wasm/runtime/loader/globals.ts +++ b/src/mono/wasm/runtime/loader/globals.ts @@ -7,10 +7,9 @@ import type { AssetEntryInternal, GlobalObjects, LoaderHelpers, RuntimeHelpers } import type { MonoConfig, RuntimeAPI } from "../types"; import { assert_runtime_running, is_exited, is_runtime_running, mono_exit } from "./exit"; import { assertIsControllablePromise, createPromiseController, getPromiseController } from "./promise-controller"; -import { ensureAssetResolvedUrl, getAssetByNameWithResolvedUrl, mono_download_assets, resolve_asset_path } from "./assets"; +import { mono_download_assets, resolve_asset_path, retrieve_asset_download } from "./assets"; import { mono_log_error, setup_proxy_console } from "./logging"; import { invokeLibraryInitializers } from "./libraryInitializers"; -import { loadResource } from "./resourceLoader"; import { hasDebuggingEnabled } from "./config"; export const ENVIRONMENT_IS_NODE = typeof process == "object" && typeof process.versions == "object" && typeof process.versions.node == "string"; @@ -95,9 +94,7 @@ export function setLoaderGlobals( setup_proxy_console, hasDebuggingEnabled, - ensureAssetResolvedUrl, - getAssetByNameWithResolvedUrl, - loadResource, + retrieve_asset_download, invokeLibraryInitializers, // from wasm-feature-detect npm package diff --git a/src/mono/wasm/runtime/satelliteAssemblies.ts b/src/mono/wasm/runtime/satelliteAssemblies.ts index 899f5cbc6f0583..cd7c75491796cd 100644 --- a/src/mono/wasm/runtime/satelliteAssemblies.ts +++ b/src/mono/wasm/runtime/satelliteAssemblies.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { loaderHelpers, runtimeHelpers } from "./globals"; -import { LoadingResource } from "./types"; +import { AssetEntry } from "./types"; export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise { const satelliteResources = loaderHelpers.config.resources!.satelliteResources; @@ -13,23 +13,23 @@ export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise await Promise.all(culturesToLoad! .filter(culture => Object.prototype.hasOwnProperty.call(satelliteResources, culture)) .map(culture => { - const promises: LoadingResource[] = []; + const promises: Promise[] = []; for (const name in satelliteResources[culture]) { - const asset = loaderHelpers.ensureAssetResolvedUrl({ + const asset: AssetEntry = { name, hash: satelliteResources[culture][name], behavior: "resource", culture - }); + }; - promises.push(loaderHelpers.loadResource(asset)); + promises.push(loaderHelpers.retrieve_asset_download(asset)); } return promises; }) - .reduce((previous, next) => previous.concat(next), new Array()) - .map(async resource => { - const response = await resource.response; + .reduce((previous, next) => previous.concat(next), new Array>()) + .map(async responsePromise => { + const response = await responsePromise; const bytes = await response.arrayBuffer(); runtimeHelpers.javaScriptExports.load_satellite_assembly(new Uint8Array(bytes)); })); diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 9da21d5a1ac309..9e5667fa07b3e4 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { AssetBehaviors, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, ResourceList, ResourceRequest, RuntimeAPI } from "."; +import type { AssetBehaviors, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, RuntimeAPI } from "."; import type { CharPtr, EmscriptenModule, ManagedPointer, NativePointer, VoidPtr, Int32Ptr } from "./emscripten"; export type GCHandle = { @@ -142,11 +142,9 @@ export type LoaderHelpers = { err(message: string): void; hasDebuggingEnabled(config: MonoConfig): boolean, - loadResource(request: ResourceRequest): LoadingResource, + retrieve_asset_download(asset: AssetEntry): Promise; onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; - ensureAssetResolvedUrl(asset: AssetEntry): AssetEntry; - getAssetByNameWithResolvedUrl(resources: ResourceList | undefined, behavior: AssetBehaviors, requestedName: string): AssetEntry | undefined; loadBootResource?: LoadBootResourceCallback; invokeLibraryInitializers: (functionName: string, args: any[]) => Promise, libraryInitializers?: { scriptName: string, exports: any }[]; From f625263381f0cf05f0188dbcbd883041342f9ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 28 Jul 2023 12:54:33 +0200 Subject: [PATCH 50/65] Extract cache from resource loader. Move loading to assets --- src/mono/wasm/runtime/loader/assets.ts | 68 ++++++++++++- .../{resourceLoader.ts => assetsCache.ts} | 97 +++++-------------- src/mono/wasm/runtime/loader/run.ts | 2 +- 3 files changed, 90 insertions(+), 77 deletions(-) rename src/mono/wasm/runtime/loader/{resourceLoader.ts => assetsCache.ts} (66%) diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index 671ab503b4fada..c437efedbb9678 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -2,12 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { AssetEntryInternal, PromiseAndController } from "../types/internal"; -import type { AssetBehaviors, AssetEntry, LoadingResource, ResourceList, ResourceRequest, SingleAssetBehaviors as SingleAssetBehaviors } from "../types"; +import type { AssetBehaviors, AssetEntry, LoadingResource, ResourceList, ResourceRequest, SingleAssetBehaviors as SingleAssetBehaviors, WebAssemblyBootResourceType } from "../types"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { createPromiseController } from "./promise-controller"; import { mono_log_debug } from "./logging"; import { mono_exit } from "./exit"; -import { loadResource } from "./resourceLoader"; +import { addCachedReponse, findCachedResponse, shouldApplyIntegrity } from "./assetsCache"; import { getIcuResourceName } from "./icu"; @@ -522,10 +522,16 @@ const totalResources = new Set(); function download_resource(request: ResourceRequest): LoadingResource { try { - const response = loadResource(request); + mono_assert(request.resolvedUrl, "Request's resolvedUrl must be set"); + const fetchResponse = download_resource_with_cache(request); + const response = { name: request.name, url: request.resolvedUrl, response: fetchResponse }; totalResources.add(request.name!); response.response.then(() => { + if (request.behavior == "assembly") { + loaderHelpers.loadedAssemblies.push(request.resolvedUrl!); + } + resourcesLoaded++; if (loaderHelpers.onDownloadResourceProgress) loaderHelpers.onDownloadResourceProgress(resourcesLoaded, totalResources.size); @@ -546,6 +552,62 @@ function download_resource(request: ResourceRequest): LoadingResource { } } +async function download_resource_with_cache(request: ResourceRequest): Promise { + let response = await findCachedResponse(request); + if (!response) { + response = await fetchResource(request); + addCachedReponse(request, response); + } + + return response; +} + +const monoToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType | undefined } = { + "resource": "assembly", + "assembly": "assembly", + "pdb": "pdb", + "icu": "globalization", + "vfs": "configuration", + "dotnetwasm": "dotnetwasm", +}; + +const credentialsIncludeAssetBehaviors: AssetBehaviors[] = ["vfs"]; // Previously only configuration + +function fetchResource(request: ResourceRequest): Promise { + // Allow developers to override how the resource is loaded + let url = request.resolvedUrl!; + if (loaderHelpers.loadBootResource) { + const resourceType = monoToBlazorAssetTypeMap[request.behavior]; + if (resourceType) { + const customLoadResult = loaderHelpers.loadBootResource(resourceType!, request.name, url, request.hash ?? ""); + if (customLoadResult instanceof Promise) { + // They are supplying an entire custom response, so just use that + return customLoadResult; + } else if (typeof customLoadResult === "string") { + // They are supplying a custom URL, so use that with the default fetch behavior + url = customLoadResult; + } + } + } + + // Note that if cacheBootResources was explicitly disabled, we also bypass hash checking + // This is to give developers an easy opt-out from the entire caching/validation flow if + // there's anything they don't like about it. + const fetchOptions: RequestInit = { + cache: "no-cache" + }; + + if (credentialsIncludeAssetBehaviors.includes(request.behavior)) { + // Include credentials so the server can allow download / provide user specific file + fetchOptions.credentials = "include"; + } else { + // Any other resource than configuration should provide integrity check + fetchOptions.integrity = shouldApplyIntegrity() ? (request.hash ?? "") : undefined; + } + + return loaderHelpers.fetch_like(url, fetchOptions); +} + export function cleanupAsset(asset: AssetEntryInternal) { // give GC chance to collect resources asset.pendingDownloadInternal = null as any; // GC diff --git a/src/mono/wasm/runtime/loader/resourceLoader.ts b/src/mono/wasm/runtime/loader/assetsCache.ts similarity index 66% rename from src/mono/wasm/runtime/loader/resourceLoader.ts rename to src/mono/wasm/runtime/loader/assetsCache.ts index 14bbfc57e6e33d..a9cbb3174c9f43 100644 --- a/src/mono/wasm/runtime/loader/resourceLoader.ts +++ b/src/mono/wasm/runtime/loader/assetsCache.ts @@ -1,40 +1,17 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { mono_assert } from "./globals"; -import type { AssetBehaviors, MonoConfig, ResourceRequest, WebAssemblyBootResourceType } from "../types"; +import type { AssetBehaviors, MonoConfig, ResourceRequest } from "../types"; import { loaderHelpers } from "./globals"; -const networkFetchCacheMode = "no-cache"; const cacheSkipAssetBehaviors: AssetBehaviors[] = ["vfs"]; // Previously only configuration -const credentialsIncludeAssetBehaviors: AssetBehaviors[] = ["vfs"]; // Previously only configuration const usedCacheKeys: { [key: string]: boolean } = {}; const networkLoads: { [name: string]: LoadLogEntry } = {}; const cacheLoads: { [name: string]: LoadLogEntry } = {}; let cacheIfUsed: Cache | null; -const monoToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType | undefined } = { - "resource": "assembly", - "assembly": "assembly", - "pdb": "pdb", - "icu": "globalization", - "vfs": "configuration", - "dotnetwasm": "dotnetwasm", -}; - -export function loadResource(request: ResourceRequest): LoadingResource { - mono_assert(request.resolvedUrl, "Request's resolvedUrl must be set"); - - const response = cacheIfUsed && !cacheSkipAssetBehaviors.includes(request.behavior) - ? loadResourceWithCaching(cacheIfUsed, request) - : loadResourceWithoutCaching(request); - - const absoluteUrl = loaderHelpers.locateFile(request.resolvedUrl); - - if (request.behavior == "assembly") { - loaderHelpers.loadedAssemblies.push(absoluteUrl); - } - return { name: request.name, url: absoluteUrl, response }; +export function shouldApplyIntegrity(): boolean { + return !!cacheIfUsed; } export function logToConsole(): void { @@ -90,15 +67,13 @@ export async function purgeUnusedCacheEntriesAsync(): Promise { } } -async function loadResourceWithCaching(cache: Cache, request: ResourceRequest) { - // Since we are going to cache the response, we require there to be a content hash for integrity - // checking. We don't want to cache bad responses. There should always be a hash, because the build - // process generates this data. - if (!request.hash || request.hash.length === 0) { - throw new Error("Content hash is required"); +export async function findCachedResponse(request: ResourceRequest): Promise { + const cache = cacheIfUsed; + if (!cache || cacheSkipAssetBehaviors.includes(request.behavior) || !request.hash || request.hash.length === 0) { + return undefined; } - const cacheKey = loaderHelpers.locateFile(`${request.resolvedUrl}.${request.hash}`); + const cacheKey = getCacheKey(request); usedCacheKeys[cacheKey] = true; let cachedResponse: Response | undefined; @@ -109,52 +84,28 @@ async function loadResourceWithCaching(cache: Cache, request: ResourceRequest) { // chromium browsers may sometimes throw when working with the cache. } - if (cachedResponse) { - // It's in the cache. - const responseBytes = parseInt(cachedResponse.headers.get("content-length") || "0"); - cacheLoads[request.name] = { responseBytes }; - return cachedResponse; - } else { - // It's not in the cache. Fetch from network. - const networkResponse = await loadResourceWithoutCaching(request); - addToCacheAsync(cache, request.name, cacheKey, networkResponse); // Don't await - add to cache in background - return networkResponse; + if (!cachedResponse) { + return undefined; } + + // It's in the cache. + const responseBytes = parseInt(cachedResponse.headers.get("content-length") || "0"); + cacheLoads[request.name] = { responseBytes }; + return cachedResponse; } -function loadResourceWithoutCaching(request: ResourceRequest): Promise { - // Allow developers to override how the resource is loaded - let url = request.resolvedUrl!; - if (loaderHelpers.loadBootResource) { - const resourceType = monoToBlazorAssetTypeMap[request.behavior]; - if (resourceType) { - const customLoadResult = loaderHelpers.loadBootResource(resourceType!, request.name, url, request.hash ?? ""); - if (customLoadResult instanceof Promise) { - // They are supplying an entire custom response, so just use that - return customLoadResult; - } else if (typeof customLoadResult === "string") { - // They are supplying a custom URL, so use that with the default fetch behavior - url = customLoadResult; - } - } +export function addCachedReponse(request: ResourceRequest, networkResponse: Response): void { + const cache = cacheIfUsed; + if (!cache || cacheSkipAssetBehaviors.includes(request.behavior) || !request.hash || request.hash.length === 0) { + return; } - // Note that if cacheBootResources was explicitly disabled, we also bypass hash checking - // This is to give developers an easy opt-out from the entire caching/validation flow if - // there's anything they don't like about it. - const fetchOptions: RequestInit = { - cache: networkFetchCacheMode - }; - - if (credentialsIncludeAssetBehaviors.includes(request.behavior)) { - // Include credentials so the server can allow download / provide user specific file - fetchOptions.credentials = "include"; - } else { - // Any other resource than configuration should provide integrity check - fetchOptions.integrity = cacheIfUsed ? (request.hash ?? "") : undefined; - } + const cacheKey = getCacheKey(request); + addToCacheAsync(cache, request.name, cacheKey, networkResponse); // Don't await - add to cache in background +} - return loaderHelpers.fetch_like(url, fetchOptions); +function getCacheKey(request: ResourceRequest) { + return `${request.resolvedUrl}.${request.hash}`; } async function addToCacheAsync(cache: Cache, name: string, cacheKey: string, response: Response) { diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts index 7912be2197c942..9372da884e20dc 100644 --- a/src/mono/wasm/runtime/loader/run.ts +++ b/src/mono/wasm/runtime/loader/run.ts @@ -16,7 +16,7 @@ import { runtimeHelpers, loaderHelpers } from "./globals"; import { init_globalization } from "./icu"; import { setupPreloadChannelToMainThread } from "./worker"; import { invokeLibraryInitializers } from "./libraryInitializers"; -import { initCacheToUseIfEnabled } from "./resourceLoader"; +import { initCacheToUseIfEnabled } from "./assetsCache"; const module = globalObjectsRoot.module; const monoConfig = module.config as MonoConfigInternal; From d1f16e939414f52706eabf75f6dccf0aa23a2574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 28 Jul 2023 12:56:35 +0200 Subject: [PATCH 51/65] Rename resolve_asset_path to resolve_single_asset_path --- src/mono/wasm/runtime/loader/assets.ts | 2 +- src/mono/wasm/runtime/loader/globals.ts | 4 ++-- src/mono/wasm/runtime/loader/run.ts | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index c437efedbb9678..a165a57e04f970 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -77,7 +77,7 @@ function getSingleAssetWithResolvedUrl(resources: ResourceList | undefined, beha }; } -export function resolve_asset_path(behavior: SingleAssetBehaviors): AssetEntryInternal { +export function resolve_single_asset_path(behavior: SingleAssetBehaviors): AssetEntryInternal { const resources = loaderHelpers.config.resources; mono_assert(resources, "Can't find resources in config"); diff --git a/src/mono/wasm/runtime/loader/globals.ts b/src/mono/wasm/runtime/loader/globals.ts index 3652adb53420d1..a165d075bda382 100644 --- a/src/mono/wasm/runtime/loader/globals.ts +++ b/src/mono/wasm/runtime/loader/globals.ts @@ -7,7 +7,7 @@ import type { AssetEntryInternal, GlobalObjects, LoaderHelpers, RuntimeHelpers } import type { MonoConfig, RuntimeAPI } from "../types"; import { assert_runtime_running, is_exited, is_runtime_running, mono_exit } from "./exit"; import { assertIsControllablePromise, createPromiseController, getPromiseController } from "./promise-controller"; -import { mono_download_assets, resolve_asset_path, retrieve_asset_download } from "./assets"; +import { mono_download_assets, resolve_single_asset_path, retrieve_asset_download } from "./assets"; import { mono_log_error, setup_proxy_console } from "./logging"; import { invokeLibraryInitializers } from "./libraryInitializers"; import { hasDebuggingEnabled } from "./config"; @@ -90,7 +90,7 @@ export function setLoaderGlobals( getPromiseController, assertIsControllablePromise, mono_download_assets, - resolve_asset_path, + resolve_asset_path: resolve_single_asset_path, setup_proxy_console, hasDebuggingEnabled, diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts index 9372da884e20dc..ce807f34347b6b 100644 --- a/src/mono/wasm/runtime/loader/run.ts +++ b/src/mono/wasm/runtime/loader/run.ts @@ -10,7 +10,7 @@ import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_WEB, exportedRuntimeAPI, globalObje import { deep_merge_config, deep_merge_module, mono_wasm_load_config } from "./config"; import { mono_exit } from "./exit"; import { setup_proxy_console, mono_log_info } from "./logging"; -import { resolve_asset_path, start_asset_download } from "./assets"; +import { resolve_single_asset_path, start_asset_download } from "./assets"; import { detect_features_and_polyfill } from "./polyfills"; import { runtimeHelpers, loaderHelpers } from "./globals"; import { init_globalization } from "./icu"; @@ -429,8 +429,8 @@ export async function createEmscripten(moduleFactory: DotnetModuleConfig | ((api } function importModules() { - runtimeHelpers.runtimeModuleUrl = resolve_asset_path("js-module-runtime").resolvedUrl!; - runtimeHelpers.nativeModuleUrl = resolve_asset_path("js-module-native").resolvedUrl!; + runtimeHelpers.runtimeModuleUrl = resolve_single_asset_path("js-module-runtime").resolvedUrl!; + runtimeHelpers.nativeModuleUrl = resolve_single_asset_path("js-module-native").resolvedUrl!; return [ // keep js module names dynamic by using config, in the future we can use feature detection to load different flavors import(/* webpackIgnore: true */runtimeHelpers.runtimeModuleUrl), @@ -470,7 +470,7 @@ async function createEmscriptenMain(): Promise { await initCacheToUseIfEnabled(); - const wasmModuleAsset = resolve_asset_path("dotnetwasm"); + const wasmModuleAsset = resolve_single_asset_path("dotnetwasm"); start_asset_download(wasmModuleAsset).then(asset => { loaderHelpers.wasmDownloadPromise.promise_control.resolve(asset); }); From 1102ee3bf5f78cc7260967698605431d0b8632e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 28 Jul 2023 13:56:19 +0200 Subject: [PATCH 52/65] Fix logDownloadStatsToConsole and purgeUnusedCacheEntriesAsync --- src/mono/wasm/runtime/loader/assetsCache.ts | 2 +- src/mono/wasm/runtime/loader/globals.ts | 3 +++ src/mono/wasm/runtime/startup.ts | 8 +++----- src/mono/wasm/runtime/types/internal.ts | 2 ++ 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/mono/wasm/runtime/loader/assetsCache.ts b/src/mono/wasm/runtime/loader/assetsCache.ts index a9cbb3174c9f43..aa0f915b0a5d5b 100644 --- a/src/mono/wasm/runtime/loader/assetsCache.ts +++ b/src/mono/wasm/runtime/loader/assetsCache.ts @@ -14,7 +14,7 @@ export function shouldApplyIntegrity(): boolean { return !!cacheIfUsed; } -export function logToConsole(): void { +export function logDownloadStatsToConsole(): void { const cacheLoadsEntries = Object.values(cacheLoads); const networkLoadsEntries = Object.values(networkLoads); const cacheResponseBytes = countTotalBytes(cacheLoadsEntries); diff --git a/src/mono/wasm/runtime/loader/globals.ts b/src/mono/wasm/runtime/loader/globals.ts index a165d075bda382..26a7d708e1f47d 100644 --- a/src/mono/wasm/runtime/loader/globals.ts +++ b/src/mono/wasm/runtime/loader/globals.ts @@ -11,6 +11,7 @@ import { mono_download_assets, resolve_single_asset_path, retrieve_asset_downloa import { mono_log_error, setup_proxy_console } from "./logging"; import { invokeLibraryInitializers } from "./libraryInitializers"; import { hasDebuggingEnabled } from "./config"; +import { logDownloadStatsToConsole, purgeUnusedCacheEntriesAsync } from "./assetsCache"; export const ENVIRONMENT_IS_NODE = typeof process == "object" && typeof process.versions == "object" && typeof process.versions.node == "string"; export const ENVIRONMENT_IS_WEB = typeof window == "object"; @@ -92,6 +93,8 @@ export function setLoaderGlobals( mono_download_assets, resolve_asset_path: resolve_single_asset_path, setup_proxy_console, + logDownloadStatsToConsole, + purgeUnusedCacheEntriesAsync, hasDebuggingEnabled, retrieve_asset_download, diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 69e4d3768e5d93..8f48f78de4b3cb 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -269,12 +269,10 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { if (!runtimeHelpers.mono_wasm_runtime_is_ready) mono_wasm_runtime_ready(); - if (INTERNAL.resourceLoader) { - if (loaderHelpers.config.debugLevel !== 0 && loaderHelpers.config.cacheBootResources) { - INTERNAL.resourceLoader.logToConsole(); - } - INTERNAL.resourceLoader.purgeUnusedCacheEntriesAsync(); // Don't await - it's fine to run in background + if (loaderHelpers.config.debugLevel !== 0 && loaderHelpers.config.cacheBootResources) { + loaderHelpers.logDownloadStatsToConsole(); } + loaderHelpers.purgeUnusedCacheEntriesAsync(); // Don't await - it's fine to run in background // call user code try { diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 9e5667fa07b3e4..9a0a6a4703d0d4 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -144,6 +144,8 @@ export type LoaderHelpers = { hasDebuggingEnabled(config: MonoConfig): boolean, retrieve_asset_download(asset: AssetEntry): Promise; onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; + logDownloadStatsToConsole: () => void; + purgeUnusedCacheEntriesAsync: () => Promise; loadBootResource?: LoadBootResourceCallback; invokeLibraryInitializers: (functionName: string, args: any[]) => Promise, From d04c549511e20ed8cb9facdd34c6e78aaa755c43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 28 Jul 2023 14:00:00 +0200 Subject: [PATCH 53/65] Stop duplicating values to deprecated fields --- .../BootJsonData.cs | 14 +++----------- .../GenerateWasmBootJson.cs | 14 ++++++++++---- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 4 ++-- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs index 0629ba9ccc0f68..c3f1af4ec4b17b 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -22,11 +22,7 @@ public class BootJsonData /// public string entryAssembly { get; set; } - public string mainAssemblyName - { - get => entryAssembly; - set => entryAssembly = value; - } + public string mainAssemblyName { get; set; } /// /// Gets the set of resources needed to boot the application. This includes the transitive @@ -79,16 +75,12 @@ public string mainAssemblyName /// /// Deprecated since .NET 8. Use instead. /// - public GlobalizationMode icuDataMode { get; set; } + public GlobalizationMode? icuDataMode { get; set; } /// /// Gets or sets the that determines how icu files are loaded. /// - public string globalizationMode - { - get => icuDataMode.ToString().ToLowerInvariant(); - set { } - } + public string globalizationMode { get; set; } /// /// Gets or sets a value that determines if the caching startup memory is enabled. diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index b8f236a8ac6b16..3d82d64c719392 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -92,20 +92,26 @@ public void WriteBootJson(Stream output, string entryAssemblyName) var result = new BootJsonData { - entryAssembly = entryAssemblyName, cacheBootResources = CacheBootResources, debugBuild = DebugBuild, debugLevel = ParseOptionalInt(DebugLevel) ?? (DebugBuild ? 1 : 0), linkerEnabled = LinkerEnabled, resources = new ResourcesData(), - icuDataMode = GetGlobalizationMode(), startupMemoryCache = ParseOptionalBool(StartupMemoryCache), }; if (IsTargeting80OrLater()) - result.config = new List(); + { + result.appsettings = new(); + result.mainAssemblyName = entryAssemblyName; + result.globalizationMode = GetGlobalizationMode().ToString().ToLowerInvariant(); + } else - result.appsettings = new List(); + { + result.config = new(); + result.entryAssembly = entryAssemblyName; + result.icuDataMode = GetGlobalizationMode(); + } if (!string.IsNullOrEmpty(RuntimeOptions)) { diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 341623034616e3..820aacfb7b9484 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -98,8 +98,8 @@ protected override bool ExecuteInternal() var bootConfig = new BootJsonData() { appsettings = new(), - entryAssembly = MainAssemblyName, - icuDataMode = GetGlobalizationMode() + mainAssemblyName = MainAssemblyName, + globalizationMode = GetGlobalizationMode().ToString().ToLowerInvariant() }; // Create app From a87bd45320e586ca53dedc36bf146fcb60ac8913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 28 Jul 2023 14:25:31 +0200 Subject: [PATCH 54/65] Use relaxed encoding and skip nulls --- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 820aacfb7b9484..c13e615f651c9d 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Text; +using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; @@ -365,7 +366,13 @@ protected override bool ExecuteInternal() { helper.ComputeResourcesHash(bootConfig); - var json = JsonSerializer.Serialize(bootConfig, new JsonSerializerOptions { WriteIndented = true }); + var jsonOptions = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + WriteIndented = true + }; + var json = JsonSerializer.Serialize(bootConfig, jsonOptions); sw.Write(json); } From 35c0ee4bd5b56b085937da21928375998690ca95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 28 Jul 2023 14:41:03 +0200 Subject: [PATCH 55/65] Omit default values from boot config --- .../BootJsonData.cs | 6 +++--- .../GenerateWasmBootJson.cs | 17 ++++++++++++----- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 2 -- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs index c3f1af4ec4b17b..7bb1930c6f1190 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -39,12 +39,12 @@ public class BootJsonData /// Gets a value that determines whether to enable caching of the /// inside a CacheStorage instance within the browser. /// - public bool cacheBootResources { get; set; } + public bool? cacheBootResources { get; set; } /// /// Gets a value that determines if this is a debug build. /// - public bool debugBuild { get; set; } + public bool? debugBuild { get; set; } /// /// Gets a value that determines what level of debugging is configured. @@ -54,7 +54,7 @@ public class BootJsonData /// /// Gets a value that determines if the linker is enabled. /// - public bool linkerEnabled { get; set; } + public bool? linkerEnabled { get; set; } /// /// Config files for the application diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index 3d82d64c719392..87cfd9e98800a2 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -92,23 +92,28 @@ public void WriteBootJson(Stream output, string entryAssemblyName) var result = new BootJsonData { - cacheBootResources = CacheBootResources, - debugBuild = DebugBuild, - debugLevel = ParseOptionalInt(DebugLevel) ?? (DebugBuild ? 1 : 0), - linkerEnabled = LinkerEnabled, resources = new ResourcesData(), startupMemoryCache = ParseOptionalBool(StartupMemoryCache), }; if (IsTargeting80OrLater()) { - result.appsettings = new(); + result.debugLevel = ParseOptionalInt(DebugLevel) ?? (DebugBuild ? 1 : 0); result.mainAssemblyName = entryAssemblyName; result.globalizationMode = GetGlobalizationMode().ToString().ToLowerInvariant(); + + if (CacheBootResources) + result.cacheBootResources = CacheBootResources; + + if (LinkerEnabled) + result.linkerEnabled = LinkerEnabled; } else { + result.cacheBootResources = CacheBootResources; + result.linkerEnabled = LinkerEnabled; result.config = new(); + result.debugBuild = DebugBuild; result.entryAssembly = entryAssemblyName; result.icuDataMode = GetGlobalizationMode(); } @@ -326,6 +331,8 @@ public void WriteBootJson(Stream output, string entryAssemblyName) string configUrl = Path.GetFileName(configFile.ItemSpec); if (IsTargeting80OrLater()) { + result.appsettings ??= new(); + configUrl = "../" + configUrl; // This needs condition once WasmRuntimeAssetsLocation is supported in Wasm SDK result.appsettings.Add(configUrl); } diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index c13e615f651c9d..9c7d8a6799134e 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -98,7 +98,6 @@ protected override bool ExecuteInternal() var bootConfig = new BootJsonData() { - appsettings = new(), mainAssemblyName = MainAssemblyName, globalizationMode = GetGlobalizationMode().ToString().ToLowerInvariant() }; @@ -206,7 +205,6 @@ protected override bool ExecuteInternal() } } - bootConfig.debugBuild = DebugLevel > 0; bootConfig.debugLevel = DebugLevel; ProcessSatelliteAssemblies(args => From 3661d890aff906278e053f89abe5e54e9c4833c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 28 Jul 2023 15:16:35 +0200 Subject: [PATCH 56/65] Unify resource name in boot config in Wasm SDk --- .../GenerateWasmBootJson.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index 87cfd9e98800a2..eb709945dcc515 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -181,7 +181,9 @@ public void WriteBootJson(Stream output, string entryAssemblyName) { Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as satellite assembly with culture '{1}'.", resource.ItemSpec, assetTraitValue); resourceData.satelliteResources ??= new Dictionary(StringComparer.OrdinalIgnoreCase); - resourceName = assetTraitValue + "/" + resourceName; + + if (!IsTargeting80OrLater()) + resourceName = assetTraitValue + "/" + resourceName; if (!resourceData.satelliteResources.TryGetValue(assetTraitValue, out resourceList)) { From 40fc448a63a21949ec2019bf45569d2bd01ca518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 28 Jul 2023 15:16:47 +0200 Subject: [PATCH 57/65] Fix reading pendingDownloadInternal in retrieve_asset_download --- src/mono/wasm/runtime/loader/assets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index a165a57e04f970..72072f1e0dfaa8 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -328,7 +328,7 @@ export function delay(ms: number): Promise { export async function retrieve_asset_download(asset: AssetEntry): Promise { const pendingAsset = await start_asset_download(asset); - const assetResponse = await pendingAsset.pendingDownload!.response; + const assetResponse = await pendingAsset.pendingDownloadInternal!.response; return assetResponse; } From 6a168ac95e08ca7cf3fa87dd3e681b0396f6fb51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 28 Jul 2023 15:27:50 +0200 Subject: [PATCH 58/65] Fix retrieve_asset_download --- src/mono/wasm/runtime/lazyLoading.ts | 4 ++-- src/mono/wasm/runtime/loader/assets.ts | 6 +++--- src/mono/wasm/runtime/satelliteAssemblies.ts | 9 ++++----- src/mono/wasm/runtime/types/internal.ts | 2 +- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/mono/wasm/runtime/lazyLoading.ts b/src/mono/wasm/runtime/lazyLoading.ts index 5ebdb61511888d..59c153a9d62365 100644 --- a/src/mono/wasm/runtime/lazyLoading.ts +++ b/src/mono/wasm/runtime/lazyLoading.ts @@ -28,7 +28,7 @@ export async function loadLazyAssembly(assemblyNameToLoad: string): Promise response.arrayBuffer()); + const dllBytesPromise = loaderHelpers.retrieve_asset_download(dllAsset); let dll = null; let pdb = null; @@ -38,7 +38,7 @@ export async function loadLazyAssembly(assemblyNameToLoad: string): Promise response.arrayBuffer()) + }) : Promise.resolve(null); const [dllBytes, pdbBytes] = await Promise.all([dllBytesPromise, pdbBytesPromise]); diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index 72072f1e0dfaa8..574544948cd13f 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -326,10 +326,10 @@ export function delay(ms: number): Promise { return new Promise(resolve => globalThis.setTimeout(resolve, ms)); } -export async function retrieve_asset_download(asset: AssetEntry): Promise { +export async function retrieve_asset_download(asset: AssetEntry): Promise { const pendingAsset = await start_asset_download(asset); - const assetResponse = await pendingAsset.pendingDownloadInternal!.response; - return assetResponse; + await pendingAsset.pendingDownloadInternal!.response; + return pendingAsset.buffer!; } // FIXME: Connection reset is probably the only good one for which we should retry diff --git a/src/mono/wasm/runtime/satelliteAssemblies.ts b/src/mono/wasm/runtime/satelliteAssemblies.ts index cd7c75491796cd..100af0696aae4d 100644 --- a/src/mono/wasm/runtime/satelliteAssemblies.ts +++ b/src/mono/wasm/runtime/satelliteAssemblies.ts @@ -13,7 +13,7 @@ export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise await Promise.all(culturesToLoad! .filter(culture => Object.prototype.hasOwnProperty.call(satelliteResources, culture)) .map(culture => { - const promises: Promise[] = []; + const promises: Promise[] = []; for (const name in satelliteResources[culture]) { const asset: AssetEntry = { name, @@ -27,10 +27,9 @@ export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise return promises; }) - .reduce((previous, next) => previous.concat(next), new Array>()) - .map(async responsePromise => { - const response = await responsePromise; - const bytes = await response.arrayBuffer(); + .reduce((previous, next) => previous.concat(next), new Array>()) + .map(async bytesPromise => { + const bytes = await bytesPromise; runtimeHelpers.javaScriptExports.load_satellite_assembly(new Uint8Array(bytes)); })); } \ No newline at end of file diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 9a0a6a4703d0d4..72894a01821a47 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -142,7 +142,7 @@ export type LoaderHelpers = { err(message: string): void; hasDebuggingEnabled(config: MonoConfig): boolean, - retrieve_asset_download(asset: AssetEntry): Promise; + retrieve_asset_download(asset: AssetEntry): Promise; onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; logDownloadStatsToConsole: () => void; purgeUnusedCacheEntriesAsync: () => Promise; From 81b79a6e368752106525317fc14ed2df8c040110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 28 Jul 2023 22:34:10 +0200 Subject: [PATCH 59/65] Feedback --- src/mono/wasm/runtime/dotnet.d.ts | 7 +++++-- src/mono/wasm/runtime/loader/assets.ts | 12 +++++++----- src/mono/wasm/runtime/loader/assetsCache.ts | 4 ++-- src/mono/wasm/runtime/loader/run.ts | 2 +- src/mono/wasm/runtime/types/index.ts | 7 +++++-- 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 2b46f16c8a206e..cab64a803fa4a7 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -194,8 +194,11 @@ interface ResourceGroups { [virtualPath: string]: ResourceList; }; } +/** + * A "key" is name of the file, a "value" is optional hash for integrity check. + */ type ResourceList = { - [name: string]: string; + [name: string]: string | null | ""; }; /** * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched @@ -211,7 +214,7 @@ interface ResourceRequest { name: string; behavior: AssetBehaviors; resolvedUrl?: string; - hash?: string; + hash?: string | null | ""; } interface LoadingResource { name: string; diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index 574544948cd13f..1bfc2fea240d73 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -7,7 +7,7 @@ import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, loaderHelpers, mono_assert, import { createPromiseController } from "./promise-controller"; import { mono_log_debug } from "./logging"; import { mono_exit } from "./exit"; -import { addCachedReponse, findCachedResponse, shouldApplyIntegrity } from "./assetsCache"; +import { addCachedReponse, findCachedResponse, isCacheAvailable } from "./assetsCache"; import { getIcuResourceName } from "./icu"; @@ -90,6 +90,8 @@ export function resolve_single_asset_path(behavior: SingleAssetBehaviors): Asset return getSingleAssetWithResolvedUrl(resources.jsModuleNative, behavior); case "js-module-runtime": return getSingleAssetWithResolvedUrl(resources.jsModuleRuntime, behavior); + default: + throw new Error(`Unknown single asset behavior ${behavior}`); } } @@ -590,9 +592,6 @@ function fetchResource(request: ResourceRequest): Promise { } } - // Note that if cacheBootResources was explicitly disabled, we also bypass hash checking - // This is to give developers an easy opt-out from the entire caching/validation flow if - // there's anything they don't like about it. const fetchOptions: RequestInit = { cache: "no-cache" }; @@ -602,7 +601,10 @@ function fetchResource(request: ResourceRequest): Promise { fetchOptions.credentials = "include"; } else { // Any other resource than configuration should provide integrity check - fetchOptions.integrity = shouldApplyIntegrity() ? (request.hash ?? "") : undefined; + // Note that if cacheBootResources was explicitly disabled, we also bypass hash checking + // This is to give developers an easy opt-out from the entire caching/validation flow if + // there's anything they don't like about it. + fetchOptions.integrity = isCacheAvailable() ? (request.hash ?? "") : undefined; } return loaderHelpers.fetch_like(url, fetchOptions); diff --git a/src/mono/wasm/runtime/loader/assetsCache.ts b/src/mono/wasm/runtime/loader/assetsCache.ts index aa0f915b0a5d5b..b9c44f3fa3ef5d 100644 --- a/src/mono/wasm/runtime/loader/assetsCache.ts +++ b/src/mono/wasm/runtime/loader/assetsCache.ts @@ -10,7 +10,7 @@ const networkLoads: { [name: string]: LoadLogEntry } = {}; const cacheLoads: { [name: string]: LoadLogEntry } = {}; let cacheIfUsed: Cache | null; -export function shouldApplyIntegrity(): boolean { +export function isCacheAvailable(): boolean { return !!cacheIfUsed; } @@ -25,7 +25,7 @@ export function logDownloadStatsToConsole(): void { return; } - const linkerDisabledWarning = loaderHelpers.config.linkerEnabled ? "%c" : "\n%cThis application was built with linking (tree shaking) disabled. Published applications will be significantly smaller."; + const linkerDisabledWarning = loaderHelpers.config.linkerEnabled ? "%c" : "\n%cThis application was built with linking (tree shaking) disabled. Published applications will be significantly smaller if you install wasm-tools workload. See also https://aka.ms/dotnet-wasm-features"; // eslint-disable-next-line no-console console.groupCollapsed(`%cdotnet%c Loaded ${toDataSizeString(totalResponseBytes)} resources${linkerDisabledWarning}`, "background: purple; color: white; padding: 1px 3px; border-radius: 3px;", "font-weight: bold;", "font-weight: normal;"); diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts index ce807f34347b6b..82f0f9988b8d12 100644 --- a/src/mono/wasm/runtime/loader/run.ts +++ b/src/mono/wasm/runtime/loader/run.ts @@ -458,7 +458,7 @@ async function initializeModules(es6Modules: [RuntimeModuleExportsInternal, Nati } async function createEmscriptenMain(): Promise { - if (!module.configSrc && (!module.config || Object.keys(module.config).length === 0 || !module.config.resources)) { + if (!module.configSrc && (!module.config || Object.keys(module.config).length === 0 || !(module.config as MonoConfigInternal).assets || !module.config.resources)) { // if config file location nor assets are provided module.configSrc = "./blazor.boot.json"; } diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index d69d111fc4bd7e..86d8f3240b476a 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -131,7 +131,10 @@ export interface ResourceGroups { readonly vfs?: { [virtualPath: string]: ResourceList }; } -export type ResourceList = { [name: string]: string }; +/** + * A "key" is name of the file, a "value" is optional hash for integrity check. + */ +export type ResourceList = { [name: string]: string | null | "" }; /** * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched @@ -148,7 +151,7 @@ export interface ResourceRequest { name: string, // the name of the asset, including extension. behavior: AssetBehaviors, // determines how the asset will be handled once loaded resolvedUrl?: string; // this should be absolute url to the asset - hash?: string; + hash?: string | null | ""; // the integrity hash of the asset (if any) } export interface LoadingResource { From d13807e05457a92ba1ee7ac7a43ad48cfbb171ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 28 Jul 2023 22:54:15 +0200 Subject: [PATCH 60/65] Pass AssetBehaviors to LoadBootResourceCallback, call it for everything --- src/mono/wasm/runtime/dotnet.d.ts | 3 +- src/mono/wasm/runtime/loader/assets.ts | 71 ++++++++++++++++++-------- src/mono/wasm/runtime/loader/config.ts | 4 +- src/mono/wasm/runtime/types/index.ts | 2 +- 4 files changed, 55 insertions(+), 25 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index cab64a803fa4a7..edbdc40fc008af 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -209,7 +209,7 @@ type ResourceList = { * @param integrity The integrity string representing the expected content in the response. * @returns A URI string or a Response promise to override the loading process, or null/undefined to allow the default loading behavior. */ -type LoadBootResourceCallback = (type: WebAssemblyBootResourceType, name: string, defaultUri: string, integrity: string) => string | Promise | null | undefined; +type LoadBootResourceCallback = (type: AssetBehaviors | "manifest", name: string, defaultUri: string, integrity: string) => string | Promise | null | undefined; interface ResourceRequest { name: string; behavior: AssetBehaviors; @@ -396,7 +396,6 @@ type ModuleAPI = { exit: (code: number, reason?: any) => void; }; type CreateDotnetRuntimeType = (moduleFactory: DotnetModuleConfig | ((api: RuntimeAPI) => DotnetModuleConfig)) => Promise; -type WebAssemblyBootResourceType = "assembly" | "pdb" | "dotnetjs" | "dotnetwasm" | "globalization" | "manifest" | "configuration"; interface IDisposable { dispose(): void; diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index 1bfc2fea240d73..92c944e9752939 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -69,12 +69,22 @@ function getSingleAssetWithResolvedUrl(resources: ResourceList | undefined, beha mono_assert(keys.length == 1, `Expect to have one ${behavior} asset in resources`); const name = keys[0]; - return { + const asset = { name, hash: resources![name], behavior, resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), behavior) }; + + const customSrc = invokeLoadBootResource(asset); + if (typeof (customSrc) === "string") { + asset.resolvedUrl = customSrc; + } else if (customSrc) { + // Since we must load this via a import, it's only valid to supply a URI (and not a Request, say) + throw new Error(`For a ${behavior} resource, custom loaders must supply a URI string.`); + } + + return asset; } export function resolve_single_asset_path(behavior: SingleAssetBehaviors): AssetEntryInternal { @@ -564,31 +574,19 @@ async function download_resource_with_cache(request: ResourceRequest): Promise { // Allow developers to override how the resource is loaded let url = request.resolvedUrl!; if (loaderHelpers.loadBootResource) { - const resourceType = monoToBlazorAssetTypeMap[request.behavior]; - if (resourceType) { - const customLoadResult = loaderHelpers.loadBootResource(resourceType!, request.name, url, request.hash ?? ""); - if (customLoadResult instanceof Promise) { - // They are supplying an entire custom response, so just use that - return customLoadResult; - } else if (typeof customLoadResult === "string") { - // They are supplying a custom URL, so use that with the default fetch behavior - url = customLoadResult; - } + const customLoadResult = invokeLoadBootResource(request); + if (customLoadResult instanceof Promise) { + // They are supplying an entire custom response, so just use that + return customLoadResult; + } else if (typeof customLoadResult === "string") { + // They are supplying a custom URL, so use that with the default fetch behavior + url = customLoadResult; } } @@ -610,6 +608,39 @@ function fetchResource(request: ResourceRequest): Promise { return loaderHelpers.fetch_like(url, fetchOptions); } +const monoToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType | undefined } = { + "resource": "assembly", + "assembly": "assembly", + "pdb": "pdb", + "icu": "globalization", + "vfs": "configuration", + "dotnetwasm": "dotnetwasm", + "js-module-native": "dotnetjs", + "js-module-runtime": "dotnetjs", + "js-module-threads": "dotnetjs" +}; + +function invokeLoadBootResource(request: ResourceRequest): string | Promise | null | undefined { + if (loaderHelpers.loadBootResource) { + const requestHash = request.hash ?? ""; + const url = request.resolvedUrl!; + + // Try to send with AssetBehaviors + let customLoadResult = loaderHelpers.loadBootResource(request.behavior, request.name, url, requestHash); + if (!customLoadResult) { + // If we don't get result, try to send with WebAssemblyBootResourceType + const resourceType = monoToBlazorAssetTypeMap[request.behavior]; + if (resourceType) { + customLoadResult = loaderHelpers.loadBootResource(resourceType as AssetBehaviors, request.name, url, requestHash); + } + } + + return customLoadResult; + } + + return undefined; +} + export function cleanupAsset(asset: AssetEntryInternal) { // give GC chance to collect resources asset.pendingDownloadInternal = null as any; // GC diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index 56f294cbe454af..59e392f6b184cb 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -3,7 +3,7 @@ import BuildConfiguration from "consts:configuration"; import type { DotnetModuleInternal, MonoConfigInternal } from "../types/internal"; -import type { DotnetModuleConfig, MonoConfig } from "../types"; +import type { AssetBehaviors, DotnetModuleConfig, MonoConfig } from "../types"; import { ENVIRONMENT_IS_WEB, exportedRuntimeAPI, loaderHelpers, runtimeHelpers } from "./globals"; import { mono_log_error, mono_log_debug } from "./logging"; import { invokeLibraryInitializers } from "./libraryInitializers"; @@ -132,7 +132,7 @@ async function loadBootConfig(module: DotnetModuleInternal): Promise { const defaultConfigSrc = loaderHelpers.locateFile(module.configSrc!); const loaderResponse = loaderHelpers.loadBootResource !== undefined ? - loaderHelpers.loadBootResource("manifest", "blazor.boot.json", defaultConfigSrc, "") : + loaderHelpers.loadBootResource("manifest" as AssetBehaviors, "blazor.boot.json", defaultConfigSrc, "") : defaultLoadBootConfig(defaultConfigSrc); let loadConfigResponse: Response; diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 86d8f3240b476a..a80f96989ecca2 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -145,7 +145,7 @@ export type ResourceList = { [name: string]: string | null | "" }; * @param integrity The integrity string representing the expected content in the response. * @returns A URI string or a Response promise to override the loading process, or null/undefined to allow the default loading behavior. */ -export type LoadBootResourceCallback = (type: WebAssemblyBootResourceType, name: string, defaultUri: string, integrity: string) => string | Promise | null | undefined; +export type LoadBootResourceCallback = (type: AssetBehaviors | "manifest", name: string, defaultUri: string, integrity: string) => string | Promise | null | undefined; export interface ResourceRequest { name: string, // the name of the asset, including extension. From ef7c8ebfedcbad329149eeccc4dc3382f5b939b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 28 Jul 2023 23:03:56 +0200 Subject: [PATCH 61/65] Deep merge resources --- src/mono/wasm/runtime/dotnet.d.ts | 30 ++++++------- src/mono/wasm/runtime/loader/config.ts | 62 +++++++++++++++++++++++++- src/mono/wasm/runtime/types/index.ts | 30 ++++++------- 3 files changed, 90 insertions(+), 32 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index edbdc40fc008af..3bb25afcdb06c4 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -174,23 +174,23 @@ type ResourceExtensions = { [extensionName: string]: ResourceList; }; interface ResourceGroups { - readonly hash?: string; - readonly assembly?: ResourceList; - readonly lazyAssembly?: ResourceList; - readonly pdb?: ResourceList; - readonly jsModuleWorker?: ResourceList; - readonly jsModuleNative: ResourceList; - readonly jsModuleRuntime: ResourceList; - readonly jsSymbols?: ResourceList; - readonly wasmNative: ResourceList; - readonly icu?: ResourceList; - readonly satelliteResources?: { + hash?: string; + assembly?: ResourceList; + lazyAssembly?: ResourceList; + pdb?: ResourceList; + jsModuleWorker?: ResourceList; + jsModuleNative: ResourceList; + jsModuleRuntime: ResourceList; + jsSymbols?: ResourceList; + wasmNative: ResourceList; + icu?: ResourceList; + satelliteResources?: { [cultureName: string]: ResourceList; }; - readonly modulesAfterConfigLoaded?: ResourceList; - readonly modulesAfterRuntimeReady?: ResourceList; - readonly extensions?: ResourceExtensions; - readonly vfs?: { + modulesAfterConfigLoaded?: ResourceList; + modulesAfterRuntimeReady?: ResourceList; + extensions?: ResourceExtensions; + vfs?: { [virtualPath: string]: ResourceList; }; } diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index 59e392f6b184cb..f9fe128537f2ac 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -3,7 +3,7 @@ import BuildConfiguration from "consts:configuration"; import type { DotnetModuleInternal, MonoConfigInternal } from "../types/internal"; -import type { AssetBehaviors, DotnetModuleConfig, MonoConfig } from "../types"; +import type { AssetBehaviors, DotnetModuleConfig, MonoConfig, ResourceGroups } from "../types"; import { ENVIRONMENT_IS_WEB, exportedRuntimeAPI, loaderHelpers, runtimeHelpers } from "./globals"; import { mono_log_error, mono_log_debug } from "./logging"; import { invokeLibraryInitializers } from "./libraryInitializers"; @@ -16,7 +16,12 @@ export function deep_merge_config(target: MonoConfigInternal, source: MonoConfig providedConfig.assets = [...(target.assets || []), ...(providedConfig.assets || [])]; } if (providedConfig.resources !== undefined) { - providedConfig.resources = { ...(target.resources || {}), ...(providedConfig.resources || {}) }; + providedConfig.resources = deep_merge_resources(target.resources || { + assembly: {}, + jsModuleNative: {}, + jsModuleRuntime: {}, + wasmNative: {} + }, providedConfig.resources); } if (providedConfig.environmentVariables !== undefined) { providedConfig.environmentVariables = { ...(target.environmentVariables || {}), ...(providedConfig.environmentVariables || {}) }; @@ -36,6 +41,53 @@ export function deep_merge_module(target: DotnetModuleInternal, source: DotnetMo return Object.assign(target, providedConfig); } +function deep_merge_resources(target: ResourceGroups, source: ResourceGroups): ResourceGroups { + const providedResources: ResourceGroups = { ...source }; + if (providedResources.assembly !== undefined) { + providedResources.assembly = { ...(target.assembly || {}), ...(providedResources.assembly || {}) }; + } + if (providedResources.lazyAssembly !== undefined) { + providedResources.lazyAssembly = { ...(target.lazyAssembly || {}), ...(providedResources.lazyAssembly || {}) }; + } + if (providedResources.pdb !== undefined) { + providedResources.pdb = { ...(target.pdb || {}), ...(providedResources.pdb || {}) }; + } + if (providedResources.jsModuleWorker !== undefined) { + providedResources.jsModuleWorker = { ...(target.jsModuleWorker || {}), ...(providedResources.jsModuleWorker || {}) }; + } + if (providedResources.jsModuleNative !== undefined) { + providedResources.jsModuleNative = { ...(target.jsModuleNative || {}), ...(providedResources.jsModuleNative || {}) }; + } + if (providedResources.jsModuleRuntime !== undefined) { + providedResources.jsModuleRuntime = { ...(target.jsModuleRuntime || {}), ...(providedResources.jsModuleRuntime || {}) }; + } + if (providedResources.jsSymbols !== undefined) { + providedResources.jsSymbols = { ...(target.jsSymbols || {}), ...(providedResources.jsSymbols || {}) }; + } + if (providedResources.wasmNative !== undefined) { + providedResources.wasmNative = { ...(target.wasmNative || {}), ...(providedResources.wasmNative || {}) }; + } + if (providedResources.icu !== undefined) { + providedResources.icu = { ...(target.icu || {}), ...(providedResources.icu || {}) }; + } + if (providedResources.satelliteResources !== undefined) { + providedResources.satelliteResources = { ...(target.satelliteResources || {}), ...(providedResources.satelliteResources || {}) }; + } + if (providedResources.modulesAfterConfigLoaded !== undefined) { + providedResources.modulesAfterConfigLoaded = { ...(target.modulesAfterConfigLoaded || {}), ...(providedResources.modulesAfterConfigLoaded || {}) }; + } + if (providedResources.modulesAfterRuntimeReady !== undefined) { + providedResources.modulesAfterRuntimeReady = { ...(target.modulesAfterRuntimeReady || {}), ...(providedResources.modulesAfterRuntimeReady || {}) }; + } + if (providedResources.extensions !== undefined) { + providedResources.extensions = { ...(target.extensions || {}), ...(providedResources.extensions || {}) }; + } + if (providedResources.vfs !== undefined) { + providedResources.vfs = { ...(target.vfs || {}), ...(providedResources.vfs || {}) }; + } + return Object.assign(target, providedResources); +} + // NOTE: this is called before setRuntimeGlobals export function normalizeConfig() { // normalize @@ -44,6 +96,12 @@ export function normalizeConfig() { config.environmentVariables = config.environmentVariables || {}; config.assets = config.assets || []; config.runtimeOptions = config.runtimeOptions || []; + config.resources = config.resources || { + assembly: {}, + jsModuleNative: {}, + jsModuleRuntime: {}, + wasmNative: {} + }; loaderHelpers.assertAfterExit = config.assertAfterExit = config.assertAfterExit || !ENVIRONMENT_IS_WEB; if (config.debugLevel === undefined && BuildConfiguration === "Debug") { diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index a80f96989ecca2..af76f00538f0dd 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -110,25 +110,25 @@ export type MonoConfig = { export type ResourceExtensions = { [extensionName: string]: ResourceList }; export interface ResourceGroups { - readonly hash?: string; - readonly assembly?: ResourceList; // nullable only temporarily - readonly lazyAssembly?: ResourceList; // nullable only temporarily - readonly pdb?: ResourceList; + hash?: string; + assembly?: ResourceList; // nullable only temporarily + lazyAssembly?: ResourceList; // nullable only temporarily + pdb?: ResourceList; - readonly jsModuleWorker?: ResourceList; - readonly jsModuleNative: ResourceList; - readonly jsModuleRuntime: ResourceList; - readonly jsSymbols?: ResourceList; - readonly wasmNative: ResourceList; - readonly icu?: ResourceList; + jsModuleWorker?: ResourceList; + jsModuleNative: ResourceList; + jsModuleRuntime: ResourceList; + jsSymbols?: ResourceList; + wasmNative: ResourceList; + icu?: ResourceList; - readonly satelliteResources?: { [cultureName: string]: ResourceList }; + satelliteResources?: { [cultureName: string]: ResourceList }; - readonly modulesAfterConfigLoaded?: ResourceList, - readonly modulesAfterRuntimeReady?: ResourceList + modulesAfterConfigLoaded?: ResourceList, + modulesAfterRuntimeReady?: ResourceList - readonly extensions?: ResourceExtensions - readonly vfs?: { [virtualPath: string]: ResourceList }; + extensions?: ResourceExtensions + vfs?: { [virtualPath: string]: ResourceList }; } /** From 79f2c421c84639c6fe18bd36ffee90534a4e081d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 28 Jul 2023 23:16:07 +0200 Subject: [PATCH 62/65] Use System.Text.Json in GenerateWasmBootJson --- .../GenerateWasmBootJson.cs | 27 +++++++++---------- ...soft.NET.Sdk.WebAssembly.Pack.Tasks.csproj | 1 + 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index eb709945dcc515..dfa897ca3bbcff 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -10,6 +10,9 @@ using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Xml.Linq; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -345,33 +348,27 @@ public void WriteBootJson(Stream output, string entryAssemblyName) } } - if (Extensions != null && Extensions.Length > 0) + var jsonOptions = new JsonSerializerOptions() { - var configSerializer = new DataContractJsonSerializer(typeof(Dictionary), new DataContractJsonSerializerSettings - { - UseSimpleDictionaryFormat = true - }); + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + WriteIndented = true + }; + if (Extensions != null && Extensions.Length > 0) + { result.extensions = new Dictionary>(); foreach (var configExtension in Extensions) { var key = configExtension.GetMetadata("key"); using var fs = File.OpenRead(configExtension.ItemSpec); - var config = (Dictionary)configSerializer.ReadObject(fs); + var config = JsonSerializer.Deserialize>(fs, jsonOptions); result.extensions[key] = config; } } helper.ComputeResourcesHash(result); - - var serializer = new DataContractJsonSerializer(typeof(BootJsonData), new DataContractJsonSerializerSettings - { - UseSimpleDictionaryFormat = true, - EmitTypeInformation = EmitTypeInformation.Never - }); - - using var writer = JsonReaderWriterFactory.CreateJsonWriter(output, Encoding.UTF8, ownsStream: false, indent: true); - serializer.WriteObject(writer, result); + JsonSerializer.Serialize(output, result, jsonOptions); void AddResourceToList(ITaskItem resource, ResourceHashesByNameDictionary resourceList, string resourceKey) { diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks.csproj b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks.csproj index 747e216bfb46e2..2f6ef98d6ad48c 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks.csproj +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks.csproj @@ -20,6 +20,7 @@ + From a9f7cf7e46e47f8fda62f1f0bf5771c39067d96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 28 Jul 2023 23:22:23 +0200 Subject: [PATCH 63/65] Order items when computing hash --- .../BootJsonBuilderHelper.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs index bf223c64b65236..92aa8531f1efb3 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using Microsoft.Build.Utilities; @@ -20,8 +21,8 @@ static void AddDictionary(StringBuilder sb, Dictionary? res) if (res == null) return; - foreach (var asset in res) - sb.Append(asset.Value); + foreach (var assetHash in res.Values.OrderBy(v => v)) + sb.Append(assetHash); } AddDictionary(sb, bootConfig.resources.assembly); From ed81e8e43defa92000598ae6a57aefe38a371cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Sat, 29 Jul 2023 00:10:28 +0200 Subject: [PATCH 64/65] Hotfix WBT --- src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs | 5 +++-- src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs | 2 ++ .../wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs index d197f0aca2ee34..794ca383f077e8 100644 --- a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs @@ -392,7 +392,7 @@ public void AssertBootJson(AssertBundleOptionsBase options) BootJsonData bootJson = ParseBootData(bootJsonPath); var bootJsonEntries = bootJson.resources.jsModuleNative.Keys .Union(bootJson.resources.jsModuleRuntime.Keys) - .Union(bootJson.resources.jsModuleWorker.Keys) + .Union(bootJson.resources.jsModuleWorker?.Keys ?? Enumerable.Empty()) .Union(bootJson.resources.wasmNative.Keys) .ToArray(); @@ -402,7 +402,8 @@ public void AssertBootJson(AssertBundleOptionsBase options) var knownSet = GetAllKnownDotnetFilesToFingerprintMap(options); foreach (string expectedFilename in expected) { - if (Path.GetExtension(expectedFilename) == ".map") + // FIXME: Find a systematic solution for skipping dotnet.js from boot json check + if (expectedFilename == "dotnet.js" || Path.GetExtension(expectedFilename) == ".map") continue; bool expectFingerprint = knownSet[expectedFilename]; diff --git a/src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs b/src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs index 4c2735e4088d0e..bc1fa0aa2933f2 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs @@ -22,6 +22,8 @@ public TestMainJsProjectProvider(ITestOutputHelper _testOutput, string? _project protected override IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(AssertBundleOptionsBase assertOptions) => new SortedDictionary() { + { "dotnet.js", false }, + { "dotnet.js.map", false }, { "dotnet.native.js", false }, { "dotnet.native.js.symbols", false }, { "dotnet.native.wasm", false }, diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs b/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs index cd05c7d56e2e12..1f70aa46df8fa1 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs @@ -21,6 +21,8 @@ public WasmSdkBasedProjectProvider(ITestOutputHelper _testOutput, string? _proje protected override IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(AssertBundleOptionsBase assertOptions) => new SortedDictionary() { + { "dotnet.js", false }, + { "dotnet.js.map", false }, { "dotnet.native.js", true }, { "dotnet.native.js.symbols", false }, { "dotnet.native.wasm", false }, From 6b3712c5a46c9ffefbe8da49ec7d06f7895a2e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Sat, 29 Jul 2023 08:41:53 +0200 Subject: [PATCH 65/65] Fix WBT --- src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs index 794ca383f077e8..99d624b78ef55c 100644 --- a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs @@ -393,6 +393,7 @@ public void AssertBootJson(AssertBundleOptionsBase options) var bootJsonEntries = bootJson.resources.jsModuleNative.Keys .Union(bootJson.resources.jsModuleRuntime.Keys) .Union(bootJson.resources.jsModuleWorker?.Keys ?? Enumerable.Empty()) + .Union(bootJson.resources.jsSymbols?.Keys ?? Enumerable.Empty()) .Union(bootJson.resources.wasmNative.Keys) .ToArray();