From 59052d2c980b89c63f5b47d3cb9e9399a8f35b54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 3 Jun 2025 15:41:56 +0200 Subject: [PATCH 01/32] wip --- .../BootJsonData.cs | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) 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 3a2eee6dca5abe..1cdcda79e20935 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -36,8 +36,12 @@ public class BootJsonData /// and values are SHA-256 hashes formatted in prefixed base-64 style (e.g., 'sha256-abcdefg...') /// as used for subresource integrity checking. /// + [DataMember(EmitDefaultValue = false)] public ResourcesData resources { get; set; } = new ResourcesData(); + [DataMember(EmitDefaultValue = false)] + public AssetsData assets { get; set; } + /// /// Gets a value that determines whether to enable caching of the /// inside a CacheStorage instance within the browser. @@ -249,6 +253,128 @@ public class ResourcesData public List remoteSources { get; set; } } +public class AssetsData +{ + /// + /// Gets a hash of all resources + /// + public string hash { get; set; } + + [DataMember(EmitDefaultValue = false)] + public List jsModuleWorker { get; set; } + + [DataMember(EmitDefaultValue = false)] + public List jsModuleDiagnostics { get; set; } + + [DataMember(EmitDefaultValue = false)] + public List jsModuleNative { get; set; } + + [DataMember(EmitDefaultValue = false)] + public List jsModuleRuntime { get; set; } + + [DataMember(EmitDefaultValue = false)] + public List wasmNative { get; set; } + + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary wasmSymbols { get; set; } + + [DataMember(EmitDefaultValue = false)] + public List icu { get; set; } + + /// + /// "assembly" (.dll) resources needed to start MonoVM + /// + public List coreAssembly { get; set; } = new(); + + /// + /// "assembly" (.dll) resources + /// + public List assembly { get; set; } = new(); + + /// + /// "debug" (.pdb) resources needed to start MonoVM + /// + [DataMember(EmitDefaultValue = false)] + public List corePdb { get; set; } + + /// + /// "debug" (.pdb) resources + /// + [DataMember(EmitDefaultValue = false)] + public List pdb { get; set; } + + /// + /// localization (.satellite resx) resources + /// + [DataMember(EmitDefaultValue = false)] + public List satelliteResources { get; set; } + + /// + /// Assembly (.dll) resources that are loaded lazily during runtime + /// + [DataMember(EmitDefaultValue = false)] + public List lazyAssembly { get; set; } + + /// + /// JavaScript module initializers that Blazor will be in charge of loading. + /// Used in .NET < 8 + /// + [DataMember(EmitDefaultValue = false)] + public List libraryInitializers { get; set; } + + [DataMember(EmitDefaultValue = false)] + public List modulesAfterConfigLoaded { get; set; } + + [DataMember(EmitDefaultValue = false)] + public List modulesAfterRuntimeReady { get; set; } + + /// + /// Extensions created by users customizing the initialization process. The format of the file(s) + /// is up to the user. + /// + [DataMember(EmitDefaultValue = false)] + public Dictionary extensions { get; set; } + + [DataMember(EmitDefaultValue = false)] + public List coreVfs { get; set; } + + [DataMember(EmitDefaultValue = false)] + public List vfs { get; set; } +} + +[DataContract] +public class JsAsset +{ + public string url { get; set; } +} + +[DataContract] +public class WasmAsset +{ + public string url { get; set; } + public string integrity { get; set; } +} + +[DataContract] +public class GeneralAsset +{ + public string name { get; set; } + public string url { get; set; } + public string integrity { get; set; } +} + +[DataContract] +public class SatelliteAsset : GeneralAsset +{ + public string culture { get; set; } +} + +[DataContract] +public class VfsAsset : GeneralAsset +{ + public string virtualPath { 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 From 1919ce46b0d4fb7e9a94d9b5a74b99e4dc68373f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 4 Jun 2025 10:18:52 +0200 Subject: [PATCH 02/32] wip --- .../BootJsonBuilderHelper.cs | 68 +++++++++++++++++++ .../BootJsonData.cs | 20 +++--- .../GenerateWasmBootJson.cs | 4 ++ src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 1 + 4 files changed, 84 insertions(+), 9 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 5b037474909f0d..1d05562da18679 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs @@ -174,5 +174,73 @@ public int GetDebugLevel(bool hasPdb) return intValue; } + + public void TransformResourcesToAssets(BootJsonData config) + { + var resources = config.resources; + var assets = new AssetsData(); + + assets.hash = resources.hash; + assets.jsModuleRuntime = MapJsAssets(resources.jsModuleRuntime); + assets.jsModuleNative = MapJsAssets(resources.jsModuleNative); + assets.jsModuleWorker = MapJsAssets(resources.jsModuleWorker); + assets.jsModuleDiagnostics = MapJsAssets(resources.jsModuleDiagnostics); + + assets.wasmNative = resources.wasmNative?.Select(a => new WasmAsset() + { + url = a.Key, + integrity = a.Value + }).ToList(); + assets.wasmSymbols = resources.wasmSymbols?.Select(a => new SymbolsAsset() + { + url = a.Key, + }).ToList(); + + assets.icu = MapGeneralAssets(resources.icu); + assets.coreAssembly = MapGeneralAssets(resources.coreAssembly); + assets.assembly = MapGeneralAssets(resources.assembly); + assets.corePdb = MapGeneralAssets(resources.corePdb); + assets.pdb = MapGeneralAssets(resources.pdb); + assets.lazyAssembly = MapGeneralAssets(resources.lazyAssembly); + + if (resources.satelliteResources != null) + { + assets.satelliteResources = resources.satelliteResources.ToDictionary( + kvp => kvp.Key, + kvp => MapGeneralAssets(kvp.Value) + ); + } + + assets.libraryInitializers = MapJsAssets(resources.libraryInitializers); + assets.modulesAfterConfigLoaded = MapJsAssets(resources.modulesAfterConfigLoaded); + assets.modulesAfterRuntimeReady = MapJsAssets(resources.modulesAfterRuntimeReady); + + assets.extensions = resources.extensions; + + assets.coreVfs = MapVfsAssets(resources.coreVfs); + assets.vfs = MapVfsAssets(resources.vfs); + + List? MapGeneralAssets(Dictionary? assets) => assets?.Select(a => new GeneralAsset() + { + name = resources.fingerprinting[a.Key], + url = a.Key, + integrity = a.Value + }).ToList(); + + List? MapJsAssets(Dictionary? assets) => assets?.Select(a => new JsAsset() + { + url = a.Key + }).ToList(); + + List? MapVfsAssets(Dictionary>? assets) => assets?.Select(a => new VfsAsset() + { + virtualPath = a.Key, + url = a.Value.Keys.First(), + integrity = a.Value.Values.First() + }).ToList(); + + config.assets = assets; + config.resources = null; + } } } 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 1cdcda79e20935..d836c673282872 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -276,7 +276,7 @@ public class AssetsData public List wasmNative { get; set; } [DataMember(EmitDefaultValue = false)] - public ResourceHashesByNameDictionary wasmSymbols { get; set; } + public List wasmSymbols { get; set; } [DataMember(EmitDefaultValue = false)] public List icu { get; set; } @@ -307,7 +307,7 @@ public class AssetsData /// localization (.satellite resx) resources /// [DataMember(EmitDefaultValue = false)] - public List satelliteResources { get; set; } + public Dictionary> satelliteResources { get; set; } /// /// Assembly (.dll) resources that are loaded lazily during runtime @@ -349,30 +349,32 @@ public class JsAsset } [DataContract] -public class WasmAsset +public class SymbolsAsset { public string url { get; set; } - public string integrity { get; set; } } [DataContract] -public class GeneralAsset +public class WasmAsset { - public string name { get; set; } public string url { get; set; } public string integrity { get; set; } } [DataContract] -public class SatelliteAsset : GeneralAsset +public class GeneralAsset { - public string culture { get; set; } + public string name { get; set; } + public string url { get; set; } + public string integrity { get; set; } } [DataContract] -public class VfsAsset : GeneralAsset +public class VfsAsset { public string virtualPath { get; set; } + public string url { get; set; } + public string integrity { get; set; } } public enum GlobalizationMode : int 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 09c78844f0b567..3e40d3b3ca7833 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -451,6 +451,10 @@ private void WriteBootConfig(string entryAssemblyName) } helper.ComputeResourcesHash(result); + + if (IsTargeting100OrLater()) + helper.TransformResourcesToAssets(result); + helper.WriteConfigToFile(result, OutputPath, mergeWith: MergeWith); void AddResourceToList(ITaskItem resource, ResourceHashesByNameDictionary resourceList, string resourceKey) diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 87980f370ff824..b8bbf474e7ab03 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -446,6 +446,7 @@ protected override bool ExecuteInternal() using TempFileName tmpConfigPath = new(); { helper.ComputeResourcesHash(bootConfig); + helper.TransformResourcesToAssets(bootConfig); helper.WriteConfigToFile(bootConfig, tmpConfigPath.Path, Path.GetExtension(ConfigFileName)); } From ab9e533f72292c02a8c0b70aa458b95372b5cf88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 4 Jun 2025 10:25:18 +0200 Subject: [PATCH 03/32] wip --- .../BootJsonBuilderHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1d05562da18679..f1104a10f5fe38 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs @@ -222,7 +222,7 @@ public void TransformResourcesToAssets(BootJsonData config) List? MapGeneralAssets(Dictionary? assets) => assets?.Select(a => new GeneralAsset() { - name = resources.fingerprinting[a.Key], + name = resources.fingerprinting?[a.Key] ?? a.Key, url = a.Key, integrity = a.Value }).ToList(); From 03e2531e31018eea7821c3f51bdae799248f7073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 4 Jun 2025 10:57:25 +0200 Subject: [PATCH 04/32] wip --- .../BootJsonBuilderHelper.cs | 59 ++++++++++--------- .../BootJsonData.cs | 5 +- .../GenerateWasmBootJson.cs | 4 +- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 41 ++++++------- 4 files changed, 52 insertions(+), 57 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 f1104a10f5fe38..8062abe52a1eb9 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs @@ -75,6 +75,8 @@ public void WriteConfigToFile(BootJsonData config, string outputPath, string? ou public void ComputeResourcesHash(BootJsonData bootConfig) { + ResourcesData resources = (ResourcesData)bootConfig.resources; + var sb = new StringBuilder(); static void AddDictionary(StringBuilder sb, Dictionary? res) @@ -86,59 +88,61 @@ static void AddDictionary(StringBuilder sb, Dictionary? res) sb.Append(assetHash); } - AddDictionary(sb, bootConfig.resources.assembly); - AddDictionary(sb, bootConfig.resources.coreAssembly); + AddDictionary(sb, resources.assembly); + AddDictionary(sb, resources.coreAssembly); - AddDictionary(sb, bootConfig.resources.jsModuleWorker); - AddDictionary(sb, bootConfig.resources.jsModuleDiagnostics); - AddDictionary(sb, bootConfig.resources.jsModuleNative); - AddDictionary(sb, bootConfig.resources.jsModuleRuntime); - AddDictionary(sb, bootConfig.resources.wasmNative); - AddDictionary(sb, bootConfig.resources.wasmSymbols); - AddDictionary(sb, bootConfig.resources.icu); - AddDictionary(sb, bootConfig.resources.runtime); - AddDictionary(sb, bootConfig.resources.lazyAssembly); + AddDictionary(sb, resources.jsModuleWorker); + AddDictionary(sb, resources.jsModuleDiagnostics); + AddDictionary(sb, resources.jsModuleNative); + AddDictionary(sb, resources.jsModuleRuntime); + AddDictionary(sb, resources.wasmNative); + AddDictionary(sb, resources.wasmSymbols); + AddDictionary(sb, resources.icu); + AddDictionary(sb, resources.runtime); + AddDictionary(sb, resources.lazyAssembly); - if (bootConfig.resources.satelliteResources != null) + if (resources.satelliteResources != null) { - foreach (var culture in bootConfig.resources.satelliteResources) + foreach (var culture in resources.satelliteResources) AddDictionary(sb, culture.Value); } - if (bootConfig.resources.vfs != null) + if (resources.vfs != null) { - foreach (var entry in bootConfig.resources.vfs) + foreach (var entry in resources.vfs) AddDictionary(sb, entry.Value); } - if (bootConfig.resources.coreVfs != null) + if (resources.coreVfs != null) { - foreach (var entry in bootConfig.resources.coreVfs) + foreach (var entry in resources.coreVfs) AddDictionary(sb, entry.Value); } - bootConfig.resources.hash = Utils.ComputeTextIntegrity(sb.ToString()); + resources.hash = Utils.ComputeTextIntegrity(sb.ToString()); } public Dictionary? GetNativeResourceTargetInBootConfig(BootJsonData bootConfig, string resourceName) { + ResourcesData resources = (ResourcesData)bootConfig.resources; + string resourceExtension = Path.GetExtension(resourceName); if (resourceName.StartsWith("dotnet.native.worker", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".mjs", StringComparison.OrdinalIgnoreCase)) - return bootConfig.resources.jsModuleWorker ??= new(); + return resources.jsModuleWorker ??= new(); else if (resourceName.StartsWith("dotnet.diagnostics", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase)) - return bootConfig.resources.jsModuleDiagnostics ??= new(); + return resources.jsModuleDiagnostics ??= new(); else if (resourceName.StartsWith("dotnet.native", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase)) - return bootConfig.resources.jsModuleNative ??= new(); + return resources.jsModuleNative ??= new(); else if (resourceName.StartsWith("dotnet.runtime", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase)) - return bootConfig.resources.jsModuleRuntime ??= new(); + return resources.jsModuleRuntime ??= new(); else if (resourceName.StartsWith("dotnet.native", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".wasm", StringComparison.OrdinalIgnoreCase)) - return bootConfig.resources.wasmNative ??= new(); + return 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.wasmSymbols ??= new(); + return resources.wasmSymbols ??= new(); else if (resourceName.StartsWith("icudt", StringComparison.OrdinalIgnoreCase)) - return bootConfig.resources.icu ??= new(); + return resources.icu ??= new(); else Log.LogError($"The resource '{resourceName}' is not recognized as any native asset"); @@ -177,7 +181,7 @@ public int GetDebugLevel(bool hasPdb) public void TransformResourcesToAssets(BootJsonData config) { - var resources = config.resources; + ResourcesData resources = (ResourcesData)config.resources; var assets = new AssetsData(); assets.hash = resources.hash; @@ -239,8 +243,7 @@ public void TransformResourcesToAssets(BootJsonData config) integrity = a.Value.Values.First() }).ToList(); - config.assets = assets; - config.resources = null; + config.resources = assets; } } } 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 d836c673282872..fce4f87487c4e0 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -37,10 +37,7 @@ public class BootJsonData /// as used for subresource integrity checking. /// [DataMember(EmitDefaultValue = false)] - public ResourcesData resources { get; set; } = new ResourcesData(); - - [DataMember(EmitDefaultValue = false)] - public AssetsData assets { get; set; } + public object resources { get; set; } = new ResourcesData(); /// /// Gets a value that determines whether to enable caching of the 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 3e40d3b3ca7833..7e10e4a3c7ce91 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -180,6 +180,7 @@ private void WriteBootConfig(string entryAssemblyName) // - runtime: // - UriPath (e.g., "dotnet.js") // - ContentHash (e.g., "3448f339acf512448") + ResourcesData resourceData = (ResourcesData)result.resources; if (Resources != null) { var endpointByAsset = Endpoints.ToDictionary(e => e.GetMetadata("AssetFile")); @@ -194,7 +195,6 @@ private void WriteBootConfig(string entryAssemblyName) }); var remainingLazyLoadAssemblies = new List(LazyLoadedAssemblies ?? Array.Empty()); - var resourceData = result.resources; if (FingerprintAssets) resourceData.fingerprinting = new(); @@ -391,7 +391,7 @@ private void WriteBootConfig(string entryAssemblyName) if (IsTargeting80OrLater()) { - result.debugLevel = helper.GetDebugLevel(result.resources?.pdb?.Count > 0); + result.debugLevel = helper.GetDebugLevel(resourceData.pdb?.Count > 0); } if (ConfigurationFiles != null) diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index b8bbf474e7ab03..54ad366d1425f2 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -194,6 +194,8 @@ protected override bool ExecuteInternal() File.WriteAllText(packageJsonPath, json); } + ResourcesData resources = (ResourcesData)bootConfig.resources; + foreach (var assembly in _assemblies) { string assemblyPath = assembly; @@ -215,7 +217,7 @@ protected override bool ExecuteInternal() var assemblyName = Path.GetFileName(assemblyPath); bool isCoreAssembly = IsAot || helper.IsCoreAssembly(assemblyName); - var assemblyList = isCoreAssembly ? bootConfig.resources.coreAssembly : bootConfig.resources.assembly; + var assemblyList = isCoreAssembly ? resources.coreAssembly : resources.assembly; assemblyList[assemblyName] = Utils.ComputeIntegrity(bytes); if (baseDebugLevel != 0) @@ -224,29 +226,22 @@ protected override bool ExecuteInternal() if (File.Exists(pdb)) { if (isCoreAssembly) - { - if (bootConfig.resources.corePdb == null) - bootConfig.resources.corePdb = new(); - } + resources.corePdb ??= new(); else - { - if (bootConfig.resources.pdb == null) - bootConfig.resources.pdb = new(); - } + resources.pdb ??= new(); - var pdbList = isCoreAssembly ? bootConfig.resources.corePdb : bootConfig.resources.pdb; + var pdbList = isCoreAssembly ? resources.corePdb : resources.pdb; pdbList[Path.GetFileName(pdb)] = Utils.ComputeIntegrity(pdb); } } } } - bootConfig.debugLevel = helper.GetDebugLevel(bootConfig.resources.pdb?.Count > 0); + bootConfig.debugLevel = helper.GetDebugLevel(resources.pdb?.Count > 0); ProcessSatelliteAssemblies(args => { - if (bootConfig.resources.satelliteResources == null) - bootConfig.resources.satelliteResources = new(); + resources.satelliteResources ??= new(); string name = Path.GetFileName(args.fullPath); string cultureDirectory = Path.Combine(runtimeAssetsPath, args.culture); @@ -263,8 +258,8 @@ protected override bool ExecuteInternal() Log.LogMessage(MessageImportance.Low, $"Skipped generating {finalWebcil} as the contents are unchanged."); _fileWrites.Add(finalWebcil); - if (!bootConfig.resources.satelliteResources.TryGetValue(args.culture, out var cultureSatelliteResources)) - bootConfig.resources.satelliteResources[args.culture] = cultureSatelliteResources = new(); + if (!resources.satelliteResources.TryGetValue(args.culture, out var cultureSatelliteResources)) + resources.satelliteResources[args.culture] = cultureSatelliteResources = new(); cultureSatelliteResources[Path.GetFileName(finalWebcil)] = Utils.ComputeIntegrity(finalWebcil); } @@ -273,8 +268,8 @@ protected override bool ExecuteInternal() var satellitePath = Path.Combine(cultureDirectory, name); FileCopyChecked(args.fullPath, satellitePath, "SatelliteAssemblies"); - if (!bootConfig.resources.satelliteResources.TryGetValue(args.culture, out var cultureSatelliteResources)) - bootConfig.resources.satelliteResources[args.culture] = cultureSatelliteResources = new(); + if (!resources.satelliteResources.TryGetValue(args.culture, out var cultureSatelliteResources)) + resources.satelliteResources[args.culture] = cultureSatelliteResources = new(); cultureSatelliteResources[name] = Utils.ComputeIntegrity(satellitePath); } @@ -334,10 +329,10 @@ protected override bool ExecuteInternal() } if (vfs.Count > 0) - bootConfig.resources.vfs = vfs; + resources.vfs = vfs; if (coreVfs.Count > 0) - bootConfig.resources.coreVfs = coreVfs; + resources.coreVfs = coreVfs; } if (!InvariantGlobalization) @@ -351,18 +346,18 @@ protected override bool ExecuteInternal() return false; } - bootConfig.resources.icu ??= new(); - bootConfig.resources.icu[Path.GetFileName(idfn)] = Utils.ComputeIntegrity(idfn); + resources.icu ??= new(); + resources.icu[Path.GetFileName(idfn)] = Utils.ComputeIntegrity(idfn); } } if (RemoteSources?.Length > 0) { - bootConfig.resources.remoteSources = new(); + resources.remoteSources = new(); foreach (var source in RemoteSources) if (source != null && source.ItemSpec != null) - bootConfig.resources.remoteSources.Add(source.ItemSpec); + resources.remoteSources.Add(source.ItemSpec); } var extraConfiguration = new Dictionary(); From 37c6ede833b4943d78af3020ec402337830cc7e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 4 Jun 2025 11:06:39 +0200 Subject: [PATCH 05/32] wip --- src/mono/browser/runtime/types/index.ts | 113 ++++++++++++++++++------ 1 file changed, 87 insertions(+), 26 deletions(-) diff --git a/src/mono/browser/runtime/types/index.ts b/src/mono/browser/runtime/types/index.ts index da3394faecd7cc..a816f772964ac4 100644 --- a/src/mono/browser/runtime/types/index.ts +++ b/src/mono/browser/runtime/types/index.ts @@ -182,10 +182,7 @@ export type MonoConfig = { */ applicationCulture?: string, - /** - * definition of assets to load along with the runtime. - */ - resources?: ResourceGroups; + resources?: Assets, /** * appsettings files to load to VFS @@ -211,31 +208,95 @@ export type MonoConfig = { export type ResourceExtensions = { [extensionName: string]: ResourceList }; -export interface ResourceGroups { +export interface Assets { hash?: string; - fingerprinting?: { [name: string]: string }, - coreAssembly?: ResourceList; // nullable only temporarily - assembly?: ResourceList; // nullable only temporarily - lazyAssembly?: ResourceList; // nullable only temporarily - corePdb?: ResourceList; - pdb?: ResourceList; - - jsModuleWorker?: ResourceList; - jsModuleDiagnostics?: ResourceList; - jsModuleNative: ResourceList; - jsModuleRuntime: ResourceList; - wasmSymbols?: ResourceList; - wasmNative: ResourceList; - icu?: ResourceList; - - satelliteResources?: { [cultureName: string]: ResourceList }; - - modulesAfterConfigLoaded?: ResourceList, - modulesAfterRuntimeReady?: ResourceList + coreAssembly?: AssemblyAsset[]; // nullable only temporarily + assembly?: AssemblyAsset[]; // nullable only temporarily + lazyAssembly?: AssemblyAsset[]; // nullable only temporarily + corePdb?: PdbAsset[]; + pdb?: PdbAsset[]; + + jsModuleWorker?: JsAsset[]; + jsModuleDiagnostics?: JsAsset[]; + jsModuleNative: JsAsset[]; + jsModuleRuntime: JsAsset[]; + + wasmSymbols?: SymbolsAsset[]; + wasmNative: WasmAsset[]; + icu?: IcuAsset[]; + + satelliteResources?: { [cultureName: string]: AssemblyAsset[] }; + + modulesAfterConfigLoaded?: JsAsset[], + modulesAfterRuntimeReady?: JsAsset[] extensions?: ResourceExtensions - coreVfs?: { [virtualPath: string]: ResourceList }; - vfs?: { [virtualPath: string]: ResourceList }; + coreVfs?: VfsAsset[]; + vfs?: VfsAsset[]; +} + +export type Asset = { + /** + * this should be absolute url to the asset + */ + resolvedUrl?: string; + /** + * If true, the runtime startup would not fail if the asset download was not successful. + */ + isOptional?: boolean + /** + * If provided, runtime doesn't have to fetch the data. + * Runtime would set the buffer to null after instantiation to free the memory. + */ + buffer?: ArrayBuffer | Promise, + /** + * It's metadata + fetch-like Promise + * If provided, the runtime doesn't have to initiate the download. It would just await the response. + */ + pendingDownload?: LoadingResource +} + +export type AssemblyAsset = Asset & { + name: string; + url: string; + hash?: string | null | ""; +} + +export type WasmAsset = Asset & { + url: string; + hash?: string | null | ""; +} + +export type PdbAsset = Asset & { + name: string; + url: string; + hash?: string | null | ""; +} + +export type JsAsset = Asset & { + /** + * If provided, runtime doesn't have to import it's JavaScript modules. + * This will not work for multi-threaded runtime. + */ + moduleExports?: any | Promise, + + url?: string; +} + +export type SymbolsAsset = Asset & { + url: string; +} + +export type VfsAsset = Asset & { + virtualPath: string; + url: string; + hash?: string | null | ""; +} + +export type IcuAsset = Asset & { + name: string; + url: string; + hash?: string | null | ""; } /** From 66e4f092f2aee671b167083ae7a65203b86436b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 4 Jun 2025 14:11:41 +0200 Subject: [PATCH 06/32] wip --- src/mono/browser/runtime/dotnet.d.ts | 103 +++++++++---- src/mono/browser/runtime/lazyLoading.ts | 64 +++----- src/mono/browser/runtime/loader/assets.ts | 137 ++++++------------ src/mono/browser/runtime/loader/config.ts | 61 ++++---- src/mono/browser/runtime/loader/icu.ts | 25 ++-- .../runtime/loader/libraryInitializers.ts | 7 +- .../browser/runtime/satelliteAssemblies.ts | 15 +- src/mono/browser/runtime/types/index.ts | 22 +-- .../BootJsonBuilderHelper.cs | 12 +- .../BootJsonData.cs | 10 +- 10 files changed, 210 insertions(+), 246 deletions(-) diff --git a/src/mono/browser/runtime/dotnet.d.ts b/src/mono/browser/runtime/dotnet.d.ts index 8fb189d748b390..2d734a3c95172a 100644 --- a/src/mono/browser/runtime/dotnet.d.ts +++ b/src/mono/browser/runtime/dotnet.d.ts @@ -228,10 +228,7 @@ type MonoConfig = { * Gets the application culture. This is a name specified in the BCP 47 format. See https://tools.ietf.org/html/bcp47 */ applicationCulture?: string; - /** - * definition of assets to load along with the runtime. - */ - resources?: ResourceGroups; + resources?: Assets; /** * appsettings files to load to VFS */ @@ -255,36 +252,84 @@ type MonoConfig = { type ResourceExtensions = { [extensionName: string]: ResourceList; }; -interface ResourceGroups { +interface Assets { hash?: string; - fingerprinting?: { - [name: string]: string; - }; - coreAssembly?: ResourceList; - assembly?: ResourceList; - lazyAssembly?: ResourceList; - corePdb?: ResourceList; - pdb?: ResourceList; - jsModuleWorker?: ResourceList; - jsModuleDiagnostics?: ResourceList; - jsModuleNative: ResourceList; - jsModuleRuntime: ResourceList; - wasmSymbols?: ResourceList; - wasmNative: ResourceList; - icu?: ResourceList; + coreAssembly?: AssemblyAsset[]; + assembly?: AssemblyAsset[]; + lazyAssembly?: AssemblyAsset[]; + corePdb?: PdbAsset[]; + pdb?: PdbAsset[]; + jsModuleWorker?: JsAsset[]; + jsModuleDiagnostics?: JsAsset[]; + jsModuleNative: JsAsset[]; + jsModuleRuntime: JsAsset[]; + wasmSymbols?: SymbolsAsset[]; + wasmNative: WasmAsset[]; + icu?: IcuAsset[]; satelliteResources?: { - [cultureName: string]: ResourceList; + [cultureName: string]: AssemblyAsset[]; }; - modulesAfterConfigLoaded?: ResourceList; - modulesAfterRuntimeReady?: ResourceList; + modulesAfterConfigLoaded?: JsAsset[]; + modulesAfterRuntimeReady?: JsAsset[]; extensions?: ResourceExtensions; - coreVfs?: { - [virtualPath: string]: ResourceList; - }; - vfs?: { - [virtualPath: string]: ResourceList; - }; + coreVfs?: VfsAsset[]; + vfs?: VfsAsset[]; } +type Asset = { + /** + * this should be absolute url to the asset + */ + resolvedUrl?: string; + /** + * If true, the runtime startup would not fail if the asset download was not successful. + */ + isOptional?: boolean; + /** + * If provided, runtime doesn't have to fetch the data. + * Runtime would set the buffer to null after instantiation to free the memory. + */ + buffer?: ArrayBuffer | Promise; + /** + * It's metadata + fetch-like Promise + * If provided, the runtime doesn't have to initiate the download. It would just await the response. + */ + pendingDownload?: LoadingResource; +}; +type WasmAsset = Asset & { + name: string; + hash?: string | null | ""; +}; +type AssemblyAsset = Asset & { + virtualPath: string; + name: string; + hash?: string | null | ""; +}; +type PdbAsset = Asset & { + virtualPath: string; + name: string; + hash?: string | null | ""; +}; +type JsAsset = Asset & { + /** + * If provided, runtime doesn't have to import it's JavaScript modules. + * This will not work for multi-threaded runtime. + */ + moduleExports?: any | Promise; + name?: string; +}; +type SymbolsAsset = Asset & { + name: string; +}; +type VfsAsset = Asset & { + virtualPath: string; + name: string; + hash?: string | null | ""; +}; +type IcuAsset = Asset & { + virtualPath: string; + name: string; + hash?: string | null | ""; +}; /** * A "key" is name of the file, a "value" is optional hash for integrity check. */ diff --git a/src/mono/browser/runtime/lazyLoading.ts b/src/mono/browser/runtime/lazyLoading.ts index 9787c18ac33ec3..88185b1fc18e7d 100644 --- a/src/mono/browser/runtime/lazyLoading.ts +++ b/src/mono/browser/runtime/lazyLoading.ts @@ -3,7 +3,8 @@ import { loaderHelpers } from "./globals"; import { load_lazy_assembly } from "./managed-exports"; -import { AssetEntry } from "./types"; +import { type AssemblyAsset, type PdbAsset } from "./types"; +import { type AssetEntryInternal } from "./types/internal"; export async function loadLazyAssembly (assemblyNameToLoad: string): Promise { const resources = loaderHelpers.config.resources!; @@ -20,50 +21,35 @@ export async function loadLazyAssembly (assemblyNameToLoad: string): Promise { - if (resources.fingerprinting && (asset.behavior == "assembly" || asset.behavior == "pdb" || asset.behavior == "resource")) { - asset.virtualPath = getNonFingerprintedAssetName(asset.name); - } + const addAsset = (asset: Asset, behavior: AssetBehaviors, isCore: boolean) => { + const assetEntry = asset as AssetEntryInternal; + assetEntry.behavior = behavior; if (isCore) { - asset.isCore = true; - coreAssetsToLoad.push(asset); + assetEntry.isCore = true; + coreAssetsToLoad.push(assetEntry); } else { - assetsToLoad.push(asset); + assetsToLoad.push(assetEntry); } }; if (resources.coreAssembly) { - for (const name in resources.coreAssembly) { - addAsset({ - name, - hash: resources.coreAssembly[name], - behavior: "assembly" - }, true); + for (let i = 0; i < resources.coreAssembly.length; i++) { + const asset = resources.coreAssembly[i]; + addAsset(asset, "assembly", true); } } if (resources.assembly) { - for (const name in resources.assembly) { - addAsset({ - name, - hash: resources.assembly[name], - behavior: "assembly" - }, !resources.coreAssembly); // if there are no core assemblies, then all assemblies are core + for (let i = 0; i < resources.assembly.length; i++) { + const asset = resources.assembly[i]; + addAsset(asset, "assembly", !resources.coreAssembly); } } if (config.debugLevel != 0 && loaderHelpers.isDebuggingSupported()) { if (resources.corePdb) { - for (const name in resources.corePdb) { - addAsset({ - name, - hash: resources.corePdb[name], - behavior: "pdb" - }, true); + for (let i = 0; i < resources.corePdb.length; i++) { + const asset = resources.corePdb[i]; + addAsset(asset, "pdb", true); } } if (resources.pdb) { - for (const name in resources.pdb) { - addAsset({ - name, - hash: resources.pdb[name], - behavior: "pdb" - }, !resources.corePdb); // if there are no core pdbs, then all pdbs are core + for (let i = 0; i < resources.pdb.length; i++) { + const asset = resources.pdb[i]; + addAsset(asset, "pdb", !resources.corePdb); } } } if (config.loadAllSatelliteResources && resources.satelliteResources) { for (const culture in resources.satelliteResources) { - for (const name in resources.satelliteResources[culture]) { - addAsset({ - name, - hash: resources.satelliteResources[culture][name], - behavior: "resource", - culture - }, !resources.coreAssembly); + for (let i = 0; i < resources.satelliteResources[culture].length; i++) { + const asset = resources.satelliteResources[culture][i] as AssemblyAsset & AssetEntryInternal; + asset.culture = culture; + addAsset(asset, "resource", !resources.coreAssembly); } } } if (resources.coreVfs) { - for (const virtualPath in resources.coreVfs) { - for (const name in resources.coreVfs[virtualPath]) { - addAsset({ - name, - hash: resources.coreVfs[virtualPath][name], - behavior: "vfs", - virtualPath - }, true); - } + for (let i = 0; i < resources.coreVfs.length; i++) { + const asset = resources.coreVfs[i]; + addAsset(asset, "vfs", true); } } if (resources.vfs) { - for (const virtualPath in resources.vfs) { - for (const name in resources.vfs[virtualPath]) { - addAsset({ - name, - hash: resources.vfs[virtualPath][name], - behavior: "vfs", - virtualPath - }, !resources.coreVfs); - } + for (let i = 0; i < resources.vfs.length; i++) { + const asset = resources.vfs[i]; + addAsset(asset, "vfs", !resources.coreVfs); } } const icuDataResourceName = getIcuResourceName(config); if (icuDataResourceName && resources.icu) { - for (const name in resources.icu) { - if (name === icuDataResourceName) { - assetsToLoad.push({ - name, - hash: resources.icu[name], - behavior: "icu", - loadRemote: true - }); + for (let i = 0; i < resources.icu.length; i++) { + const asset = resources.icu[i]; + if (asset.name === icuDataResourceName) { + addAsset(asset, "icu", false); } } } if (resources.wasmSymbols) { - for (const name in resources.wasmSymbols) { - coreAssetsToLoad.push({ - name, - hash: resources.wasmSymbols[name], - behavior: "symbols" - }); + for (let i = 0; i < resources.wasmSymbols.length; i++) { + const asset = resources.wasmSymbols[i]; + addAsset(asset, "symbols", false); } } } @@ -451,15 +411,6 @@ export function prepareAssets () { config.assets = [...coreAssetsToLoad, ...assetsToLoad, ...modulesAssets]; } -export function getNonFingerprintedAssetName (assetName: string) { - const fingerprinting = loaderHelpers.config.resources?.fingerprinting; - if (fingerprinting && fingerprinting[assetName]) { - return fingerprinting[assetName]; - } - - return assetName; -} - export function prepareAssetsWorker () { const config = loaderHelpers.config; mono_assert(config.assets, "config.assets must be defined"); diff --git a/src/mono/browser/runtime/loader/config.ts b/src/mono/browser/runtime/loader/config.ts index 3ae87d5109b75d..67c769aa0fab40 100644 --- a/src/mono/browser/runtime/loader/config.ts +++ b/src/mono/browser/runtime/loader/config.ts @@ -5,7 +5,7 @@ import BuildConfiguration from "consts:configuration"; import WasmEnableThreads from "consts:wasmEnableThreads"; import { type DotnetModuleInternal, type MonoConfigInternal, JSThreadBlockingMode } from "../types/internal"; -import type { BootModule, DotnetModuleConfig, MonoConfig, ResourceGroups, ResourceList } from "../types"; +import type { AssemblyAsset, Assets, BootModule, DotnetModuleConfig, IcuAsset, JsAsset, MonoConfig, PdbAsset, SymbolsAsset, VfsAsset, WasmAsset } from "../types"; import { exportedRuntimeAPI, loaderHelpers, runtimeHelpers } from "./globals"; import { mono_log_error, mono_log_debug } from "./logging"; import { importLibraryInitializers, invokeLibraryInitializers } from "./libraryInitializers"; @@ -24,10 +24,10 @@ export function deep_merge_config (target: MonoConfigInternal, source: MonoConfi } if (providedConfig.resources !== undefined) { providedConfig.resources = deep_merge_resources(target.resources || { - assembly: {}, - jsModuleNative: {}, - jsModuleRuntime: {}, - wasmNative: {} + assembly: [], + jsModuleNative: [], + jsModuleRuntime: [], + wasmNative: [] }, providedConfig.resources); } if (providedConfig.environmentVariables !== undefined) { @@ -51,11 +51,11 @@ export function deep_merge_module (target: DotnetModuleInternal, source: DotnetM return Object.assign(target, providedConfig); } -function deep_merge_resources (target: ResourceGroups, source: ResourceGroups): ResourceGroups { +function deep_merge_resources (target: Assets, source: Assets): Assets { // no need to merge the same object if (target === source) return target; - const providedResources: ResourceGroups = { ...source }; + const providedResources: Assets = { ...source }; if (providedResources.assembly !== undefined) { providedResources.assembly = { ...(target.assembly || {}), ...(providedResources.assembly || {}) }; } @@ -87,7 +87,7 @@ function deep_merge_resources (target: ResourceGroups, source: ResourceGroups): providedResources.icu = { ...(target.icu || {}), ...(providedResources.icu || {}) }; } if (providedResources.satelliteResources !== undefined) { - providedResources.satelliteResources = deep_merge_dict(target.satelliteResources || {}, providedResources.satelliteResources || {}); + providedResources.satelliteResources = deepMergeSatelliteResources(target.satelliteResources || {}, providedResources.satelliteResources || {}); } if (providedResources.modulesAfterConfigLoaded !== undefined) { providedResources.modulesAfterConfigLoaded = { ...(target.modulesAfterConfigLoaded || {}), ...(providedResources.modulesAfterConfigLoaded || {}) }; @@ -99,12 +99,12 @@ function deep_merge_resources (target: ResourceGroups, source: ResourceGroups): providedResources.extensions = { ...(target.extensions || {}), ...(providedResources.extensions || {}) }; } if (providedResources.vfs !== undefined) { - providedResources.vfs = deep_merge_dict(target.vfs || {}, providedResources.vfs || {}); + providedResources.vfs = { ...(target.vfs || {}), ...(providedResources.vfs || {}) }; } return Object.assign(target, providedResources); } -function deep_merge_dict (target: { [key: string]: ResourceList }, source: { [key: string]: ResourceList }) { +function deepMergeSatelliteResources (target: { [key: string]: AssemblyAsset[] }, source: { [key: string]: AssemblyAsset[] }) { // no need to merge the same object if (target === source) return target; @@ -122,56 +122,53 @@ export function normalizeConfig () { config.environmentVariables = config.environmentVariables || {}; config.runtimeOptions = config.runtimeOptions || []; config.resources = config.resources || { - assembly: {}, - jsModuleNative: {}, - jsModuleWorker: {}, - jsModuleRuntime: {}, - wasmNative: {}, - vfs: {}, - satelliteResources: {}, + assembly: [], + jsModuleNative: [], + jsModuleWorker: [], + jsModuleRuntime: [], + wasmNative: [], + vfs: [], + satelliteResources: {} }; if (config.assets) { mono_log_debug("config.assets is deprecated, use config.resources instead"); for (const asset of config.assets) { - const resource = {} as ResourceList; - resource[asset.name] = asset.hash || ""; - const toMerge = {} as ResourceGroups; + const toMerge = {} as Assets; switch (asset.behavior as string) { case "assembly": - toMerge.assembly = resource; + toMerge.assembly = [asset as AssemblyAsset]; break; case "pdb": - toMerge.pdb = resource; + toMerge.pdb = [asset as PdbAsset]; break; case "resource": toMerge.satelliteResources = {}; - toMerge.satelliteResources[asset.culture!] = resource; + toMerge.satelliteResources[asset.culture!] = [asset as AssemblyAsset]; break; case "icu": - toMerge.icu = resource; + toMerge.icu = [asset as IcuAsset]; break; case "symbols": - toMerge.wasmSymbols = resource; + toMerge.wasmSymbols = [asset as SymbolsAsset]; break; case "vfs": - toMerge.vfs = {}; - toMerge.vfs[asset.virtualPath!] = resource; + toMerge.vfs = [asset as VfsAsset]; break; case "dotnetwasm": - toMerge.wasmNative = resource; + toMerge.wasmNative = [asset as WasmAsset]; break; case "js-module-threads": - toMerge.jsModuleWorker = resource; + toMerge.jsModuleWorker = [asset as JsAsset]; break; case "js-module-runtime": - toMerge.jsModuleRuntime = resource; + toMerge.jsModuleRuntime = [asset as JsAsset]; break; case "js-module-native": - toMerge.jsModuleNative = resource; + toMerge.jsModuleNative = [asset as JsAsset]; break; case "js-module-diagnostics": - toMerge.jsModuleDiagnostics = resource; + toMerge.jsModuleDiagnostics = [asset as JsAsset]; break; case "js-module-dotnet": // don't merge loader diff --git a/src/mono/browser/runtime/loader/icu.ts b/src/mono/browser/runtime/loader/icu.ts index 1e90674a67dcce..9ca4936e02be22 100644 --- a/src/mono/browser/runtime/loader/icu.ts +++ b/src/mono/browser/runtime/loader/icu.ts @@ -5,7 +5,6 @@ import { mono_log_error } from "./logging"; import { GlobalizationMode, MonoConfig } from "../types"; import { ENVIRONMENT_IS_WEB, loaderHelpers } from "./globals"; import { mono_log_info, mono_log_debug } from "./logging"; -import { getNonFingerprintedAssetName } from "./assets"; export function init_globalization () { loaderHelpers.preferredIcuAsset = getIcuResourceName(loaderHelpers.config); @@ -48,24 +47,13 @@ export function getIcuResourceName (config: MonoConfig): string | null { // TODO: when starting on sidecar, we should pass default culture from UI thread const culture = config.applicationCulture || (ENVIRONMENT_IS_WEB ? (globalThis.navigator && globalThis.navigator.languages && globalThis.navigator.languages[0]) : Intl.DateTimeFormat().resolvedOptions().locale); - const icuFiles = Object.keys(config.resources.icu); - const fileMapping: { - [k: string]: string - } = {}; - for (let index = 0; index < icuFiles.length; index++) { - const icuFile = icuFiles[index]; - if (config.resources.fingerprinting) { - fileMapping[getNonFingerprintedAssetName(icuFile)] = icuFile; - } else { - fileMapping[icuFile] = icuFile; - } - } + const icuFiles = config.resources.icu; let icuFile = null; if (config.globalizationMode === GlobalizationMode.Custom) { // custom ICU file is saved in the resources with fingerprinting and does not require mapping if (icuFiles.length >= 1) { - return icuFiles[0]; + return icuFiles[0].name; } } else if (!culture || config.globalizationMode === GlobalizationMode.All) { icuFile = "icudt.dat"; @@ -73,8 +61,13 @@ export function getIcuResourceName (config: MonoConfig): string | null { icuFile = getShardedIcuResourceName(culture); } - if (icuFile && fileMapping[icuFile]) { - return fileMapping[icuFile]; + if (icuFile) { + for (let i = 0; i < icuFiles.length; i++) { + const asset = icuFiles[i]; + if (asset.name === icuFile) { + return asset.name; + } + } } } diff --git a/src/mono/browser/runtime/loader/libraryInitializers.ts b/src/mono/browser/runtime/loader/libraryInitializers.ts index 4dd0ccfed9ff25..fa3e6d65b11867 100644 --- a/src/mono/browser/runtime/loader/libraryInitializers.ts +++ b/src/mono/browser/runtime/loader/libraryInitializers.ts @@ -5,15 +5,14 @@ import { mono_log_debug, mono_log_warn } from "./logging"; import { appendUniqueQuery } from "./assets"; import { loaderHelpers } from "./globals"; import { mono_exit } from "./exit"; -import { ResourceList } from "../types"; +import { JsAsset } from "../types"; -export async function importLibraryInitializers (libraryInitializers: ResourceList | undefined): Promise { +export async function importLibraryInitializers (libraryInitializers: JsAsset[] | undefined): Promise { if (!libraryInitializers) { return; } - const initializerFiles = Object.keys(libraryInitializers); - await Promise.all(initializerFiles.map(f => importInitializer(f))); + await Promise.all((libraryInitializers ?? []).map(i => importInitializer(i.name!))); async function importInitializer (path: string): Promise { try { diff --git a/src/mono/browser/runtime/satelliteAssemblies.ts b/src/mono/browser/runtime/satelliteAssemblies.ts index 713cfb94161f9b..ebbf6d88294bda 100644 --- a/src/mono/browser/runtime/satelliteAssemblies.ts +++ b/src/mono/browser/runtime/satelliteAssemblies.ts @@ -3,7 +3,8 @@ import { loaderHelpers } from "./globals"; import { load_satellite_assembly } from "./managed-exports"; -import { AssetEntry } from "./types"; +import type { AssemblyAsset } from "./types"; +import type { AssetEntryInternal } from "./types/internal"; export async function loadSatelliteAssemblies (culturesToLoad: string[]): Promise { const satelliteResources = loaderHelpers.config.resources!.satelliteResources; @@ -15,14 +16,10 @@ export async function loadSatelliteAssemblies (culturesToLoad: string[]): Promis .filter(culture => Object.prototype.hasOwnProperty.call(satelliteResources, culture)) .map(culture => { const promises: Promise[] = []; - for (const name in satelliteResources[culture]) { - const asset: AssetEntry = { - name, - hash: satelliteResources[culture][name], - behavior: "resource", - culture - }; - + for (let i = 0; i < satelliteResources[culture].length; i++) { + const asset = satelliteResources[culture][i] as AssemblyAsset & AssetEntryInternal; + asset.behavior = "resource"; + asset.culture = culture; promises.push(loaderHelpers.retrieve_asset_download(asset)); } diff --git a/src/mono/browser/runtime/types/index.ts b/src/mono/browser/runtime/types/index.ts index a816f772964ac4..c32d7df349f7a6 100644 --- a/src/mono/browser/runtime/types/index.ts +++ b/src/mono/browser/runtime/types/index.ts @@ -256,20 +256,20 @@ export type Asset = { pendingDownload?: LoadingResource } -export type AssemblyAsset = Asset & { +export type WasmAsset = Asset & { name: string; - url: string; hash?: string | null | ""; } -export type WasmAsset = Asset & { - url: string; +export type AssemblyAsset = Asset & { + virtualPath: string; + name: string; // actually URL hash?: string | null | ""; } export type PdbAsset = Asset & { - name: string; - url: string; + virtualPath: string; + name: string; // actually URL hash?: string | null | ""; } @@ -280,22 +280,22 @@ export type JsAsset = Asset & { */ moduleExports?: any | Promise, - url?: string; + name?: string; // actually URL } export type SymbolsAsset = Asset & { - url: string; + name: string; // actually URL } export type VfsAsset = Asset & { virtualPath: string; - url: string; + name: string; // actually URL hash?: string | null | ""; } export type IcuAsset = Asset & { - name: string; - url: string; + virtualPath: string; + name: string; // actually URL hash?: string | null | ""; } 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 8062abe52a1eb9..0511c552516dcc 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs @@ -192,12 +192,12 @@ public void TransformResourcesToAssets(BootJsonData config) assets.wasmNative = resources.wasmNative?.Select(a => new WasmAsset() { - url = a.Key, + name = a.Key, integrity = a.Value }).ToList(); assets.wasmSymbols = resources.wasmSymbols?.Select(a => new SymbolsAsset() { - url = a.Key, + name = a.Key, }).ToList(); assets.icu = MapGeneralAssets(resources.icu); @@ -226,20 +226,20 @@ public void TransformResourcesToAssets(BootJsonData config) List? MapGeneralAssets(Dictionary? assets) => assets?.Select(a => new GeneralAsset() { - name = resources.fingerprinting?[a.Key] ?? a.Key, - url = a.Key, + virtualPath = resources.fingerprinting?[a.Key] ?? a.Key, + name = a.Key, integrity = a.Value }).ToList(); List? MapJsAssets(Dictionary? assets) => assets?.Select(a => new JsAsset() { - url = a.Key + name = a.Key }).ToList(); List? MapVfsAssets(Dictionary>? assets) => assets?.Select(a => new VfsAsset() { virtualPath = a.Key, - url = a.Value.Keys.First(), + name = a.Value.Keys.First(), integrity = a.Value.Values.First() }).ToList(); 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 fce4f87487c4e0..178febdd19594e 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -342,27 +342,27 @@ public class AssetsData [DataContract] public class JsAsset { - public string url { get; set; } + public string name { get; set; } } [DataContract] public class SymbolsAsset { - public string url { get; set; } + public string name { get; set; } } [DataContract] public class WasmAsset { - public string url { get; set; } + public string name { get; set; } public string integrity { get; set; } } [DataContract] public class GeneralAsset { + public string virtualPath { get; set; } public string name { get; set; } - public string url { get; set; } public string integrity { get; set; } } @@ -370,7 +370,7 @@ public class GeneralAsset public class VfsAsset { public string virtualPath { get; set; } - public string url { get; set; } + public string name { get; set; } public string integrity { get; set; } } From 953aa72ad708982cb78164524bf319e1351e0bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 4 Jun 2025 14:34:15 +0200 Subject: [PATCH 07/32] wip --- src/mono/browser/runtime/loader/config.ts | 26 +++++++++---------- src/mono/sample/wasm/Directory.Build.targets | 2 +- src/mono/sample/wasm/browser-advanced/main.js | 6 ++--- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/mono/browser/runtime/loader/config.ts b/src/mono/browser/runtime/loader/config.ts index 67c769aa0fab40..f8767749f7638b 100644 --- a/src/mono/browser/runtime/loader/config.ts +++ b/src/mono/browser/runtime/loader/config.ts @@ -57,49 +57,49 @@ function deep_merge_resources (target: Assets, source: Assets): Assets { const providedResources: Assets = { ...source }; if (providedResources.assembly !== undefined) { - providedResources.assembly = { ...(target.assembly || {}), ...(providedResources.assembly || {}) }; + providedResources.assembly = [...(target.assembly || []), ...(providedResources.assembly || [])]; } if (providedResources.lazyAssembly !== undefined) { - providedResources.lazyAssembly = { ...(target.lazyAssembly || {}), ...(providedResources.lazyAssembly || {}) }; + providedResources.lazyAssembly = [...(target.lazyAssembly || []), ...(providedResources.lazyAssembly || [])]; } if (providedResources.pdb !== undefined) { - providedResources.pdb = { ...(target.pdb || {}), ...(providedResources.pdb || {}) }; + providedResources.pdb = [...(target.pdb || []), ...(providedResources.pdb || [])]; } if (providedResources.jsModuleWorker !== undefined) { - providedResources.jsModuleWorker = { ...(target.jsModuleWorker || {}), ...(providedResources.jsModuleWorker || {}) }; + providedResources.jsModuleWorker = [...(target.jsModuleWorker || []), ...(providedResources.jsModuleWorker || [])]; } if (providedResources.jsModuleNative !== undefined) { - providedResources.jsModuleNative = { ...(target.jsModuleNative || {}), ...(providedResources.jsModuleNative || {}) }; + providedResources.jsModuleNative = [...(target.jsModuleNative || []), ...(providedResources.jsModuleNative || [])]; } if (providedResources.jsModuleDiagnostics !== undefined) { - providedResources.jsModuleDiagnostics = { ...(target.jsModuleDiagnostics || {}), ...(providedResources.jsModuleDiagnostics || {}) }; + providedResources.jsModuleDiagnostics = [...(target.jsModuleDiagnostics || []), ...(providedResources.jsModuleDiagnostics || [])]; } if (providedResources.jsModuleRuntime !== undefined) { - providedResources.jsModuleRuntime = { ...(target.jsModuleRuntime || {}), ...(providedResources.jsModuleRuntime || {}) }; + providedResources.jsModuleRuntime = [...(target.jsModuleRuntime || []), ...(providedResources.jsModuleRuntime || [])]; } if (providedResources.wasmSymbols !== undefined) { - providedResources.wasmSymbols = { ...(target.wasmSymbols || {}), ...(providedResources.wasmSymbols || {}) }; + providedResources.wasmSymbols = [...(target.wasmSymbols || []), ...(providedResources.wasmSymbols || [])]; } if (providedResources.wasmNative !== undefined) { - providedResources.wasmNative = { ...(target.wasmNative || {}), ...(providedResources.wasmNative || {}) }; + providedResources.wasmNative = [...(target.wasmNative || []), ...(providedResources.wasmNative || [])]; } if (providedResources.icu !== undefined) { - providedResources.icu = { ...(target.icu || {}), ...(providedResources.icu || {}) }; + providedResources.icu = [...(target.icu || []), ...(providedResources.icu || [])]; } if (providedResources.satelliteResources !== undefined) { providedResources.satelliteResources = deepMergeSatelliteResources(target.satelliteResources || {}, providedResources.satelliteResources || {}); } if (providedResources.modulesAfterConfigLoaded !== undefined) { - providedResources.modulesAfterConfigLoaded = { ...(target.modulesAfterConfigLoaded || {}), ...(providedResources.modulesAfterConfigLoaded || {}) }; + providedResources.modulesAfterConfigLoaded = [...(target.modulesAfterConfigLoaded || []), ...(providedResources.modulesAfterConfigLoaded || [])]; } if (providedResources.modulesAfterRuntimeReady !== undefined) { - providedResources.modulesAfterRuntimeReady = { ...(target.modulesAfterRuntimeReady || {}), ...(providedResources.modulesAfterRuntimeReady || {}) }; + 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 || {}) }; + providedResources.vfs = [...(target.vfs || []), ...(providedResources.vfs || [])]; } return Object.assign(target, providedResources); } diff --git a/src/mono/sample/wasm/Directory.Build.targets b/src/mono/sample/wasm/Directory.Build.targets index d51375b28356c9..6acedb3db4b82f 100644 --- a/src/mono/sample/wasm/Directory.Build.targets +++ b/src/mono/sample/wasm/Directory.Build.targets @@ -82,7 +82,7 @@ - + diff --git a/src/mono/sample/wasm/browser-advanced/main.js b/src/mono/sample/wasm/browser-advanced/main.js index 6a22eee4a5c0a8..016a3771aa08c1 100644 --- a/src/mono/sample/wasm/browser-advanced/main.js +++ b/src/mono/sample/wasm/browser-advanced/main.js @@ -39,9 +39,9 @@ try { .withConfig({ maxParallelDownloads: 1, resources: { - modulesAfterConfigLoaded: { - "advanced-sample.lib.module.js": "" - } + modulesAfterConfigLoaded: [{ + "name": "advanced-sample.lib.module.js" + }] } }) .withModuleConfig({ From cc0a5497e75160c14ca22b46b25dd8f8016bb38b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 4 Jun 2025 16:59:03 +0200 Subject: [PATCH 08/32] wip --- .../wasm/Wasm.Build.Tests/Blazor/MiscTests.cs | 2 +- .../Wasm.Build.Tests/ProjectProviderBase.cs | 44 ++++++++++--------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs index 2a7d9b58cae8cb..b8eaf4afcb4b0e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs @@ -96,6 +96,6 @@ public void BugRegression_60479_WithRazorClassLib() string bootConfigPath = _provider.GetBootConfigPath(GetBlazorBinFrameworkDir(config, forPublish: true)); BootJsonData bootJson = _provider.GetBootJson(bootConfigPath); - Assert.Contains(bootJson.resources.lazyAssembly.Keys, f => f.StartsWith(razorClassLibraryName)); + Assert.Contains(((AssetsData)bootJson.resources).lazyAssembly, f => f.name.StartsWith(razorClassLibraryName)); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs index 30dbee46735d21..351fd1f1cb2d23 100644 --- a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs @@ -380,14 +380,22 @@ private string[] GetFilesMatchingNameConsideringFingerprinting(string filePath, { string bootJsonPath = GetBootConfigPath(paths.BinFrameworkDir, "dotnet.js"); BootJsonData bootJson = GetBootJson(bootJsonPath); + AssetsData assets = (AssetsData)bootJson.resources; var keysToUpdate = new List(); var updates = new List<(string oldKey, string newKey, (string fullPath, bool unchanged) value)>(); + foreach (var expectedItem in dict) { string filename = Path.GetFileName(expectedItem.Value.fullPath); - var expectedFingerprintedItem = bootJson.resources.fingerprinting - .Where(kv => kv.Value == filename) - .SingleOrDefault().Key; + string? expectedFingerprintedItem = filename switch + { + "dotnet.runtime.js" => assets.jsModuleRuntime?.SingleOrDefault()?.name, + "dotnet.native.js" => assets.jsModuleNative?.SingleOrDefault()?.name, + "dotnet.native.wasm" => assets.wasmNative?.SingleOrDefault()?.name, + _ => filename == $"{projectName}{WasmAssemblyExtension}" + ? assets.assembly?.SingleOrDefault(a => a.virtualPath == $"{projectName}{WasmAssemblyExtension}")?.name + : null + }; if (string.IsNullOrEmpty(expectedFingerprintedItem)) continue; @@ -436,6 +444,8 @@ public static void AssertDotNetJsSymbols(AssertBundleOptions assertOptions) public void AssertIcuAssets(AssertBundleOptions assertOptions, BootJsonData bootJson) { + AssetsData assets = (AssetsData)bootJson.resources; + List expected = new(); switch (assertOptions.BuildOptions.GlobalizationMode) { @@ -468,7 +478,7 @@ public void AssertIcuAssets(AssertBundleOptions assertOptions, BootJsonData boot var expectedFingerprinted = new List(expected.Count); foreach (var expectedItem in expected) { - var expectedFingerprintedItem = bootJson.resources.fingerprinting.Where(kv => kv.Value == expectedItem).SingleOrDefault().Key; + var expectedFingerprintedItem = assets.icu.FirstOrDefault(a => a.virtualPath == expectedItem)?.name; if (string.IsNullOrEmpty(expectedFingerprintedItem)) throw new XunitException($"Could not find ICU asset {expectedItem} in fingerprinting in boot config"); @@ -540,30 +550,24 @@ public string GetBootConfigPath(string binFrameworkDir, string? bootConfigFileNa public BootJsonData AssertBootJson(AssertBundleOptions options) { + EnsureProjectDirIsSet(); string bootJsonPath = GetBootConfigPath(options.BinFrameworkDir, options.BuildOptions.BootConfigFileName); BootJsonData bootJson = GetBootJson(bootJsonPath); - string spcExpectedFilename = $"System.Private.CoreLib{WasmAssemblyExtension}"; + AssetsData assets = (AssetsData)bootJson.resources; - if (IsFingerprintingEnabled) - { - spcExpectedFilename = bootJson.resources.fingerprinting.Where(kv => kv.Value == spcExpectedFilename).SingleOrDefault().Key; - if (string.IsNullOrEmpty(spcExpectedFilename)) - throw new XunitException($"Could not find an assembly System.Private.CoreLib in fingerprinting in {bootJsonPath}"); - } + string spcExpectedFilename = $"System.Private.CoreLib{WasmAssemblyExtension}"; - string? spcActualFilename = bootJson.resources.coreAssembly.Keys - .Where(a => a == spcExpectedFilename) - .SingleOrDefault(); + string? spcActualFilename = assets.coreAssembly.SingleOrDefault(a => a.virtualPath == spcExpectedFilename)?.name; if (spcActualFilename is null) throw new XunitException($"Could not find an assembly named System.Private.CoreLib.* in {bootJsonPath}"); - var bootJsonEntries = bootJson.resources.jsModuleNative.Keys - .Union(bootJson.resources.wasmNative.Keys) - .Union(bootJson.resources.jsModuleRuntime.Keys) - .Union(bootJson.resources.jsModuleWorker?.Keys ?? Enumerable.Empty()) - .Union(bootJson.resources.jsModuleDiagnostics?.Keys ?? Enumerable.Empty()) - .Union(bootJson.resources.wasmSymbols?.Keys ?? Enumerable.Empty()) + var bootJsonEntries = assets.jsModuleNative.Select(a => a.name) + .Union(assets.wasmNative.Select(a => a.name)) + .Union(assets.jsModuleRuntime.Select(a => a.name)) + .Union(assets.jsModuleWorker?.Select(a => a.name) ?? Enumerable.Empty()) + .Union(assets.jsModuleDiagnostics?.Select(a => a.name) ?? Enumerable.Empty()) + .Union(assets.wasmSymbols?.Select(a => a.name) ?? Enumerable.Empty()) .ToArray(); var expectedEntries = new SortedDictionary>(); From 38d1c31646b402c17c15702c2d283d26c9130342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 5 Jun 2025 09:27:49 +0200 Subject: [PATCH 09/32] wip --- .../Wasm.Build.Tests/ProjectProviderBase.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs index 351fd1f1cb2d23..6d4b9227e9f488 100644 --- a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs @@ -11,6 +11,7 @@ using System.Runtime.Serialization.Json; using System.Text; using System.Text.Json; +using System.Text.Json.Serialization; using System.Text.RegularExpressions; using Microsoft.NET.Sdk.WebAssembly; using Xunit; @@ -625,7 +626,9 @@ public static BootJsonData ParseBootData(string bootConfigPath) string jsonContent = GetBootJsonContent(bootConfigPath); try { - BootJsonData? config = JsonSerializer.Deserialize(jsonContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + options.Converters.Add(new ResourcesConverter()); + BootJsonData? config = JsonSerializer.Deserialize(jsonContent, options); Assert.NotNull(config); return config!; } @@ -685,4 +688,29 @@ protected void EnsureProjectDirIsSet() if (string.IsNullOrEmpty(ProjectDir)) throw new Exception($"{nameof(ProjectDir)} is not set"); } + + internal class ResourcesConverter : JsonConverter + { + public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.StartObject) + { + try + { + return JsonSerializer.Deserialize(ref reader, options)!; + } + catch + { + return JsonSerializer.Deserialize(ref reader, options)!; + } + } + + return JsonSerializer.Deserialize(ref reader, options)!; + } + + public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value, value.GetType(), options); + } + } } From fd7b15d8c2e9de1f63b5afe2771a8621b684569b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 5 Jun 2025 12:00:33 +0200 Subject: [PATCH 10/32] wip --- src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs index 6d4b9227e9f488..d04f0d878e73a7 100644 --- a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs @@ -697,7 +697,10 @@ public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS { try { - return JsonSerializer.Deserialize(ref reader, options)!; + var nestedOptions = new JsonSerializerOptions(options); + nestedOptions.Converters.Remove(this); + + return JsonSerializer.Deserialize(ref reader, nestedOptions)!; } catch { From b61ac1d70b557b9b07951c423c49c3877cc4755d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 5 Jun 2025 13:11:03 +0200 Subject: [PATCH 11/32] wip --- src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs index d04f0d878e73a7..f051a3a2617937 100644 --- a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs @@ -693,22 +693,23 @@ internal class ResourcesConverter : JsonConverter { public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + var nestedOptions = new JsonSerializerOptions(options); + nestedOptions.Converters.Remove(this); + if (reader.TokenType == JsonTokenType.StartObject) { try { - var nestedOptions = new JsonSerializerOptions(options); - nestedOptions.Converters.Remove(this); return JsonSerializer.Deserialize(ref reader, nestedOptions)!; } catch { - return JsonSerializer.Deserialize(ref reader, options)!; + return JsonSerializer.Deserialize(ref reader, nestedOptions)!; } } - return JsonSerializer.Deserialize(ref reader, options)!; + return JsonSerializer.Deserialize(ref reader, nestedOptions)!; } public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) From 75afef0cba004ef66c842fa964c4f1d2caaa876d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 5 Jun 2025 13:18:33 +0200 Subject: [PATCH 12/32] wip --- src/mono/browser/runtime/loader/icu.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/browser/runtime/loader/icu.ts b/src/mono/browser/runtime/loader/icu.ts index 9ca4936e02be22..4e5b41bff13a24 100644 --- a/src/mono/browser/runtime/loader/icu.ts +++ b/src/mono/browser/runtime/loader/icu.ts @@ -64,7 +64,7 @@ export function getIcuResourceName (config: MonoConfig): string | null { if (icuFile) { for (let i = 0; i < icuFiles.length; i++) { const asset = icuFiles[i]; - if (asset.name === icuFile) { + if (asset.virtualPath === icuFile) { return asset.name; } } From 0d9d77b597cd0306f460f5d2768095a738999c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 5 Jun 2025 13:25:24 +0200 Subject: [PATCH 13/32] wip --- src/mono/browser/runtime/lazyLoading.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mono/browser/runtime/lazyLoading.ts b/src/mono/browser/runtime/lazyLoading.ts index 88185b1fc18e7d..2643a4d6b5cb79 100644 --- a/src/mono/browser/runtime/lazyLoading.ts +++ b/src/mono/browser/runtime/lazyLoading.ts @@ -25,7 +25,7 @@ export async function loadLazyAssembly (assemblyNameToLoad: string): Promise Date: Fri, 6 Jun 2025 10:23:46 +0200 Subject: [PATCH 14/32] wip --- src/mono/browser/runtime/loader/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/browser/runtime/loader/config.ts b/src/mono/browser/runtime/loader/config.ts index f8767749f7638b..1c3fc4df3e392d 100644 --- a/src/mono/browser/runtime/loader/config.ts +++ b/src/mono/browser/runtime/loader/config.ts @@ -109,7 +109,7 @@ function deepMergeSatelliteResources (target: { [key: string]: AssemblyAsset[] } if (target === source) return target; for (const key in source) { - target[key] = { ...target[key], ...source[key] }; + target[key] = [...target[key], ...source[key]]; } return target; } From 5a929163073d5dc0b0afe7aeb1d41b67a419977b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 6 Jun 2025 12:19:59 +0200 Subject: [PATCH 15/32] wip --- src/mono/browser/runtime/loader/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/browser/runtime/loader/config.ts b/src/mono/browser/runtime/loader/config.ts index 1c3fc4df3e392d..084e804132a040 100644 --- a/src/mono/browser/runtime/loader/config.ts +++ b/src/mono/browser/runtime/loader/config.ts @@ -109,7 +109,7 @@ function deepMergeSatelliteResources (target: { [key: string]: AssemblyAsset[] } if (target === source) return target; for (const key in source) { - target[key] = [...target[key], ...source[key]]; + target[key] = [...target[key] || [], ...source[key] || []]; } return target; } From 346d49a1ff223ad2d9f142f4883837657437e4a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 6 Jun 2025 14:04:33 +0200 Subject: [PATCH 16/32] wip --- src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs index f051a3a2617937..e19d13914ea094 100644 --- a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs @@ -384,6 +384,7 @@ private string[] GetFilesMatchingNameConsideringFingerprinting(string filePath, AssetsData assets = (AssetsData)bootJson.resources; var keysToUpdate = new List(); var updates = new List<(string oldKey, string newKey, (string fullPath, bool unchanged) value)>(); + List allAssemblies = [..assets.coreAssembly, ..assets.assembly]; foreach (var expectedItem in dict) { @@ -394,7 +395,7 @@ private string[] GetFilesMatchingNameConsideringFingerprinting(string filePath, "dotnet.native.js" => assets.jsModuleNative?.SingleOrDefault()?.name, "dotnet.native.wasm" => assets.wasmNative?.SingleOrDefault()?.name, _ => filename == $"{projectName}{WasmAssemblyExtension}" - ? assets.assembly?.SingleOrDefault(a => a.virtualPath == $"{projectName}{WasmAssemblyExtension}")?.name + ? allAssemblies?.SingleOrDefault(a => a.virtualPath == $"{projectName}{WasmAssemblyExtension}")?.name : null }; From de5ea7faf60afb571c945589515e6f2f6632696e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 10 Jun 2025 12:18:34 +0200 Subject: [PATCH 17/32] Bundler friendly imports --- src/mono/browser/runtime/rollup.config.js | 1 + ...rosoft.NET.Sdk.WebAssembly.Browser.targets | 9 +- .../BootJsonBuilderHelper.cs | 102 +++++++++++++++--- .../BootJsonData.cs | 3 + .../GenerateWasmBootJson.cs | 7 +- 5 files changed, 103 insertions(+), 19 deletions(-) diff --git a/src/mono/browser/runtime/rollup.config.js b/src/mono/browser/runtime/rollup.config.js index ec1de7d67b0f75..1966e3dfc7bb9f 100644 --- a/src/mono/browser/runtime/rollup.config.js +++ b/src/mono/browser/runtime/rollup.config.js @@ -171,6 +171,7 @@ const loaderConfig = { format: "es", file: nativeBinDir + "/dotnet.js", banner, + intro: "/*! bundlerFriendlyImports */", plugins, sourcemap: true, sourcemapPathTransform, 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 9422801595deee..7e25aac29b4dc8 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 @@ -213,6 +213,9 @@ Copyright (c) .NET Foundation. All rights reserved. <_WasmFingerprintBootConfig Condition="'$(_WasmFingerprintBootConfig)' == ''">false <_WasmPreloadAssets>$(WasmPreloadAssets) <_WasmPreloadAssets Condition="'$(_WasmPreloadAssets)' == ''">true + + <_WasmBundlerFriendlyBootConfig>$(WasmBundlerFriendlyBootConfig) + <_WasmBundlerFriendlyBootConfig Condition="'$(_WasmBundlerFriendlyBootConfig)' == ''">false $(OutputPath)$(PublishDirName)\ @@ -409,7 +412,8 @@ Copyright (c) .NET Foundation. All rights reserved. IsPublish="false" IsAot="$(RunAOTCompilation)" IsMultiThreaded="$(WasmEnableThreads)" - FingerprintAssets="$(_WasmFingerprintAssets)" /> + FingerprintAssets="$(_WasmFingerprintAssets)" + BundlerFriendly="$(_WasmBundlerFriendlyBootConfig)" /> @@ -817,7 +821,8 @@ Copyright (c) .NET Foundation. All rights reserved. IsPublish="true" IsAot="$(RunAOTCompilation)" IsMultiThreaded="$(WasmEnableThreads)" - FingerprintAssets="$(_WasmFingerprintAssets)" /> + FingerprintAssets="$(_WasmFingerprintAssets)" + BundlerFriendly="$(_WasmBundlerFriendlyBootConfig)" /> 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 0511c552516dcc..4b63bfcd3d0801 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -18,6 +19,7 @@ public class BootJsonBuilderHelper(TaskLoggingHelper Log, string DebugLevel, boo { #pragma warning disable SYSLIB1045 // Convert to 'GeneratedRegexAttribute'. internal static readonly Regex mergeWithPlaceholderRegex = new Regex(@"/\*!\s*dotnetBootConfig\s*\*/\s*{}"); + internal static readonly Regex bundlerFriendlyImportsRegex = new Regex(@"/\*!\s*bundlerFriendlyImports\s*\*/"); #pragma warning restore SYSLIB1045 // Convert to 'GeneratedRegexAttribute'. private static readonly string[] coreAssemblyNames = [ @@ -52,18 +54,33 @@ public bool IsCoreAssembly(string fileName) return false; } - public void WriteConfigToFile(BootJsonData config, string outputPath, string? outputFileExtension = null, string? mergeWith = null) + public void WriteConfigToFile(BootJsonData config, string outputPath, string? outputFileExtension = null, string? mergeWith = null, string? imports = null) { var output = JsonSerializer.Serialize(config, JsonOptions); + // Remove the $#[ and ]#$" that are used to mark JS variable usage. + output = output + .Replace("\"$#[", string.Empty) + .Replace("]#$\"", string.Empty); + outputFileExtension ??= Path.GetExtension(outputPath); Log.LogMessage($"Write config in format '{outputFileExtension}'"); if (mergeWith != null) { string existingContent = File.ReadAllText(mergeWith); - output = mergeWithPlaceholderRegex.Replace(existingContent, e => $"/*json-start*/{output}/*json-end*/"); - if (existingContent.Equals(output)) - Log.LogError($"Merging boot config into '{mergeWith}' failed to find the placeholder."); + output = ReplaceWithAssert( + mergeWithPlaceholderRegex, + existingContent, + $"/*json-start*/{output}/*json-end*/", + $"Merging boot config into '{mergeWith}' failed to find the placeholder." + ); + + output = ReplaceWithAssert( + bundlerFriendlyImportsRegex, + output, + imports ?? string.Empty, + $"Failed to find the placeholder for bundler friendly imports." + ); } else if (outputFileExtension == ".js") { @@ -73,6 +90,16 @@ public void WriteConfigToFile(BootJsonData config, string outputPath, string? ou File.WriteAllText(outputPath, output); } + private string ReplaceWithAssert(Regex regex, string content, string replacement, string errorMessage) + { + string existingContent = content; + content = regex.Replace(content, e => replacement); + if (existingContent.Equals(content)) + Log.LogError(errorMessage); + + return content; + } + public void ComputeResourcesHash(BootJsonData bootConfig) { ResourcesData resources = (ResourcesData)bootConfig.resources; @@ -179,8 +206,10 @@ public int GetDebugLevel(bool hasPdb) return intValue; } - public void TransformResourcesToAssets(BootJsonData config) + public string TransformResourcesToAssets(BootJsonData config, bool bundlerFriendly = false) { + List imports = []; + ResourcesData resources = (ResourcesData)config.resources; var assets = new AssetsData(); @@ -190,10 +219,22 @@ public void TransformResourcesToAssets(BootJsonData config) assets.jsModuleWorker = MapJsAssets(resources.jsModuleWorker); assets.jsModuleDiagnostics = MapJsAssets(resources.jsModuleDiagnostics); - assets.wasmNative = resources.wasmNative?.Select(a => new WasmAsset() + assets.wasmNative = resources.wasmNative?.Select(a => { - name = a.Key, - integrity = a.Value + var asset = new WasmAsset() + { + name = a.Key, + integrity = a.Value + }; + + if (bundlerFriendly) + { + string escaped = EscapeName(a.Key); + imports.Add($"import {escaped} from \"./{a.Key}\";"); + asset.resolvedUrl = EncodeJavascriptVariableInJson(escaped); + } + + return asset; }).ToList(); assets.wasmSymbols = resources.wasmSymbols?.Select(a => new SymbolsAsset() { @@ -211,7 +252,7 @@ public void TransformResourcesToAssets(BootJsonData config) { assets.satelliteResources = resources.satelliteResources.ToDictionary( kvp => kvp.Key, - kvp => MapGeneralAssets(kvp.Value) + kvp => MapGeneralAssets(kvp.Value, variableNamePrefix: kvp.Key) ); } @@ -224,16 +265,45 @@ public void TransformResourcesToAssets(BootJsonData config) assets.coreVfs = MapVfsAssets(resources.coreVfs); assets.vfs = MapVfsAssets(resources.vfs); - List? MapGeneralAssets(Dictionary? assets) => assets?.Select(a => new GeneralAsset() + string EscapeName(string name) => Utils.FixupSymbolName(name); + string EncodeJavascriptVariableInJson(string name) => $"$#[{name}]#$"; + + List? MapGeneralAssets(Dictionary? assets, string? variableNamePrefix = null) => assets?.Select(a => { - virtualPath = resources.fingerprinting?[a.Key] ?? a.Key, - name = a.Key, - integrity = a.Value + Debugger.Launch(); + + var asset = new GeneralAsset() + { + virtualPath = resources.fingerprinting?[a.Key] ?? a.Key, + name = a.Key, + integrity = a.Value + }; + + if (bundlerFriendly) + { + string escaped = EscapeName(string.Concat(variableNamePrefix, a.Key)); + imports.Add($"import {escaped} from \"./{a.Key}\";"); + asset.resolvedUrl = EncodeJavascriptVariableInJson(escaped); + } + + return asset; }).ToList(); - List? MapJsAssets(Dictionary? assets) => assets?.Select(a => new JsAsset() + List? MapJsAssets(Dictionary? assets) => assets?.Select(a => { - name = a.Key + var asset = new JsAsset() + { + name = a.Key + }; + + if (bundlerFriendly) + { + string escaped = EscapeName(a.Key); + imports.Add($"import * as {escaped} from \"./{a.Key}\";"); + asset.moduleExports = EncodeJavascriptVariableInJson(escaped); + } + + return asset; }).ToList(); List? MapVfsAssets(Dictionary>? assets) => assets?.Select(a => new VfsAsset() @@ -244,6 +314,8 @@ public void TransformResourcesToAssets(BootJsonData config) }).ToList(); config.resources = assets; + + return string.Join(Environment.NewLine, imports); } } } 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 178febdd19594e..2e590fbb38acb8 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -343,6 +343,7 @@ public class AssetsData public class JsAsset { public string name { get; set; } + public string moduleExports { get; set; } } [DataContract] @@ -356,6 +357,7 @@ public class WasmAsset { public string name { get; set; } public string integrity { get; set; } + public string resolvedUrl { get; set; } } [DataContract] @@ -364,6 +366,7 @@ public class GeneralAsset public string virtualPath { get; set; } public string name { get; set; } public string integrity { get; set; } + public string resolvedUrl { get; set; } } [DataContract] 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 4688a1ac1b49a9..e84c0278208c6a 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -92,6 +92,8 @@ public class GenerateWasmBootJson : Task public string MergeWith { get; set; } + public bool BundlerFriendly { get; set; } + public override bool Execute() { var entryAssemblyName = AssemblyName.GetAssemblyName(AssemblyPath).Name; @@ -452,10 +454,11 @@ private void WriteBootConfig(string entryAssemblyName) helper.ComputeResourcesHash(result); + string? imports = null; if (IsTargeting100OrLater()) - helper.TransformResourcesToAssets(result); + imports = helper.TransformResourcesToAssets(result, BundlerFriendly); - helper.WriteConfigToFile(result, OutputPath, mergeWith: MergeWith); + helper.WriteConfigToFile(result, OutputPath, mergeWith: MergeWith, imports: imports); void AddResourceToList(ITaskItem resource, ResourceHashesByNameDictionary resourceList, string resourceKey) { From 94f3c006cd41a09f695d3584cb472e1f03bb6b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 11 Jun 2025 13:02:42 +0200 Subject: [PATCH 18/32] fix --- .../BootJsonBuilderHelper.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 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 4b63bfcd3d0801..e1a0d5ca9b5914 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs @@ -252,11 +252,11 @@ public string TransformResourcesToAssets(BootJsonData config, bool bundlerFriend { assets.satelliteResources = resources.satelliteResources.ToDictionary( kvp => kvp.Key, - kvp => MapGeneralAssets(kvp.Value, variableNamePrefix: kvp.Key) + kvp => MapGeneralAssets(kvp.Value, variableNamePrefix: kvp.Key, subFolder: kvp.Key) ); } - assets.libraryInitializers = MapJsAssets(resources.libraryInitializers); + assets.libraryInitializers = MapJsAssets(resources.libraryInitializers, subFolder: ".."); assets.modulesAfterConfigLoaded = MapJsAssets(resources.modulesAfterConfigLoaded); assets.modulesAfterRuntimeReady = MapJsAssets(resources.modulesAfterRuntimeReady); @@ -268,10 +268,8 @@ public string TransformResourcesToAssets(BootJsonData config, bool bundlerFriend string EscapeName(string name) => Utils.FixupSymbolName(name); string EncodeJavascriptVariableInJson(string name) => $"$#[{name}]#$"; - List? MapGeneralAssets(Dictionary? assets, string? variableNamePrefix = null) => assets?.Select(a => + List? MapGeneralAssets(Dictionary? assets, string? variableNamePrefix = null, string? subFolder = null) => assets?.Select(a => { - Debugger.Launch(); - var asset = new GeneralAsset() { virtualPath = resources.fingerprinting?[a.Key] ?? a.Key, @@ -281,15 +279,16 @@ public string TransformResourcesToAssets(BootJsonData config, bool bundlerFriend if (bundlerFriendly) { - string escaped = EscapeName(string.Concat(variableNamePrefix, a.Key)); - imports.Add($"import {escaped} from \"./{a.Key}\";"); + string escaped = EscapeName(string.Concat(subFolder, a.Key)); + string subFolderWithSeparator = subFolder != null ? $"{subFolder}/" : string.Empty; + imports.Add($"import {escaped} from \"./{subFolderWithSeparator}{a.Key}\";"); asset.resolvedUrl = EncodeJavascriptVariableInJson(escaped); } return asset; }).ToList(); - List? MapJsAssets(Dictionary? assets) => assets?.Select(a => + List? MapJsAssets(Dictionary? assets, string? variableNamePrefix = null, string? subFolder = null) => assets?.Select(a => { var asset = new JsAsset() { @@ -298,8 +297,9 @@ public string TransformResourcesToAssets(BootJsonData config, bool bundlerFriend if (bundlerFriendly) { - string escaped = EscapeName(a.Key); - imports.Add($"import * as {escaped} from \"./{a.Key}\";"); + string escaped = EscapeName(string.Concat(subFolder, a.Key)); + string subFolderWithSeparator = subFolder != null ? $"{subFolder}/" : string.Empty; + imports.Add($"import * as {escaped} from \"./{subFolderWithSeparator}{a.Key}\";"); asset.moduleExports = EncodeJavascriptVariableInJson(escaped); } From ed122a7454d79cac075ce7c994bcf45472087d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 11 Jun 2025 14:24:41 +0200 Subject: [PATCH 19/32] Library initializers & moduleExports --- .../runtime/loader/libraryInitializers.ts | 18 ++++++----- .../BootJsonBuilderHelper.cs | 30 ++++++++++++++++--- .../BootJsonData.cs | 1 + 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/mono/browser/runtime/loader/libraryInitializers.ts b/src/mono/browser/runtime/loader/libraryInitializers.ts index fa3e6d65b11867..d319d125b0b387 100644 --- a/src/mono/browser/runtime/loader/libraryInitializers.ts +++ b/src/mono/browser/runtime/loader/libraryInitializers.ts @@ -12,17 +12,19 @@ export async function importLibraryInitializers (libraryInitializers: JsAsset[] return; } - await Promise.all((libraryInitializers ?? []).map(i => importInitializer(i.name!))); + await Promise.all((libraryInitializers ?? []).map(i => importInitializer(i!))); - async function importInitializer (path: string): Promise { + async function importInitializer (asset: JsAsset): Promise { try { - const adjustedPath = appendUniqueQuery(loaderHelpers.locateFile(path), "js-module-library-initializer"); - mono_log_debug(() => `Attempting to import '${adjustedPath}' for ${path}`); - const initializer = await import(/*! webpackIgnore: true */ adjustedPath); - - loaderHelpers.libraryInitializers!.push({ scriptName: path, exports: initializer }); + const path = asset.name!; + if (!asset.moduleExports) { + const adjustedPath = appendUniqueQuery(loaderHelpers.locateFile(path), "js-module-library-initializer"); + mono_log_debug(() => `Attempting to import '${adjustedPath}' for ${asset}`); + asset.moduleExports = await import(/*! webpackIgnore: true */ adjustedPath); + } + loaderHelpers.libraryInitializers!.push({ scriptName: path, exports: asset.moduleExports }); } catch (error) { - mono_log_warn(`Failed to import library initializer '${path}': ${error}`); + mono_log_warn(`Failed to import library initializer '${asset}': ${error}`); } } } 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 e1a0d5ca9b5914..93ead55e48c113 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs @@ -265,6 +265,16 @@ public string TransformResourcesToAssets(BootJsonData config, bool bundlerFriend assets.coreVfs = MapVfsAssets(resources.coreVfs); assets.vfs = MapVfsAssets(resources.vfs); + if (bundlerFriendly && config.appsettings != null) + { + config.appsettings = config.appsettings.Select(a => + { + string escaped = EscapeName(a); + imports.Add($"import {escaped} from \"./{a}\";"); + return EncodeJavascriptVariableInJson(escaped); + }).ToList(); + } + string EscapeName(string name) => Utils.FixupSymbolName(name); string EncodeJavascriptVariableInJson(string name) => $"$#[{name}]#$"; @@ -306,11 +316,23 @@ public string TransformResourcesToAssets(BootJsonData config, bool bundlerFriend return asset; }).ToList(); - List? MapVfsAssets(Dictionary>? assets) => assets?.Select(a => new VfsAsset() + List? MapVfsAssets(Dictionary>? assets) => assets?.Select(a => { - virtualPath = a.Key, - name = a.Value.Keys.First(), - integrity = a.Value.Values.First() + var asset = new VfsAsset() + { + virtualPath = a.Key, + name = a.Value.Keys.First(), + integrity = a.Value.Values.First() + }; + + if (bundlerFriendly) + { + string escaped = EscapeName(string.Concat(asset.name)); + imports.Add($"import * as {escaped} from \"./{asset.name}\";"); + asset.resolvedUrl = EncodeJavascriptVariableInJson(escaped); + } + + return asset; }).ToList(); config.resources = assets; 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 2e590fbb38acb8..9da2cbad9071f6 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -375,6 +375,7 @@ public class VfsAsset public string virtualPath { get; set; } public string name { get; set; } public string integrity { get; set; } + public string resolvedUrl { get; set; } } public enum GlobalizationMode : int From 4e223dacfb8c42b13aa43a57343c5f3160df1ed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 11 Jun 2025 14:59:11 +0200 Subject: [PATCH 20/32] WBT --- src/libraries/sendtohelix-browser.targets | 1 + src/libraries/sendtohelix-wasm.targets | 5 +- src/libraries/sendtohelix.proj | 18 +++++- src/libraries/sendtohelixhelp.proj | 3 +- .../Common/BuildEnvironment.cs | 2 +- .../Common/EnvironmentVariables.cs | 1 + .../LibraryInitializerTests.cs | 2 +- .../Templates/WasmTemplateTestsBase.cs | 63 +++++++++++++++---- .../Wasm.Build.Tests/Wasm.Build.Tests.csproj | 6 +- .../data/RunScriptTemplate.cmd | 5 ++ .../data/RunScriptTemplate.sh | 6 ++ .../JavascriptBundlers/package.json | 26 ++++++++ .../JavascriptBundlers/rollup.config.mjs | 42 +++++++++++++ 13 files changed, 158 insertions(+), 22 deletions(-) create mode 100644 src/mono/wasm/testassets/JavascriptBundlers/package.json create mode 100644 src/mono/wasm/testassets/JavascriptBundlers/rollup.config.mjs diff --git a/src/libraries/sendtohelix-browser.targets b/src/libraries/sendtohelix-browser.targets index c1e5d9d5d20c4f..eba7ec0e95f2c7 100644 --- a/src/libraries/sendtohelix-browser.targets +++ b/src/libraries/sendtohelix-browser.targets @@ -138,6 +138,7 @@ <_XUnitTraitArg Condition="'$(TestUsingWorkloads)' == 'true'">-notrait category=no-workload <_XUnitTraitArg Condition="'$(TestUsingWorkloads)' != 'true'">-trait category=no-workload <_XUnitTraitArg Condition="'$(WasmFingerprintAssets)' == 'false'">$(_XUnitTraitArg) -trait category=no-fingerprinting + <_XUnitTraitArg Condition="'$(WasmBundlerFriendlyBootConfig)' == 'true'">$(_XUnitTraitArg) -trait category=bundler-friendly diff --git a/src/libraries/sendtohelix-wasm.targets b/src/libraries/sendtohelix-wasm.targets index f92b17d4fc9723..3a8b618f54f3d9 100644 --- a/src/libraries/sendtohelix-wasm.targets +++ b/src/libraries/sendtohelix-wasm.targets @@ -14,6 +14,7 @@ NoWorkload- NoWebcil- NoFingerprint- + JavascriptBundler- $(WorkItemPrefix)ST- $(WorkItemPrefix)MT- @@ -50,7 +51,7 @@ - + $(_BuildWasmAppsPayloadArchive) set "HELIX_XUNIT_ARGS=-class %(Identity)" export "HELIX_XUNIT_ARGS=-class %(Identity)" @@ -58,7 +59,7 @@ $(_workItemTimeout) - + $(_BuildWasmAppsPayloadArchive) $(HelixCommand) $(_workItemTimeout) diff --git a/src/libraries/sendtohelix.proj b/src/libraries/sendtohelix.proj index a916ea3e836cc3..26b8fe5d0f99b5 100644 --- a/src/libraries/sendtohelix.proj +++ b/src/libraries/sendtohelix.proj @@ -88,6 +88,7 @@ <_TestUsingWorkloadsValues Include="true;false" /> <_TestUsingWebcilValues Include="true;false" Condition="'$(TargetOS)' == 'browser'" /> <_TestUsingFingerprintingValues Include="true;false" Condition="'$(TargetOS)' == 'browser'" /> + <_TestUsingJavascriptBundlerValues Include="true;false" Condition="'$(TargetOS)' == 'browser'" /> <_TestUsingCrossProductValuesTemp Include="@(_TestUsingWorkloadsValues)"> @@ -96,15 +97,26 @@ <_TestUsingCrossProductValuesTemp2 Include="@(_TestUsingCrossProductValuesTemp)"> %(_TestUsingWebcilValues.Identity) - <_TestUsingCrossProductValues Include="@(_TestUsingCrossProductValuesTemp2)"> + <_TestUsingCrossProductValuesTemp3 Include="@(_TestUsingCrossProductValuesTemp2)"> %(_TestUsingFingerprintingValues.Identity) + + <_TestUsingCrossProductValues Include="@(_TestUsingCrossProductValuesTemp3)"> + %(_TestUsingJavascriptBundlerValues.Identity) - + <_TestUsingCrossProductValues Remove="@(_TestUsingCrossProductValues)" Condition="'%(_TestUsingCrossProductValues.Workloads)' == 'false' and '%(_TestUsingCrossProductValues.Fingerprinting)' == 'false'" /> + <_TestUsingCrossProductValues Remove="@(_TestUsingCrossProductValues)" Condition="'%(_TestUsingCrossProductValues.Workloads)' == 'false' and '%(_TestUsingCrossProductValues.BundlerFriendly)' == 'false'" /> <_BuildWasmAppsProjectsToBuild Include="$(PerScenarioProjectFile)"> - $(_PropertiesToPass);Scenario=BuildWasmApps;TestArchiveRuntimeFile=$(TestArchiveRuntimeFile);TestUsingWorkloads=%(_TestUsingCrossProductValues.Workloads);WasmEnableWebcil=%(_TestUsingCrossProductValues.Webcil);WasmFingerprintAssets=%(_TestUsingCrossProductValues.Fingerprinting) + + $(_PropertiesToPass);Scenario=BuildWasmApps; + TestArchiveRuntimeFile=$(TestArchiveRuntimeFile); + TestUsingWorkloads=%(_TestUsingCrossProductValues.Workloads); + WasmEnableWebcil=%(_TestUsingCrossProductValues.Webcil); + WasmFingerprintAssets=%(_TestUsingCrossProductValues.Fingerprinting); + WasmBundlerFriendlyBootConfig=%(_TestUsingCrossProductValues.BundlerFriendly) + %(_BuildWasmAppsProjectsToBuild.AdditionalProperties);NeedsToBuildWasmAppsOnHelix=$(NeedsToBuildWasmAppsOnHelix) diff --git a/src/libraries/sendtohelixhelp.proj b/src/libraries/sendtohelixhelp.proj index b01079ab6f07d1..87d3c1d371f808 100644 --- a/src/libraries/sendtohelixhelp.proj +++ b/src/libraries/sendtohelixhelp.proj @@ -159,6 +159,7 @@ + @@ -348,7 +349,7 @@ + Text="Scenario: $(Scenario), TestUsingWorkloads: $(TestUsingWorkloads), WasmEnableWebcil: $(WasmEnableWebcil), WasmFingerprintAssets: $(WasmFingerprintAssets), WasmBundlerFriendlyBootConfig: $(WasmBundlerFriendlyBootConfig)" /> diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs b/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs index e21004e5aab04e..e1c3ed4cc023ca 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs @@ -32,7 +32,7 @@ public class BuildEnvironment public static readonly string RelativeTestAssetsPath = @"..\testassets\"; public static readonly string TestAssetsPath = Path.Combine(AppContext.BaseDirectory, "testassets"); public static readonly string TestDataPath = Path.Combine(AppContext.BaseDirectory, "data"); - public static readonly string TmpPath = Path.Combine(AppContext.BaseDirectory, "wbt artifacts"); + public static readonly string TmpPath = Path.Combine(AppContext.BaseDirectory, EnvironmentVariables.UseJavascriptBundler ? "wbtartifacts" : "wbt artifacts"); public static readonly string DefaultRuntimeIdentifier = #if TARGET_WASI diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs b/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs index d928fc1e1b4069..c1a932aed44b92 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs @@ -23,6 +23,7 @@ internal static class EnvironmentVariables internal static readonly bool ShowBuildOutput = IsRunningOnCI || Environment.GetEnvironmentVariable("SHOW_BUILD_OUTPUT") is not null; internal static readonly bool UseWebcil = Environment.GetEnvironmentVariable("USE_WEBCIL_FOR_TESTS") is "true"; internal static readonly bool UseFingerprinting = Environment.GetEnvironmentVariable("USE_FINGERPRINTING_FOR_TESTS") is "true"; + internal static readonly bool UseJavascriptBundler = Environment.GetEnvironmentVariable("USE_JAVASCRIPT_BUNDLER_FOR_TESTS") is "true"; internal static readonly string? SdkDirName = Environment.GetEnvironmentVariable("SDK_DIR_NAME"); internal static readonly string? WasiSdkPath = Environment.GetEnvironmentVariable("WASI_SDK_PATH"); internal static readonly bool WorkloadsTestPreviousVersions = Environment.GetEnvironmentVariable("WORKLOADS_TEST_PREVIOUS_VERSIONS") is "true"; diff --git a/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs b/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs index b60bf5c858f31b..059222bbda1095 100644 --- a/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs @@ -24,7 +24,7 @@ public LibraryInitializerTests(ITestOutputHelper output, SharedBuildPerTestClass { } - [Fact] + [Fact, TestCategory("bundler-friendly")] public async Task LoadLibraryInitializer() { Configuration config = Configuration.Debug; diff --git a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs index 5288275ce6a643..4799ac1acd5bb9 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Configuration; using System.IO; using System.Linq; using System.Text; @@ -104,6 +105,17 @@ protected ProjectInfo CopyTestAsset( _projectDir = Path.Combine(_projectDir, asset.RunnableProjectSubPath); } string projectFilePath = Path.Combine(_projectDir, $"{asset.Name}.csproj"); + + if (EnvironmentVariables.UseJavascriptBundler) + { + extraProperties += + """ + true + false + false + """; + } + UpdateProjectFile(projectFilePath, runAnalyzers, extraProperties, extraItems, insertAtEnd); return new ProjectInfo(asset.Name, projectFilePath, logPath, nugetDir); } @@ -186,6 +198,23 @@ public virtual (string projectDir, string buildOutput) BuildProject( return (_projectDir, res.Output); } + if (EnvironmentVariables.UseJavascriptBundler) + { + string publicWwwrootDir = Path.GetFullPath(Path.Combine(GetBinFrameworkDir(configuration, forPublish: true), "..")); + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "JavascriptBundlers", "package.json"), Path.Combine(publicWwwrootDir, "package.json")); + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "JavascriptBundlers", "rollup.config.mjs"), Path.Combine(publicWwwrootDir, "rollup.config.mjs")); + + // TODO MF: Discover npm + ToolCommand npmCommand = new ToolCommand(@"C:\Program Files\nodejs\npm.cmd", _testOutput).WithWorkingDirectory(publicWwwrootDir); + npmCommand.Execute("install").EnsureSuccessful(); + npmCommand.Execute("run build").EnsureSuccessful(); + + string publicDir = Path.Combine(publicWwwrootDir, "public"); + File.Copy(Path.Combine(publicWwwrootDir, "index.html"), Path.Combine(publicDir, "index.html")); + + buildOptions = buildOptions with { AssertAppBundle = false }; + } + if (buildOptions.AssertAppBundle) { _provider.AssertWasmSdkBundle(configuration, buildOptions, IsUsingWorkloads, isNativeBuild, wasmFingerprintDotnetJs, res.Output); @@ -270,20 +299,28 @@ public virtual async Task RunForBuildWithDotnetRun(RunOptions runOpti public virtual async Task RunForPublishWithWebServer(RunOptions runOptions) => await BrowserRun(runOptions with { Host = RunHost.WebServer }); - private async Task BrowserRun(RunOptions runOptions) => runOptions.Host switch + private async Task BrowserRun(RunOptions runOptions) { - RunHost.DotnetRun => - await BrowserRunTest($"run -c {runOptions.Configuration} --no-build", _projectDir, runOptions), - - RunHost.WebServer => - await BrowserRunTest($"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files", - string.IsNullOrEmpty(runOptions.CustomBundleDir) ? - Path.GetFullPath(Path.Combine(GetBinFrameworkDir(runOptions.Configuration, forPublish: true), "..")) : - runOptions.CustomBundleDir, - runOptions), - - _ => throw new NotImplementedException(runOptions.Host.ToString()) - }; + if (EnvironmentVariables.UseJavascriptBundler) + { + runOptions = runOptions with { CustomBundleDir = Path.GetFullPath(Path.Combine(GetBinFrameworkDir(runOptions.Configuration, forPublish: true), "..", "public")) }; + } + + return runOptions.Host switch + { + RunHost.DotnetRun => + await BrowserRunTest($"run -c {runOptions.Configuration} --no-build", _projectDir, runOptions), + + RunHost.WebServer => + await BrowserRunTest($"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files", + string.IsNullOrEmpty(runOptions.CustomBundleDir) ? + Path.GetFullPath(Path.Combine(GetBinFrameworkDir(runOptions.Configuration, forPublish: true), "..")) : + runOptions.CustomBundleDir, + runOptions), + + _ => throw new NotImplementedException(runOptions.Host.ToString()) + }; + } private async Task BrowserRunTest(string runArgs, string workingDirectory, diff --git a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj index a366e7d0782c9b..d97bb3162c3c64 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj +++ b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj @@ -96,6 +96,7 @@ <_XUnitTraitArg Condition="'$(TestUsingWorkloads)' == 'true'">-notrait category=no-workload <_XUnitTraitArg Condition="'$(TestUsingWorkloads)' != 'true'">-trait category=no-workload <_XUnitTraitArg Condition="'$(WasmFingerprintAssets)' == 'false'">-trait category=no-fingerprinting + <_XUnitTraitArg Condition="'$(WasmBundlerFriendlyBootConfig)' == 'true'">-trait category=bundler-friendly @@ -117,6 +118,9 @@ + + + @@ -158,7 +162,7 @@ dotnet exec xunit.console.dll $(AssemblyName).dll -xml %24XHARNESS_OUT/testResults.xml - dotnet.exe exec xunit.console.dll $(AssemblyName).dll -xml %XHARNESS_OUT%\testResults.xml + D:\Development\dotnet\runtime\artifacts\bin\dotnet-latest\dotnet.exe exec xunit.console.dll $(AssemblyName).dll -xml %XHARNESS_OUT%\testResults.xml $(RunScriptCommand) %24HELIX_XUNIT_ARGS $(RunScriptCommand) %HELIX_XUNIT_ARGS% diff --git a/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.cmd b/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.cmd index 6cbf9b15d78ccd..70ceca68c6c9c5 100644 --- a/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.cmd +++ b/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.cmd @@ -61,6 +61,11 @@ if [%WASM_FINGERPRINT_ASSETS%] == [false] ( ) else ( set USE_FINGERPRINTING_FOR_TESTS=true ) +if [%WASM_BUNDLER_FRIENDLY_BOOT_CONFIG%] == [false] ( + set USE_JAVASCRIPT_BUNDLER_FOR_TESTS=false +) else ( + set USE_JAVASCRIPT_BUNDLER_FOR_TESTS=true +) if [%HELIX_CORRELATION_PAYLOAD%] NEQ [] ( robocopy /mt /np /nfl /NDL /nc /e %BASE_DIR%\%SDK_DIR_NAME% %EXECUTION_DIR%\%SDK_DIR_NAME% diff --git a/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.sh b/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.sh index cfbcda4a64307c..ce9cf5c4cfb882 100644 --- a/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.sh +++ b/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.sh @@ -45,6 +45,12 @@ function set_env_vars() export USE_FINGERPRINTING_FOR_TESTS=true fi + if [ "x$WASM_BUNDLER_FRIENDLY_BOOT_CONFIG" = "xfalse" ]; then + export USE_JAVASCRIPT_BUNDLER_FOR_TESTS=false + else + export USE_JAVASCRIPT_BUNDLER_FOR_TESTS=true + fi + local _SDK_DIR= if [[ -n "$HELIX_WORKITEM_UPLOAD_ROOT" ]]; then cp -r $BASE_DIR/$SDK_DIR_NAME $EXECUTION_DIR diff --git a/src/mono/wasm/testassets/JavascriptBundlers/package.json b/src/mono/wasm/testassets/JavascriptBundlers/package.json new file mode 100644 index 00000000000000..13e1957c33262e --- /dev/null +++ b/src/mono/wasm/testassets/JavascriptBundlers/package.json @@ -0,0 +1,26 @@ +{ + "name": "project1", + "version": "1.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "build": "rollup -c" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + }, + "devDependencies": { + "@babel/core": "^7.21.3", + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^28.0.2", + "@rollup/plugin-image": "^3.0.3", + "@rollup/plugin-node-resolve": "^16.0.0", + "@rollup/plugin-replace": "^6.0.2", + "@rollup/plugin-url": "^8.0.2", + "rollup": "^4.34.6", + "rollup-plugin-import-file": "^1.0.1" + } +} diff --git a/src/mono/wasm/testassets/JavascriptBundlers/rollup.config.mjs b/src/mono/wasm/testassets/JavascriptBundlers/rollup.config.mjs new file mode 100644 index 00000000000000..7ae75e991bb5ca --- /dev/null +++ b/src/mono/wasm/testassets/JavascriptBundlers/rollup.config.mjs @@ -0,0 +1,42 @@ +import nodeResolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import babel from '@rollup/plugin-babel'; +import replace from '@rollup/plugin-replace'; +import files from 'rollup-plugin-import-file'; + +export default { + input: 'main.js', + output: { + file: 'public/main.js', + format: 'esm' + }, + plugins: [ + files({ + output: 'public', + extensions: /\.(wasm|dat)$/, + hash: true, + }), + files({ + output: 'public', + extensions: /\.(json)$/, + }), + nodeResolve({ + extensions: ['.js'] + }), + babel({ + babelHelpers: 'bundled', + extensions: ['.js'], + generatorOpts: { + // Increase the size limit from 500KB to 10MB + compact: true, + retainLines: true, + maxSize: 10000000 + } + }), + commonjs(), + replace({ + preventAssignment: false, + 'process.env.NODE_ENV': '"production"' + }), + ] +} \ No newline at end of file From 4a499fb10b06076368c224ee142867389926b427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 11 Jun 2025 16:55:24 +0200 Subject: [PATCH 21/32] fix --- src/libraries/sendtohelix.proj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/sendtohelix.proj b/src/libraries/sendtohelix.proj index 26b8fe5d0f99b5..8f0ca1dd035c18 100644 --- a/src/libraries/sendtohelix.proj +++ b/src/libraries/sendtohelix.proj @@ -101,7 +101,7 @@ %(_TestUsingFingerprintingValues.Identity) <_TestUsingCrossProductValues Include="@(_TestUsingCrossProductValuesTemp3)"> - %(_TestUsingJavascriptBundlerValues.Identity) + %(_TestUsingJavascriptBundlerValues.Identity) From 0e373f6ca1c5d7851b204d34a401beab73c7fd90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 12 Jun 2025 09:31:57 +0200 Subject: [PATCH 22/32] fix --- src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj index d97bb3162c3c64..de2528595ea2e6 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj +++ b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj @@ -162,7 +162,7 @@ dotnet exec xunit.console.dll $(AssemblyName).dll -xml %24XHARNESS_OUT/testResults.xml - D:\Development\dotnet\runtime\artifacts\bin\dotnet-latest\dotnet.exe exec xunit.console.dll $(AssemblyName).dll -xml %XHARNESS_OUT%\testResults.xml + dotnet.exe exec xunit.console.dll $(AssemblyName).dll -xml %XHARNESS_OUT%\testResults.xml $(RunScriptCommand) %24HELIX_XUNIT_ARGS $(RunScriptCommand) %HELIX_XUNIT_ARGS% From b0c65785c89614d49e4849df560dd9e8c5b38104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 12 Jun 2025 09:40:33 +0200 Subject: [PATCH 23/32] Improve WBT --- src/libraries/sendtohelix.proj | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/libraries/sendtohelix.proj b/src/libraries/sendtohelix.proj index 8f0ca1dd035c18..c121c3c009d51b 100644 --- a/src/libraries/sendtohelix.proj +++ b/src/libraries/sendtohelix.proj @@ -93,20 +93,25 @@ <_TestUsingCrossProductValuesTemp Include="@(_TestUsingWorkloadsValues)"> %(_TestUsingWorkloadsValues.Identity) + false <_TestUsingCrossProductValuesTemp2 Include="@(_TestUsingCrossProductValuesTemp)"> %(_TestUsingWebcilValues.Identity) - <_TestUsingCrossProductValuesTemp3 Include="@(_TestUsingCrossProductValuesTemp2)"> + <_TestUsingCrossProductValues Include="@(_TestUsingCrossProductValuesTemp2)"> %(_TestUsingFingerprintingValues.Identity) - - <_TestUsingCrossProductValues Include="@(_TestUsingCrossProductValuesTemp3)"> - %(_TestUsingJavascriptBundlerValues.Identity) - + <_TestUsingCrossProductValues Remove="@(_TestUsingCrossProductValues)" Condition="'%(_TestUsingCrossProductValues.Workloads)' == 'false' and '%(_TestUsingCrossProductValues.Fingerprinting)' == 'false'" /> - <_TestUsingCrossProductValues Remove="@(_TestUsingCrossProductValues)" Condition="'%(_TestUsingCrossProductValues.Workloads)' == 'false' and '%(_TestUsingCrossProductValues.BundlerFriendly)' == 'false'" /> + + + <_TestUsingCrossProductValues Include="JavaScriptBundlerFriendly"> + true + true + true + true + <_BuildWasmAppsProjectsToBuild Include="$(PerScenarioProjectFile)"> From 4e400986a6ab43c1cf05cf442d7141a344751109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 12 Jun 2025 11:34:50 +0200 Subject: [PATCH 24/32] Fix WBT --- src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.cmd | 6 +++--- src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.sh | 6 +++--- .../BootJsonData.cs | 3 +++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.cmd b/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.cmd index 70ceca68c6c9c5..608b2c6c1ea37e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.cmd +++ b/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.cmd @@ -61,10 +61,10 @@ if [%WASM_FINGERPRINT_ASSETS%] == [false] ( ) else ( set USE_FINGERPRINTING_FOR_TESTS=true ) -if [%WASM_BUNDLER_FRIENDLY_BOOT_CONFIG%] == [false] ( - set USE_JAVASCRIPT_BUNDLER_FOR_TESTS=false -) else ( +if [%WASM_BUNDLER_FRIENDLY_BOOT_CONFIG%] == [true] ( set USE_JAVASCRIPT_BUNDLER_FOR_TESTS=true +) else ( + set USE_JAVASCRIPT_BUNDLER_FOR_TESTS=false ) if [%HELIX_CORRELATION_PAYLOAD%] NEQ [] ( diff --git a/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.sh b/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.sh index ce9cf5c4cfb882..cee1769b006639 100644 --- a/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.sh +++ b/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.sh @@ -45,10 +45,10 @@ function set_env_vars() export USE_FINGERPRINTING_FOR_TESTS=true fi - if [ "x$WASM_BUNDLER_FRIENDLY_BOOT_CONFIG" = "xfalse" ]; then - export USE_JAVASCRIPT_BUNDLER_FOR_TESTS=false - else + if [ "x$WASM_BUNDLER_FRIENDLY_BOOT_CONFIG" = "xtrue" ]; then export USE_JAVASCRIPT_BUNDLER_FOR_TESTS=true + else + export USE_JAVASCRIPT_BUNDLER_FOR_TESTS=false fi local _SDK_DIR= 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 9da2cbad9071f6..a9723d884d0438 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -28,6 +28,9 @@ public class BootJsonData public string applicationEnvironment { get; set; } /// + /// For .NET < 10, this contains . + /// For .NET >= 10, this contains . + /// --- /// 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, /// and any PDBs to be loaded. From a429ecc1c8476352c396bd02ff7d6b80b8b089e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 12 Jun 2025 11:46:23 +0200 Subject: [PATCH 25/32] CI npm? --- .../wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs index 4799ac1acd5bb9..9d8a45f31cd321 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs @@ -204,8 +204,8 @@ public virtual (string projectDir, string buildOutput) BuildProject( File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "JavascriptBundlers", "package.json"), Path.Combine(publicWwwrootDir, "package.json")); File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "JavascriptBundlers", "rollup.config.mjs"), Path.Combine(publicWwwrootDir, "rollup.config.mjs")); - // TODO MF: Discover npm - ToolCommand npmCommand = new ToolCommand(@"C:\Program Files\nodejs\npm.cmd", _testOutput).WithWorkingDirectory(publicWwwrootDir); + string npmPath = s_isWindows ? @"C:\Program Files\nodejs\npm.cmd" : "/usb/bin/node/bin/npm"; + ToolCommand npmCommand = new ToolCommand(npmPath, _testOutput).WithWorkingDirectory(publicWwwrootDir); npmCommand.Execute("install").EnsureSuccessful(); npmCommand.Execute("run build").EnsureSuccessful(); From b76dc79362fd73d2071deb3dac6dc8c1f177d551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 12 Jun 2025 14:29:56 +0200 Subject: [PATCH 26/32] Fix npm linux --- .../wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs index 9d8a45f31cd321..38391d8595df66 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs @@ -204,7 +204,7 @@ public virtual (string projectDir, string buildOutput) BuildProject( File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "JavascriptBundlers", "package.json"), Path.Combine(publicWwwrootDir, "package.json")); File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "JavascriptBundlers", "rollup.config.mjs"), Path.Combine(publicWwwrootDir, "rollup.config.mjs")); - string npmPath = s_isWindows ? @"C:\Program Files\nodejs\npm.cmd" : "/usb/bin/node/bin/npm"; + string npmPath = s_isWindows ? @"C:\Program Files\nodejs\npm.cmd" : "/bin/npm"; ToolCommand npmCommand = new ToolCommand(npmPath, _testOutput).WithWorkingDirectory(publicWwwrootDir); npmCommand.Execute("install").EnsureSuccessful(); npmCommand.Execute("run build").EnsureSuccessful(); From fafb3285b9b8d1b3f34b91444a8d9044d41c756b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 12 Jun 2025 20:59:41 +0200 Subject: [PATCH 27/32] More bundler friendly tests --- .../wasm/Wasm.Build.Tests/AppSettingsTests.cs | 18 +++++++++++++----- .../LibraryInitializerTests.cs | 2 +- .../wasm/Wasm.Build.Tests/ModuleConfigTests.cs | 2 +- .../Wasm.Build.Tests/SatelliteLoadingTests.cs | 2 +- .../Templates/WasmTemplateTestsBase.cs | 2 +- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/AppSettingsTests.cs b/src/mono/wasm/Wasm.Build.Tests/AppSettingsTests.cs index 3c2da858c29124..11b360a89eb032 100644 --- a/src/mono/wasm/Wasm.Build.Tests/AppSettingsTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/AppSettingsTests.cs @@ -24,23 +24,31 @@ public AppSettingsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture public static IEnumerable LoadAppSettingsBasedOnApplicationEnvironmentData() { // Defaults - yield return new object?[] { false, null, null, "Development" }; + if (!EnvironmentVariables.UseJavascriptBundler) + yield return new object?[] { false, null, null, "Development" }; + yield return new object?[] { true, null, null, "Production" }; // Override defaults from MSBuild - yield return new object?[] { false, "Production", null, "Production" }; + if (!EnvironmentVariables.UseJavascriptBundler) + yield return new object?[] { false, "Production", null, "Production" }; + yield return new object?[] { true, "Development", null, "Development" }; // Override defaults from JavaScript - yield return new object?[] { false, null, "Production", "Production" }; + if (!EnvironmentVariables.UseJavascriptBundler) + yield return new object?[] { false, null, "Production", "Production" }; + yield return new object?[] { true, null, "Development", "Development" }; // Override MSBuild from JavaScript - yield return new object?[] { false, "FromMSBuild", "Production", "Production" }; + if (!EnvironmentVariables.UseJavascriptBundler) + yield return new object?[] { false, "FromMSBuild", "Production", "Production" }; + yield return new object?[] { true, "FromMSBuild", "Development", "Development" }; } - [Theory] + [Theory, TestCategory("bundler-friendly")] [MemberData(nameof(LoadAppSettingsBasedOnApplicationEnvironmentData))] public async Task LoadAppSettingsBasedOnApplicationEnvironment(bool publish, string? msBuildApplicationEnvironment, string? queryApplicationEnvironment, string expectedApplicationEnvironment) { diff --git a/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs b/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs index 059222bbda1095..e281f86d48d8f4 100644 --- a/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs @@ -40,7 +40,7 @@ public async Task LoadLibraryInitializer() [GeneratedRegex("MONO_WASM: Failed to invoke 'onRuntimeConfigLoaded' on library initializer '../WasmBasicTestApp.[a-z0-9]+.lib.module.js': Error: Error thrown from library initializer")] private static partial Regex AbortStartupOnErrorRegex(); - [Fact] + [Fact, TestCategory("bundler-friendly")] public async Task AbortStartupOnError() { Configuration config = Configuration.Debug; diff --git a/src/mono/wasm/Wasm.Build.Tests/ModuleConfigTests.cs b/src/mono/wasm/Wasm.Build.Tests/ModuleConfigTests.cs index 55548caee65e23..b50bedbe2b1aca 100644 --- a/src/mono/wasm/Wasm.Build.Tests/ModuleConfigTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ModuleConfigTests.cs @@ -57,7 +57,7 @@ public async Task DownloadProgressFinishes(bool failAssemblyDownload) ); } - [Fact] + [Fact, TestCategory("bundler-friendly")] public async Task OutErrOverrideWorks() { Configuration config = Configuration.Debug; diff --git a/src/mono/wasm/Wasm.Build.Tests/SatelliteLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/SatelliteLoadingTests.cs index 98d40e6e9eefc9..90d27ad7287c76 100644 --- a/src/mono/wasm/Wasm.Build.Tests/SatelliteLoadingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/SatelliteLoadingTests.cs @@ -58,7 +58,7 @@ public async Task LoadSatelliteAssembly(bool loadAllSatelliteResources) ); } - [Fact] + [Fact, TestCategory("bundler-friendly")] public async Task LoadSatelliteAssemblyFromReference() { Configuration config = Configuration.Release; diff --git a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs index 38391d8595df66..4d1b08ac1e1d6b 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs @@ -198,7 +198,7 @@ public virtual (string projectDir, string buildOutput) BuildProject( return (_projectDir, res.Output); } - if (EnvironmentVariables.UseJavascriptBundler) + if (EnvironmentVariables.UseJavascriptBundler && buildOptions.IsPublish) { string publicWwwrootDir = Path.GetFullPath(Path.Combine(GetBinFrameworkDir(configuration, forPublish: true), "..")); File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "JavascriptBundlers", "package.json"), Path.Combine(publicWwwrootDir, "package.json")); From 54c50100415aed1fd0c92780d84717ff86d1c8a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 13 Jun 2025 11:09:36 +0200 Subject: [PATCH 28/32] Docs --- src/mono/wasm/features.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/mono/wasm/features.md b/src/mono/wasm/features.md index 50d52a0e03e6db..be2f730d52c27b 100644 --- a/src/mono/wasm/features.md +++ b/src/mono/wasm/features.md @@ -137,11 +137,13 @@ When you want to call JavaScript functions from C# or managed code from JavaScri * or [the documentation](https://learn.microsoft.com/aspnet/core/client-side/dotnet-interop). ### Embedding dotnet in existing JavaScript applications -To embed the .NET runtime inside of a JavaScript application, you will need to use both the MSBuild toolchain (to build and publish your managed code) and your existing web build toolchain. +The default build output rely on exact file names produced during .NET build. In our testing the dynamic loading of assets provides faster startup and shorter download times. -The output of the MSBuild toolchain - located in the [AppBundle](#Project-folder-structure) folder - must be fed in to your web build toolchain in order to ensure that the runtime and managed binaries are deployed with the rest of your application assets. - -For a sample of using the .NET runtime in a React component, [see here](https://github.com/maraf/dotnet-wasm-react). +JavaScript tools like [webpack](https://github.com/webpack/webpack) or [rollup](https://github.com/rollup/rollup) can be used for further file modifications. +An msbuild property `true` can be used to generate a different JavaScript files that are not runnable +in the browsers, but they can consumed by these JavaScript tools. Some examples: + - Merge all JavaScript files, resolve wasm & other files as files, copying them to the output directory, optionally fingerprinting them, etc. + - Embed all JavaScripts files and wasm & other files as base64 encoded blobs directly into a single file. ## Project folder structure @@ -274,11 +276,6 @@ Browsers do not offer a way to access the contents of their time zone database, This requires that you have the [wasm-tools workload](#wasm-tools-workload) installed. -### Bundling JavaScript and other assets -Many web developers use tools like [webpack](https://github.com/webpack/webpack) or [rollup](https://github.com/rollup/rollup) to bundle many files into one large .js file. When deploying a .NET application to the web, you can safely bundle the `dotnet.js` ES6 module with the rest of your JavaScript application, but the other assets and modules in the `_framework` folder may not be bundled as they are loaded dynamically. - -In our testing the dynamic loading of assets provides faster startup and shorter download times. We would like to [hear from the community](https://github.com/dotnet/runtime/issues/86162) if there are scenarios where you need the ability to bundle the rest of an application. - ## Resources consumed on the target device When you deploy a .NET application to the browser, many necessary components and databases are included: - The .NET runtime, including a garbage collector, interpreter, and JIT compiler From b2ff58d73d8fe88b91f308589fc79d6291505880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 13 Jun 2025 12:44:05 +0200 Subject: [PATCH 29/32] Update src/mono/wasm/features.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander Köplinger --- src/mono/wasm/features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/features.md b/src/mono/wasm/features.md index be2f730d52c27b..e086e4fd5cf58e 100644 --- a/src/mono/wasm/features.md +++ b/src/mono/wasm/features.md @@ -137,7 +137,7 @@ When you want to call JavaScript functions from C# or managed code from JavaScri * or [the documentation](https://learn.microsoft.com/aspnet/core/client-side/dotnet-interop). ### Embedding dotnet in existing JavaScript applications -The default build output rely on exact file names produced during .NET build. In our testing the dynamic loading of assets provides faster startup and shorter download times. +The default build output relies on exact file names produced during .NET build. In our testing the dynamic loading of assets provides faster startup and shorter download times. JavaScript tools like [webpack](https://github.com/webpack/webpack) or [rollup](https://github.com/rollup/rollup) can be used for further file modifications. An msbuild property `true` can be used to generate a different JavaScript files that are not runnable From 8a81558f568e6832a6819f2bc7f084b106017236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 13 Jun 2025 12:44:14 +0200 Subject: [PATCH 30/32] Update src/mono/wasm/features.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander Köplinger --- src/mono/wasm/features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/features.md b/src/mono/wasm/features.md index e086e4fd5cf58e..ff97173a8dbbe9 100644 --- a/src/mono/wasm/features.md +++ b/src/mono/wasm/features.md @@ -140,7 +140,7 @@ When you want to call JavaScript functions from C# or managed code from JavaScri The default build output relies on exact file names produced during .NET build. In our testing the dynamic loading of assets provides faster startup and shorter download times. JavaScript tools like [webpack](https://github.com/webpack/webpack) or [rollup](https://github.com/rollup/rollup) can be used for further file modifications. -An msbuild property `true` can be used to generate a different JavaScript files that are not runnable +An msbuild property `true` can be used to generate different JavaScript files that are not runnable in the browsers, but they can consumed by these JavaScript tools. Some examples: - Merge all JavaScript files, resolve wasm & other files as files, copying them to the output directory, optionally fingerprinting them, etc. - Embed all JavaScripts files and wasm & other files as base64 encoded blobs directly into a single file. From 3f059f04722117b0ca53d8c2315daadb486d419b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 13 Jun 2025 12:44:30 +0200 Subject: [PATCH 31/32] Update src/mono/wasm/features.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander Köplinger --- src/mono/wasm/features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/features.md b/src/mono/wasm/features.md index ff97173a8dbbe9..97f1e8213d988f 100644 --- a/src/mono/wasm/features.md +++ b/src/mono/wasm/features.md @@ -141,7 +141,7 @@ The default build output relies on exact file names produced during .NET build. JavaScript tools like [webpack](https://github.com/webpack/webpack) or [rollup](https://github.com/rollup/rollup) can be used for further file modifications. An msbuild property `true` can be used to generate different JavaScript files that are not runnable -in the browsers, but they can consumed by these JavaScript tools. Some examples: +in the browsers, but they can be consumed by these JavaScript tools. Some examples: - Merge all JavaScript files, resolve wasm & other files as files, copying them to the output directory, optionally fingerprinting them, etc. - Embed all JavaScripts files and wasm & other files as base64 encoded blobs directly into a single file. From 8b44df96cc8d121161fbc5cdf5dbe2c08eb3ed3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 13 Jun 2025 13:39:21 +0200 Subject: [PATCH 32/32] Don't change libraryInitializers as those are imported only by blazor --- .../BootJsonBuilderHelper.cs | 2 +- .../Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs | 3 +-- 2 files changed, 2 insertions(+), 3 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 93ead55e48c113..f21dc4ed8fc8ac 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs @@ -256,7 +256,7 @@ public string TransformResourcesToAssets(BootJsonData config, bool bundlerFriend ); } - assets.libraryInitializers = MapJsAssets(resources.libraryInitializers, subFolder: ".."); + assets.libraryInitializers = resources.libraryInitializers; assets.modulesAfterConfigLoaded = MapJsAssets(resources.modulesAfterConfigLoaded); assets.modulesAfterRuntimeReady = MapJsAssets(resources.modulesAfterRuntimeReady); 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 a9723d884d0438..92d618f88d84f1 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -317,10 +317,9 @@ public class AssetsData /// /// JavaScript module initializers that Blazor will be in charge of loading. - /// Used in .NET < 8 /// [DataMember(EmitDefaultValue = false)] - public List libraryInitializers { get; set; } + public ResourceHashesByNameDictionary libraryInitializers { get; set; } [DataMember(EmitDefaultValue = false)] public List modulesAfterConfigLoaded { get; set; }