- Update `WasmAppBuilder` to produce the same boot config schema as `GenerateWasmBootJson`
- Update AppBundle layout to match blazor layout.
- All the runtime files (js, wasm, icu) are deployed to `_framework` folder.
- This behavior can be overriden by setting `<WasmRuntimeAssetsLocation>` property. The empty value is overriden to default `_framework`, but `./` can be used to flatten the output structure.
- WBT test with flat output
- The only remaining difference is `AppBundle` vs `wwwrooot` folder name.
- `JSHost.ImportAsync` now requires the same relative paths as in blazor (`./module.js` => `../module.js`)
Contributes to https://github.com/dotnet/runtime/issues/70762
---------
Co-authored-by: Ankit Jain <radical@gmail.com>
{
if (_module == null)
{
- _module = await JSHost.ImportAsync("Timers", "./timers.mjs");
+ _module = await JSHost.ImportAsync("Timers", "../timers.mjs");
}
}
[Fact]
public async Task MultipleImportAsync()
{
- var first = await JSHost.ImportAsync("JavaScriptTestHelper", "./JavaScriptTestHelper.mjs");
- var second = await JSHost.ImportAsync("JavaScriptTestHelper", "./JavaScriptTestHelper.mjs");
+ var first = await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs");
+ var second = await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs");
Assert.NotNull(first);
Assert.NotNull(second);
Assert.Equal("object", first.GetTypeOfProperty("instance"));
public async Task CancelableImportAsync()
{
var cts = new CancellationTokenSource();
- var exTask = Assert.ThrowsAsync<JSException>(async () => await JSHost.ImportAsync("JavaScriptTestHelper", "./JavaScriptTestHelper.mjs", cts.Token));
+ var exTask = Assert.ThrowsAsync<JSException>(async () => await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs", cts.Token));
cts.Cancel();
var actualEx2 = await exTask;
Assert.Equal("OperationCanceledException", actualEx2.Message);
- var actualEx = await Assert.ThrowsAsync<JSException>(async () => await JSHost.ImportAsync("JavaScriptTestHelper", "./JavaScriptTestHelper.mjs", new CancellationToken(true)));
+ var actualEx = await Assert.ThrowsAsync<JSException>(async () => await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs", new CancellationToken(true)));
Assert.Equal("OperationCanceledException", actualEx.Message);
}
yield return new object[] { new object[] { "JSData" } }; // special cased, so we call createData in the test itself
yield return new object[] { new object[] { new byte[] { }, new int[] { }, new double[] { }, new string[] { }, new object[] { } } };
yield return new object[] { new object[] { new byte[] { 1, 2, 3 }, new int[] { 1, 2, 3 }, new double[] { 1, 2, 3 }, new string[] { "a", "b", "c" }, new object[] { } } };
- yield return new object[] { new object[] { new object[] { new byte[] { 1, 2, 3 }, new int[] { 1, 2, 3 }, new double[] { 1, 2, 3 }, new string[] { "a", "b", "c" } , new object(), new SomethingRef(), new SomethingStruct(), new Exception("test") } } };
+ yield return new object[] { new object[] { new object[] { new byte[] { 1, 2, 3 }, new int[] { 1, 2, 3 }, new double[] { 1, 2, 3 }, new string[] { "a", "b", "c" }, new object(), new SomethingRef(), new SomethingStruct(), new Exception("test") } } };
yield return new object[] { new object[] { } };
yield return new object[] { null };
}
Assert.Equal(expected, actual);
if (expected != null) for (int i = 0; i < expected.Length; i++)
- {
- var actualI = JavaScriptTestHelper.store_ObjectArray(expected, i);
- Assert.Equal(expected[i], actualI);
- }
+ {
+ var actualI = JavaScriptTestHelper.store_ObjectArray(expected, i);
+ Assert.Equal(expected[i], actualI);
+ }
}
public static IEnumerable<object[]> MarshalObjectArrayCasesToDouble()
yield return new object[] { new object[] { (ushort)0 } };
yield return new object[] { new object[] { new SomethingStruct[] { } } };
yield return new object[] { new object[] { new SomethingRef[] { }, } };
- yield return new object[] { new object[] { new ArraySegment<byte>(new byte[] { 11 }) , } };
+ yield return new object[] { new object[] { new ArraySegment<byte>(new byte[] { 11 }), } };
}
delegate void dummyDelegate();
static void dummyDelegateA()
[JSExport]
public static void Optimized1V(int a1)
{
- optimizedReached+= a1;
+ optimizedReached += a1;
}
[JSImport("invoke1V", "JavaScriptTestHelper")]
public static partial void invoke1V(int a1);
[JSExport]
public static int Optimized2R(int a1, int a2)
{
- optimizedReached += a1+ a2;
- return a1 + a2 +1;
+ optimizedReached += a1 + a2;
+ return a1 + a2 + 1;
}
[JSImport("invoke2R", "JavaScriptTestHelper")]
public static partial int invoke2R(int a1, int a2);
if (_module == null)
{
// Log("JavaScriptTestHelper.mjs importing");
- _module = await JSHost.ImportAsync("JavaScriptTestHelper", "./JavaScriptTestHelper.mjs");
+ _module = await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs");
await Setup();
// Log("JavaScriptTestHelper.mjs imported");
}
public partial class JavaScriptTestHelper
{
[System.Runtime.InteropServices.JavaScript.JSExport]
- public static string EchoString(string message)
+ public static string EchoString(string message)
{
return message + "11";
}
{
[System.Runtime.InteropServices.JavaScript.JSExport]
public static string EchoString(string message) => message + "12";
-
+
private partial class DoubleNestedClass
{
[System.Runtime.InteropServices.JavaScript.JSExport]
public partial class SecondRuntimeTest
{
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowserDomSupportedOrNodeJS))]
- public static async Task RunSecondRuntimeAndTestStaticState()
+ public static async Task RunSecondRuntimeAndTestStaticState()
{
- await JSHost.ImportAsync("SecondRuntimeTest", "./SecondRuntimeTest.js");
+ await JSHost.ImportAsync("SecondRuntimeTest", "../SecondRuntimeTest.js");
Interop.State = 42;
var state2 = await Interop.RunSecondRuntimeAndTestStaticState();
export async function runSecondRuntimeAndTestStaticState() {
- const { dotnet: dotnet2 } = await import('./dotnet.js?instance=2');
+ const { dotnet: dotnet2 } = await import('./_framework/dotnet.js?instance=2');
const runtime2 = await dotnet2
.withStartupMemoryCache(false)
.withConfig({
-import { dotnet } from './dotnet.js'
+import { dotnet } from './_framework/dotnet.js'
try {
const { getAssemblyExports } = await dotnet
const exports = await getAssemblyExports("WasmDelta.dll");
const update = exports.Sample.Test.Update;
const testMeaning = exports.Sample.Test.TestMeaning;
-
+
const outElement = document.getElementById("out");
document.getElementById("update").addEventListener("click", function () {
update();
<_ServeHeaders>$(_ServeHeaders) -h "Content-Security-Policy: default-src 'self' 'wasm-unsafe-eval'"</_ServeHeaders>
<!-- enable reporting to profiler in browser dev tools -->
<WasmProfilers>browser;</WasmProfilers>
+
+ <!-- Put "framework" (dotnet.js, dlls, etc) files directly into the AppBundle -->
+ <WasmRuntimeAssetsLocation>./</WasmRuntimeAssetsLocation>
</PropertyGroup>
<ItemGroup>
<WasmExtraFilesToDeploy Include="main.js" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'wasm-unsafe-eval';" />
<script type='module' src="./main.js"></script>
- <script type='module' src="./dotnet.js"></script>
+ <script type='module' src="./_framework/dotnet.js"></script>
<link rel="preload" href="./_framework/blazor.boot.json" as="fetch" crossorigin="anonymous">
<link rel="prefetch" href="./dotnet.native.js" as="fetch" crossorigin="anonymous">
<link rel="prefetch" href="./dotnet.runtime.js" as="fetch" crossorigin="anonymous">
// here we show how emscripten could be further configured
// It is preferred to use specific 'with***' methods instead in all other cases.
.withModuleConfig({
- configSrc: "./_framework/blazor.boot.json",
+ configSrc: "./blazor.boot.json",
onConfigLoaded: (config) => {
// This is called during emscripten `dotnet.wasm` instantiation, after we fetched config.
console.log('user code Module.onConfigLoaded');
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="module" src="./frame-main.js"></script>
- <script type='module' src="./dotnet.js"></script>
+ <script type='module' src="./_framework/dotnet.js"></script>
<link rel="preload" href="./_framework/blazor.boot.json" as="fetch" crossorigin="anonymous">
<link rel="prefetch" href="./dotnet.native.js" as="fetch" crossorigin="anonymous">
<link rel="prefetch" href="./dotnet.runtime.js" as="fetch" crossorigin="anonymous">
"use strict";
-import { dotnet, exit } from './dotnet.js'
+import { dotnet, exit } from './_framework/dotnet.js'
class FrameApp {
async init({ getAssemblyExports }) {
"use strict";
-import { dotnet, exit } from './dotnet.js'
+import { dotnet, exit } from './_framework/dotnet.js'
let runBenchmark;
let setTasks;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import { dotnet, exit } from "./dotnet.js";
+import { dotnet, exit } from "./_framework/dotnet.js";
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import { dotnet, exit } from './dotnet.js'
+import { dotnet, exit } from './_framework/dotnet.js'
function saveProfile(aotProfileData) {
if (!aotProfileData) {
[JSImport("globalThis.console.log")]
private static partial void GlobalThisConsoleLog(string text);
- const string fetchhelper = "./fetchelper.js";
+ const string fetchhelper = "../fetchhelper.js";
[JSImport("responseText", fetchhelper)]
private static partial Task<string> FetchHelperResponseText(JSObject response, int delayMs);
internal static Task TestHelloWebWorker()
{
Console.WriteLine($"smoke: TestHelloWebWorker 1 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
- Task t= WebWorker.RunAsync(() =>
+ Task t = WebWorker.RunAsync(() =>
{
Console.WriteLine($"smoke: TestHelloWebWorker 2 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
GlobalThisConsoleLog($"smoke: TestHelloWebWorker 3 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
return t.ContinueWith(Gogo);
}
- private static void Gogo(Task t){
+ private static void Gogo(Task t)
+ {
Console.WriteLine($"smoke: TestHelloWebWorker 6 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
}
internal static void StartTimerFromWorker()
{
Console.WriteLine("smoke: StartTimerFromWorker 1 utc {0}", DateTime.UtcNow.ToUniversalTime());
- WebWorker.RunAsync(async () =>
+ WebWorker.RunAsync(async () =>
{
- while (!_timerDone)
+ while (!_timerDone)
{
await Task.Delay(1 * 1000);
Console.WriteLine("smoke: StartTimerFromWorker 2 utc {0}", DateTime.UtcNow.ToUniversalTime());
internal static void StartAllocatorFromWorker()
{
Console.WriteLine("smoke: StartAllocatorFromWorker 1 utc {0}", DateTime.UtcNow.ToUniversalTime());
- WebWorker.RunAsync(async () =>
+ WebWorker.RunAsync(async () =>
{
- while (!_timerDone)
+ while (!_timerDone)
{
await Task.Delay(1 * 100);
var x = new List<int[]>();
for (int i = 0; i < 1000; i++)
{
- var v=new int[1000];
+ var v = new int[1000];
v[i] = i;
x.Add(v);
}
- Console.WriteLine("smoke: StartAllocatorFromWorker 2 utc {0} {1} {2}", DateTime.UtcNow.ToUniversalTime(),x[1][1], GC.GetTotalAllocatedBytes());
+ Console.WriteLine("smoke: StartAllocatorFromWorker 2 utc {0} {1} {2}", DateTime.UtcNow.ToUniversalTime(), x[1][1], GC.GetTotalAllocatedBytes());
}
Console.WriteLine("smoke: StartAllocatorFromWorker done utc {0}", DateTime.UtcNow.ToUniversalTime());
});
public static async Task TestCallSetTimeoutOnWorker()
{
await WebWorker.RunAsync(() => TimeOutThenComplete());
- Console.WriteLine ($"XYZ: Main Thread caught task tid:{Thread.CurrentThread.ManagedThreadId}");
+ Console.WriteLine($"XYZ: Main Thread caught task tid:{Thread.CurrentThread.ManagedThreadId}");
}
private static async Task<string> HttpClientGet(string name, string url)
var ctx = SynchronizationContext.Current;
Console.WriteLine($"smoke: FetchBackground 2 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
- var x=JSHost.ImportAsync(fetchhelper, "./fetchhelper.js");
+ var x = JSHost.ImportAsync(fetchhelper, "../fetchhelper.js");
Console.WriteLine($"smoke: FetchBackground 3A ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
// using var import = await x.ConfigureAwait(false);
Console.WriteLine($"smoke: FetchBackground 3B ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
Console.WriteLine($"smoke: FetchBackground 5 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
if (ok)
{
- #if DEBUG
+#if DEBUG
var text = await FetchHelperResponseText(r, 5000);
- #else
+#else
var text = await FetchHelperResponseText(r, 25000);
- #endif
+#endif
Console.WriteLine($"smoke: FetchBackground 6 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
return text;
}
{
Console.WriteLine($"smoke {meaning}: TestTLS 2 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
meaning = 41;
- await JSHost.ImportAsync(fetchhelper, "./fetchhelper.js");
+ await JSHost.ImportAsync(fetchhelper, "../fetchhelper.js");
Console.WriteLine($"smoke {meaning}: TestTLS 3 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
meaning = 43;
Console.WriteLine($"smoke {meaning}: TestTLS 4 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
private static async Task TimeOutThenComplete()
{
var tcs = new TaskCompletionSource();
- Console.WriteLine ($"smoke: Task running tid:{Thread.CurrentThread.ManagedThreadId}");
- GlobalThisSetTimeout(() => {
+ Console.WriteLine($"smoke: Task running tid:{Thread.CurrentThread.ManagedThreadId}");
+ GlobalThisSetTimeout(() =>
+ {
tcs.SetResult();
- Console.WriteLine ($"smoke: Timeout fired tid:{Thread.CurrentThread.ManagedThreadId}");
+ Console.WriteLine($"smoke: Timeout fired tid:{Thread.CurrentThread.ManagedThreadId}");
}, 250);
- Console.WriteLine ($"smoke: Task sleeping tid:{Thread.CurrentThread.ManagedThreadId}");
+ Console.WriteLine($"smoke: Task sleeping tid:{Thread.CurrentThread.ManagedThreadId}");
await tcs.Task;
- Console.WriteLine ($"smoke: Task resumed tid:{Thread.CurrentThread.ManagedThreadId}");
+ Console.WriteLine($"smoke: Task resumed tid:{Thread.CurrentThread.ManagedThreadId}");
}
[JSExport]
public static async Task<int> RunBackgroundThreadCompute()
{
var tcs = new TaskCompletionSource<int>();
- var t = new Thread(() => {
+ var t = new Thread(() =>
+ {
var n = CountingCollatzTest();
tcs.SetResult(n);
});
public static async Task<int> RunBackgroundLongRunningTaskCompute()
{
var factory = new TaskFactory();
- var t = factory.StartNew<int> (() => {
+ var t = factory.StartNew<int>(() =>
+ {
var n = CountingCollatzTest();
return n;
}, TaskCreationOptions.LongRunning);
[JSExport]
public static async Task<int> RunBackgroundTaskRunCompute()
{
- var t1 = Task.Run (() => {
+ var t1 = Task.Run(() =>
+ {
var n = CountingCollatzTest();
return n;
});
- var t2 = Task.Run (() => {
+ var t2 = Task.Run(() =>
+ {
var n = CountingCollatzTest();
return n;
});
- var rs = await Task.WhenAll (new [] { t1, t2 });
+ var rs = await Task.WhenAll(new[] { t1, t2 });
if (rs[0] != rs[1])
- throw new Exception ($"Results from two tasks {rs[0]}, {rs[1]}, differ");
+ throw new Exception($"Results from two tasks {rs[0]}, {rs[1]}, differ");
return rs[0];
}
int bigly = 0;
int hugely = 0;
int maxSteps = 0;
- for (int n = 1; n < maxInput; n++) {
- int steps = CountingCollatz ((long)n, limit);
+ for (int n = 1; n < maxInput; n++)
+ {
+ int steps = CountingCollatz((long)n, limit);
if (steps > maxSteps)
maxSteps = steps;
if (steps > 120)
hugely++;
}
- Console.WriteLine ($"Bigly: {bigly}, Hugely: {hugely}, maxSteps: {maxSteps}");
+ Console.WriteLine($"Bigly: {bigly}, Hugely: {hugely}, maxSteps: {maxSteps}");
if (bigly == 86187 && hugely == 0 && maxSteps == 382)
return 524;
}
- private static int CountingCollatz (long n, int limit)
+ private static int CountingCollatz(long n, int limit)
{
int steps = 0;
- while (n > 1) {
- n = Collatz1 (n);
+ while (n > 1)
+ {
+ n = Collatz1(n);
steps++;
if (steps >= limit)
break;
return steps;
}
- private static long Collatz1 (long n)
+ private static long Collatz1(long n)
{
if (n <= 0)
throw new Exception("Unexpected non-positive input");
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import { dotnet, exit } from './dotnet.js'
+import { dotnet, exit } from './_framework/dotnet.js'
const assemblyName = "Wasm.Browser.Threads.Minimal.Sample.dll";
try {
- const { setModuleImports, getAssemblyExports, runMain } = await dotnet
+ const resolveUrl = (relativeUrl) => (new URL(relativeUrl, window.location.href)).toString()
+
+ const { getAssemblyExports, runMain } = await dotnet
//.withEnvironmentVariable("MONO_LOG_LEVEL", "debug")
//.withDiagnosticTracing(true)
.withConfig({
/* ActiveIssue https://github.com/dotnet/runtime/issues/88057
console.log("smoke: running FetchBackground(blurst.txt)");
- let s = await exports.Sample.Test.FetchBackground("./blurst.txt");
+ let s = await exports.Sample.Test.FetchBackground(resolveUrl("./blurst.txt"));
console.log("smoke: FetchBackground(blurst.txt) done");
if (!s.startsWith("It was the best of times, it was the blurst of times.")) {
const msg = `Unexpected FetchBackground result ${s}`;
}
console.log("smoke: running FetchBackground(missing)");
- s = await exports.Sample.Test.FetchBackground("./missing.txt");
+ s = await exports.Sample.Test.FetchBackground(resolveUrl("./missing.txt"));
console.log("smoke: FetchBackground(missing) done");
if (s !== "not-ok") {
const msg = `Unexpected FetchBackground(missing) result ${s}`;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import { dotnet, exit } from './dotnet.js'
+import { dotnet, exit } from './_framework/dotnet.js'
let progressElement = null;
</ItemGroup>
- <Target Name="CopyRelinkedPackage" AfterTargets="WasmBuildApp" DependsOnTargets="Build" Inputs="$(WasmAppDir)/dotnet.js;
- $(WasmAppDir)/dotnet.runtime.js;
- $(WasmAppDir)/dotnet.native.js;
- $(WasmAppDir)/dotnet.native.wasm;
+ <Target Name="CopyRelinkedPackage" AfterTargets="WasmBuildApp" DependsOnTargets="Build" Inputs="$(WasmAppDir)/_framework/dotnet.js;
+ $(WasmAppDir)/_framework/dotnet.runtime.js;
+ $(WasmAppDir)/_framework/dotnet.native.js;
+ $(WasmAppDir)/_framework/dotnet.native.wasm;
$(MicrosoftNetCoreAppRuntimePackNativeDir)/dotnet.d.ts;
$(MicrosoftNetCoreAppRuntimePackNativeDir)/dotnet-legacy.d.ts;
$(MicrosoftNetCoreAppRuntimePackNativeDir)/package.json;" Outputs="bin/dotnet-runtime/.npm-stamp">
<ItemGroup>
- <NpmPackageFiles Include="$(WasmAppDir)/dotnet.js"/>
- <NpmPackageFiles Include="$(WasmAppDir)/dotnet.runtime.js"/>
- <NpmPackageFiles Include="$(WasmAppDir)/dotnet.native.js"/>
- <NpmPackageFiles Include="$(WasmAppDir)/dotnet.native.wasm"/>
+ <NpmPackageFiles Include="$(WasmAppDir)/_framework/dotnet.js"/>
+ <NpmPackageFiles Include="$(WasmAppDir)/_framework/dotnet.runtime.js"/>
+ <NpmPackageFiles Include="$(WasmAppDir)/_framework/dotnet.native.js"/>
+ <NpmPackageFiles Include="$(WasmAppDir)/_framework/dotnet.native.wasm"/>
<NpmPackageFiles Include="$(MicrosoftNetCoreAppRuntimePackNativeDir)/dotnet.d.ts"/>
<NpmPackageFiles Include="$(MicrosoftNetCoreAppRuntimePackNativeDir)/dotnet-legacy.d.ts"/>
<NpmPackageFiles Include="$(MicrosoftNetCoreAppRuntimePackNativeDir)/package.json"/>
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import { dotnet, exit } from './dotnet.js'
+import { dotnet, exit } from './_framework/dotnet.js'
function displayMeaning(meaning) {
document.getElementById("out").innerHTML = `${meaning}`;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import { dotnet } from './dotnet.js'
+import { dotnet } from './_framework/dotnet.js'
await dotnet
.withDiagnosticTracing(false)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import { dotnet } from './dotnet.js'
+import { dotnet } from './_framework/dotnet.js'
await dotnet
.withDiagnosticTracing(false)
<WasmMainJSPath>main.mjs</WasmMainJSPath>
</PropertyGroup>
- <Target Name="CopyRelinkedPackage" AfterTargets="WasmBuildApp" DependsOnTargets="Build" Inputs="$(WasmAppDir)/dotnet.js;
- $(WasmAppDir)/dotnet.runtime.js;
- $(WasmAppDir)/dotnet.native.js;
- $(WasmAppDir)/dotnet.native.wasm;
+ <Target Name="CopyRelinkedPackage" AfterTargets="WasmBuildApp" DependsOnTargets="Build" Inputs="$(WasmAppDir)/_framework/dotnet.js;
+ $(WasmAppDir)/_framework/dotnet.runtime.js;
+ $(WasmAppDir)/_framework/dotnet.native.js;
+ $(WasmAppDir)/_framework/dotnet.native.wasm;
$(MicrosoftNetCoreAppRuntimePackNativeDir)/dotnet.d.ts;
$(MicrosoftNetCoreAppRuntimePackNativeDir)/dotnet-legacy.d.ts;
$(MicrosoftNetCoreAppRuntimePackNativeDir)/package.json;" Outputs="bin/dotnet-runtime/.npm-stamp">
<ItemGroup>
- <NpmPackageFiles Include="$(WasmAppDir)/dotnet.js"/>
- <NpmPackageFiles Include="$(WasmAppDir)/dotnet.runtime.js"/>
- <NpmPackageFiles Include="$(WasmAppDir)/dotnet.native.js"/>
- <NpmPackageFiles Include="$(WasmAppDir)/dotnet.native.wasm"/>
+ <NpmPackageFiles Include="$(WasmAppDir)/_framework/dotnet.js"/>
+ <NpmPackageFiles Include="$(WasmAppDir)/_framework/dotnet.runtime.js"/>
+ <NpmPackageFiles Include="$(WasmAppDir)/_framework/dotnet.native.js"/>
+ <NpmPackageFiles Include="$(WasmAppDir)/_framework/dotnet.native.wasm"/>
<NpmPackageFiles Include="$(MicrosoftNetCoreAppRuntimePackNativeDir)/dotnet.d.ts"/>
<NpmPackageFiles Include="$(MicrosoftNetCoreAppRuntimePackNativeDir)/dotnet-legacy.d.ts"/>
<NpmPackageFiles Include="$(MicrosoftNetCoreAppRuntimePackNativeDir)/package.json"/>
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import { dotnet } from './dotnet.js'
+import { dotnet } from './_framework/dotnet.js'
function renderCanvas(rgbaView) {
const canvas = document.getElementById("out");
options.InitProject?.Invoke();
File.WriteAllText(Path.Combine(_projectDir, $"{buildArgs.ProjectName}.csproj"), buildArgs.ProjectFileContents);
- File.Copy(Path.Combine(AppContext.BaseDirectory,
- options.TargetFramework == "net8.0" ? "test-main.js" : "data/test-main-7.0.js"),
- Path.Combine(_projectDir, "test-main.js"));
+ File.Copy(
+ Path.Combine(
+ AppContext.BaseDirectory,
+ string.IsNullOrEmpty(options.TargetFramework) || options.TargetFramework == "net8.0"
+ ? "test-main.js"
+ : "data/test-main-7.0.js"
+ ),
+ Path.Combine(_projectDir, "test-main.js")
+ );
File.WriteAllText(Path.Combine(_projectDir!, "index.html"), @"<html><body><script type=""module"" src=""test-main.js""></script></body></html>");
}
var filesToExist = new List<string>()
{
mainJS,
- "dotnet.native.wasm",
+ "_framework/dotnet.native.wasm",
"_framework/blazor.boot.json",
- "dotnet.js",
- "dotnet.native.js",
- "dotnet.runtime.js"
+ "_framework/dotnet.js",
+ "_framework/dotnet.native.js",
+ "_framework/dotnet.runtime.js"
};
if (isBrowserProject)
AssertFilesExist(bundleDir, new[] { "run-v8.sh" }, expectToExist: hasV8Script);
AssertIcuAssets();
- string managedDir = Path.Combine(bundleDir, "managed");
+ string managedDir = Path.Combine(bundleDir, "_framework");
string bundledMainAppAssembly =
useWebcil ? $"{projectName}{WebcilInWasmExtension}" : $"{projectName}.dll";
AssertFilesExist(managedDir, new[] { bundledMainAppAssembly });
case GlobalizationMode.PredefinedIcu:
if (string.IsNullOrEmpty(predefinedIcudt))
throw new ArgumentException("WasmBuildTest is invalid, value for predefinedIcudt is required when GlobalizationMode=PredefinedIcu.");
- AssertFilesExist(bundleDir, new[] { predefinedIcudt }, expectToExist: true);
+ AssertFilesExist(bundleDir, new[] { Path.Combine("_framework", predefinedIcudt) }, expectToExist: true);
// predefined ICU name can be identical with the icu files from runtime pack
switch (predefinedIcudt)
{
expectNOCJK = true;
break;
}
- AssertFilesExist(bundleDir, new[] { "icudt.dat" }, expectToExist: expectFULL);
- AssertFilesExist(bundleDir, new[] { "icudt_EFIGS.dat" }, expectToExist: expectEFIGS);
- AssertFilesExist(bundleDir, new[] { "icudt_CJK.dat" }, expectToExist: expectCJK);
- AssertFilesExist(bundleDir, new[] { "icudt_no_CJK.dat" }, expectToExist: expectNOCJK);
- AssertFilesExist(bundleDir, new[] { "icudt_hybrid.dat" }, expectToExist: expectHYBRID);
+
+ var frameworkDir = Path.Combine(bundleDir, "_framework");
+ AssertFilesExist(frameworkDir, new[] { "icudt.dat" }, expectToExist: expectFULL);
+ AssertFilesExist(frameworkDir, new[] { "icudt_EFIGS.dat" }, expectToExist: expectEFIGS);
+ AssertFilesExist(frameworkDir, new[] { "icudt_CJK.dat" }, expectToExist: expectCJK);
+ AssertFilesExist(frameworkDir, new[] { "icudt_no_CJK.dat" }, expectToExist: expectNOCJK);
+ AssertFilesExist(frameworkDir, new[] { "icudt_hybrid.dat" }, expectToExist: expectHYBRID);
}
}
protected static void AssertDotNetWasmJs(string bundleDir, bool fromRuntimePack, string targetFramework)
{
AssertFile(Path.Combine(s_buildEnv.GetRuntimeNativeDir(targetFramework), "dotnet.native.wasm"),
- Path.Combine(bundleDir, "dotnet.native.wasm"),
+ Path.Combine(bundleDir, "_framework/dotnet.native.wasm"),
"Expected dotnet.native.wasm to be same as the runtime pack",
same: fromRuntimePack);
AssertFile(Path.Combine(s_buildEnv.GetRuntimeNativeDir(targetFramework), "dotnet.native.js"),
- Path.Combine(bundleDir, "dotnet.native.js"),
+ Path.Combine(bundleDir, "_framework/dotnet.native.js"),
"Expected dotnet.native.js to be same as the runtime pack",
same: fromRuntimePack);
}
protected static void AssertDotNetJsSymbols(string bundleDir, bool fromRuntimePack, string targetFramework)
=> AssertFile(Path.Combine(s_buildEnv.GetRuntimeNativeDir(targetFramework), "dotnet.native.js.symbols"),
- Path.Combine(bundleDir, "dotnet.native.js.symbols"),
+ Path.Combine(bundleDir, "_framework/dotnet.native.js.symbols"),
same: fromRuntimePack);
protected static void AssertFilesDontExist(string dir, string[] filenames, string? label = null)
public class V8HostRunner : IHostRunner
{
- private string GetXharnessArgs(string jsRelativePath) => $"--js-file={jsRelativePath} --engine=V8 -v trace --engine-arg=--experimental-wasm-simd";
+ private string GetXharnessArgs(string jsRelativePath) => $"--js-file={jsRelativePath} --engine=V8 -v trace --engine-arg=--experimental-wasm-simd --engine-arg=--module";
public string GetTestCommand() => "wasm test";
public string GetXharnessArgsWindowsOS(XHarnessArgsOptions options) => GetXharnessArgs(options.jsRelativePath);
Path.Combine(paths.ObjWasmDir, "pinvoke-table.h"),
Path.Combine(paths.ObjWasmDir, "driver-gen.c"),
- Path.Combine(paths.BundleDir, "dotnet.native.wasm"),
- Path.Combine(paths.BundleDir, "dotnet.native.js"),
+ Path.Combine(paths.BundleDir, "_framework", "dotnet.native.wasm"),
+ Path.Combine(paths.BundleDir, "_framework", "dotnet.native.js"),
};
if (buildArgs.AOT)
dict[Path.GetFileName(file)] = (file, unchanged);
// those files do not change on re-link
- dict["dotnet.js"]=(Path.Combine(paths.BundleDir, "dotnet.js"), true);
- dict["dotnet.runtime.js"]=(Path.Combine(paths.BundleDir, "dotnet.runtime.js"), true);
+ dict["dotnet.js"]=(Path.Combine(paths.BundleDir, "_framework", "dotnet.js"), true);
+ dict["dotnet.runtime.js"]=(Path.Combine(paths.BundleDir, "_framework", "dotnet.runtime.js"), true);
return dict;
}
File.WriteAllText(path, text);
}
- private void UpdateBrowserMainJs(string targetFramework)
+ private const string DefaultRuntimeAssetsRelativePath = "./_framework/";
+
+ private void UpdateBrowserMainJs(string targetFramework, string runtimeAssetsRelativePath = DefaultRuntimeAssetsRelativePath)
{
string mainJsPath = Path.Combine(_projectDir!, "main.js");
string mainJsContent = File.ReadAllText(mainJsPath);
targetFramework == "net8.0"
? ".withConsoleForwarding().withElementOnExit().withExitCodeLogging().withExitOnUnhandledError().create()"
: ".withConsoleForwarding().withElementOnExit().withExitCodeLogging().create()");
+
+ mainJsContent = mainJsContent.Replace("from './_framework/dotnet.js'", $"from '{runtimeAssetsRelativePath}dotnet.js'");
+
File.WriteAllText(mainJsPath, mainJsContent);
}
}
[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
- [InlineData("", BuildTestBase.DefaultTargetFramework)]
+ [InlineData("", BuildTestBase.DefaultTargetFramework, DefaultRuntimeAssetsRelativePath)]
+ [InlineData("", BuildTestBase.DefaultTargetFramework, "./")]
// [ActiveIssue("https://github.com/dotnet/runtime/issues/79313")]
// [InlineData("-f net7.0", "net7.0")]
- [InlineData("-f net8.0", "net8.0")]
- public async Task BrowserBuildAndRun(string extraNewArgs, string targetFramework)
+ [InlineData("-f net8.0", "net8.0", DefaultRuntimeAssetsRelativePath)]
+ [InlineData("-f net8.0", "net8.0", "./")]
+ public async Task BrowserBuildAndRun(string extraNewArgs, string targetFramework, string runtimeAssetsRelativePath)
{
string config = "Debug";
string id = $"browser_{config}_{Path.GetRandomFileName()}";
CreateWasmTemplateProject(id, "wasmbrowser", extraNewArgs);
- UpdateBrowserMainJs(targetFramework);
+ UpdateBrowserMainJs(targetFramework, runtimeAssetsRelativePath);
new DotNetCommand(s_buildEnv, _testOutput)
.WithWorkingDirectory(_projectDir!)
- .Execute($"build -c {config} -bl:{Path.Combine(s_buildEnv.LogRootPath, $"{id}.binlog")}")
+ .Execute($"build -c {config} -bl:{Path.Combine(s_buildEnv.LogRootPath, $"{id}.binlog")} {(runtimeAssetsRelativePath != DefaultRuntimeAssetsRelativePath ? "-p:WasmRuntimeAssetsLocation=" + runtimeAssetsRelativePath : "")}")
.EnsureSuccessful();
using var runCommand = new RunCommand(s_buildEnv, _testOutput)
- $(WasmAllowUndefinedSymbols) - Controls whether undefined symbols are allowed or not,
if true, appends 'allow-undefined' and sets 'ERROR_ON_UNDEFINED_SYMBOLS=0' as arguments for wasm-ld,
if false (default), removes 'allow-undefined' and sets 'ERROR_ON_UNDEFINED_SYMBOLS=1'.
+ - $(WasmRuntimeAssetsLocation) - Allows to override a location for build generated files.
+ Defaults to '_framework', if you want to put framework files in the same directory as user files, use './' value.
+ Output structure
+ - AppBundle directly contains user files
+ - AppBundle/_framework contains generated files (dlls, runtime scripts, icu)
+ - AppBundle/_content contains web files from nuget packages (css, js, etc)
Public items:
- @(WasmExtraFilesToDeploy) - Files to copy to $(WasmAppDir).
<!-- by default, package assemblies as webcil -->
<WasmEnableWebcil Condition="'$(WasmEnableWebcil)' == ''">true</WasmEnableWebcil>
+
+ <WasmRuntimeAssetsLocation Condition="'$(WasmRuntimeAssetsLocation)' == ''">_framework</WasmRuntimeAssetsLocation>
</PropertyGroup>
<ItemGroup>
IncludeThreadsWorker="$(_WasmAppIncludeThreadsWorker)"
PThreadPoolSize="$(_WasmPThreadPoolSize)"
UseWebcil="$(WasmEnableWebcil)"
+ WasmIncludeFullIcuData="$(WasmIncludeFullIcuData)"
+ WasmIcuDataFileName="$(WasmIcuDataFileName)"
+ RuntimeAssetsLocation="$(WasmRuntimeAssetsLocation)"
>
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
</WasmAppBuilder>
public async Task DuplicateAssemblyLoadedEventForAssemblyFromBundle(bool load_pdb, int expected_count)
=> await AssemblyLoadedEventTest(
"debugger-test",
- Path.Combine(DebuggerTestAppPath, "managed/debugger-test.dll"),
- load_pdb ? Path.Combine(DebuggerTestAppPath, "managed/debugger-test.pdb") : null,
+ Path.Combine(DebuggerTestAppPath, "_framework/debugger-test.dll"),
+ load_pdb ? Path.Combine(DebuggerTestAppPath, "_framework/debugger-test.pdb") : null,
"/debugger-test.cs",
expected_count
);
"use strict";
-import { dotnet, exit } from './dotnet.js'
+import { dotnet, exit } from './_framework/dotnet.js'
try {
const runtime = await dotnet
readonly icuDataMode: ICUDataMode;
readonly startupMemoryCache: boolean | undefined;
readonly runtimeOptions: string[] | undefined;
+ readonly environmentVariables?: {
+ [name: string]: string;
+ };
+ readonly diagnosticTracing?: boolean;
+ readonly pthreadPoolSize: number;
modifiableAssemblies: string | null;
aspnetCoreBrowserTools: string | null;
}
[extensionName: string]: ResourceList;
};
interface ResourceGroups {
+ readonly hash?: string;
readonly assembly: ResourceList;
readonly lazyAssembly: ResourceList;
readonly pdb?: ResourceList;
readonly libraryInitializers?: ResourceList;
readonly extensions?: BootJsonDataExtension;
readonly runtimeAssets: ExtendedResourceList;
+ readonly vfs?: {
+ [virtualPath: string]: ResourceList;
+ };
}
type ResourceList = {
[name: string]: string;
import type { AssetBehaviours, AssetEntry, LoadingResource, WebAssemblyBootResourceType, WebAssemblyStartOptions } from "../../types";
import type { BootJsonData } from "../../types/blazor";
-import { INTERNAL, loaderHelpers } from "../globals";
+import { ENVIRONMENT_IS_WEB, INTERNAL, loaderHelpers } from "../globals";
import { BootConfigResult } from "./BootConfig";
import { WebAssemblyResourceLoader } from "./WebAssemblyResourceLoader";
import { hasDebuggingEnabled } from "./_Polyfill";
export async function initializeBootConfig(bootConfigResult: BootConfigResult, module: DotnetModuleInternal, startupOptions?: Partial<WebAssemblyStartOptions>) {
INTERNAL.resourceLoader = resourceLoader = await WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, startupOptions ?? {});
mapBootConfigToMonoConfig(loaderHelpers.config, bootConfigResult.applicationEnvironment);
- setupModuleForBlazor(module);
+
+ if (ENVIRONMENT_IS_WEB) {
+ setupModuleForBlazor(module);
+ }
}
let resourcesLoaded = 0;
: (name.startsWith("dotnet.native") && name.endsWith(".js")) ? "js-module-native"
: (name.startsWith("dotnet.runtime") && name.endsWith(".js")) ? "js-module-runtime"
: (name.startsWith("dotnet") && name.endsWith(".js")) ? "js-module-dotnet"
- : name.startsWith("icudt") ? "icu"
- : "other";
+ : (name.startsWith("dotnet.native") && name.endsWith(".symbols")) ? "symbols"
+ : name.startsWith("icudt") ? "icu"
+ : "other";
};
const monoToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType | undefined } = {
};
loaderHelpers.downloadResource = downloadResource; // polyfills were already assigned
- module.disableDotnet6Compatibility = false;
+}
+
+function appendUniqueQuery(attemptUrl: string): string {
+ if (loaderHelpers.assetUniqueQuery) {
+ attemptUrl = attemptUrl + loaderHelpers.assetUniqueQuery;
+ }
+
+ return attemptUrl;
}
export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, applicationEnvironment: string) {
const resources = resourceLoader.bootConfig.resources;
const assets: AssetEntry[] = [];
- const environmentVariables: any = {};
+ const environmentVariables: any = {
+ // From boot config
+ ...(resourceLoader.bootConfig.environmentVariables || {}),
+ // From JavaScript
+ ...(moduleConfig.environmentVariables || {})
+ };
moduleConfig.applicationEnvironment = applicationEnvironment;
+ moduleConfig.remoteSources = (resourceLoader.bootConfig.resources as any).remoteSources;
+ moduleConfig.assetsHash = resourceLoader.bootConfig.resources.hash;
moduleConfig.assets = assets;
moduleConfig.globalizationMode = "icu";
- moduleConfig.environmentVariables = environmentVariables;
- moduleConfig.debugLevel = hasDebuggingEnabled(resourceLoader.bootConfig) ? 1 : 0;
- moduleConfig.maxParallelDownloads = 1000000; // disable throttling parallel downloads
- moduleConfig.enableDownloadRetry = false; // disable retry downloads
+ moduleConfig.debugLevel = hasDebuggingEnabled(resourceLoader.bootConfig) ? resourceLoader.bootConfig.debugLevel : 0;
moduleConfig.mainAssemblyName = resourceLoader.bootConfig.entryAssembly;
+ const anyBootConfig = (resourceLoader.bootConfig as any);
+ for (const key in resourceLoader.bootConfig) {
+ if (Object.prototype.hasOwnProperty.call(anyBootConfig, key)) {
+ if (anyBootConfig[key] === null) {
+ delete anyBootConfig[key];
+ }
+ }
+ }
+
// FIXME this mix of both formats is ugly temporary hack
Object.assign(moduleConfig, {
...resourceLoader.bootConfig,
});
+ moduleConfig.environmentVariables = environmentVariables;
+
if (resourceLoader.bootConfig.startupMemoryCache !== undefined) {
moduleConfig.startupMemoryCache = resourceLoader.bootConfig.startupMemoryCache;
}
for (const name in resources.runtimeAssets) {
const asset = resources.runtimeAssets[name] as AssetEntry;
asset.name = name;
- asset.resolvedUrl = loaderHelpers.locateFile(name);
+ asset.resolvedUrl = appendUniqueQuery(loaderHelpers.locateFile(name));
assets.push(asset);
}
for (const name in resources.assembly) {
const asset: AssetEntry = {
name,
- resolvedUrl: loaderHelpers.locateFile(name),
+ resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name)),
hash: resources.assembly[name],
behavior: "assembly",
};
for (const name in resources.pdb) {
const asset: AssetEntry = {
name,
- resolvedUrl: loaderHelpers.locateFile(name),
+ resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name)),
hash: resources.pdb[name],
behavior: "pdb",
};
assets.push(asset);
}
}
- const applicationCulture = resourceLoader.startOptions.applicationCulture || (navigator.languages && navigator.languages[0]);
- const icuDataResourceName = getICUResourceName(resourceLoader.bootConfig, applicationCulture);
+ const applicationCulture = resourceLoader.startOptions.applicationCulture || ENVIRONMENT_IS_WEB ? (navigator.languages && navigator.languages[0]) : Intl.DateTimeFormat().resolvedOptions().locale;
+ const icuDataResourceName = getICUResourceName(resourceLoader.bootConfig, moduleConfig, applicationCulture);
let hasIcuData = false;
for (const name in resources.runtime) {
const behavior = behaviorByName(name) as any;
+ let loadRemote = false;
if (behavior === "icu") {
if (resourceLoader.bootConfig.icuDataMode === ICUDataMode.Invariant) {
continue;
if (name !== icuDataResourceName) {
continue;
}
+ loadRemote = true;
hasIcuData = true;
} else if (behavior === "js-module-dotnet") {
continue;
continue;
}
- const resolvedUrl = loaderHelpers.locateFile(name);
+ const resolvedUrl = appendUniqueQuery(loaderHelpers.locateFile(name));
const asset: AssetEntry = {
name,
resolvedUrl,
hash: resources.runtime[name],
behavior,
+ loadRemote
};
assets.push(asset);
}
+
+ if (moduleConfig.loadAllSatelliteResources && resources.satelliteResources) {
+ for (const culture in resources.satelliteResources) {
+ for (const name in resources.satelliteResources[culture]) {
+ assets.push({
+ name,
+ culture,
+ behavior: "resource",
+ hash: resources.satelliteResources[culture][name],
+ });
+ }
+ }
+ }
+
for (let i = 0; i < resourceLoader.bootConfig.config.length; i++) {
const config = resourceLoader.bootConfig.config[i];
if (config === "appsettings.json" || config === `appsettings.${applicationEnvironment}.json`) {
assets.push({
name: config,
- resolvedUrl: (document ? document.baseURI : "/") + config,
+ resolvedUrl: appendUniqueQuery((document ? document.baseURI : "/") + config),
behavior: "vfs",
});
}
}
+ for (const virtualPath in resources.vfs) {
+ for (const name in resources.vfs[virtualPath]) {
+ const asset: AssetEntry = {
+ name,
+ resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name)),
+ hash: resources.vfs[virtualPath][name],
+ behavior: "vfs",
+ virtualPath
+ };
+ assets.push(asset);
+ }
+ }
+
if (!hasIcuData) {
moduleConfig.globalizationMode = "invariant";
}
}
}
-function getICUResourceName(bootConfig: BootJsonData, culture: string | undefined): string {
+function getICUResourceName(bootConfig: BootJsonData, moduleConfig: MonoConfigInternal, culture: string | undefined): string {
if (bootConfig.icuDataMode === ICUDataMode.Custom) {
const icuFiles = Object
.keys(bootConfig.resources.runtime)
.filter(n => n.startsWith("icudt") && n.endsWith(".dat"));
if (icuFiles.length === 1) {
+ moduleConfig.globalizationMode = "icu";
const customIcuFile = icuFiles[0];
return customIcuFile;
}
}
- if (bootConfig.icuDataMode === ICUDataMode.Hybrid)
- {
+ if (bootConfig.icuDataMode === ICUDataMode.Hybrid) {
+ moduleConfig.globalizationMode = "hybrid";
const reducedICUResourceName = "icudt_hybrid.dat";
return reducedICUResourceName;
}
if (!culture || bootConfig.icuDataMode === ICUDataMode.All) {
+ moduleConfig.globalizationMode = "icu";
const combinedICUResourceName = "icudt.dat";
return combinedICUResourceName;
}
export function hasDebuggingEnabled(bootConfig: BootJsonData): boolean {
// Copied from blazor MonoDebugger.ts/attachDebuggerHotkey
+ if (!globalThis.navigator) {
+ return false;
+ }
const hasReferencedPdbs = !!bootConfig.resources.pdb;
- const debugBuild = bootConfig.debugBuild;
-
- return (hasReferencedPdbs || debugBuild) && (loaderHelpers.isChromium || loaderHelpers.isFirefox);
+ return (hasReferencedPdbs || bootConfig.debugBuild || bootConfig.debugLevel != 0) && (loaderHelpers.isChromium || loaderHelpers.isFirefox);
}
\ No newline at end of file
const loadedAnyConfig: any = (await configResponse.json()) || {};
if (loadedAnyConfig.resources) {
// If we found boot config schema
+ normalizeConfig();
await initializeBootConfig(BootConfigResult.fromFetchResponse(configResponse, loadedAnyConfig as BootJsonData, loaderHelpers.config.applicationEnvironment), module, loaderHelpers.config.startupOptions);
} else {
// Otherwise we found mono config schema
}
loaderHelpers.afterConfigLoaded.promise_control.resolve(loaderHelpers.config);
} catch (err) {
- const errMessage = `Failed to load config file ${configFilePath} ${err}`;
+ const errMessage = `Failed to load config file ${configFilePath} ${err} ${(err as Error)?.stack}`;
loaderHelpers.config = module.config = <any>{ message: errMessage, error: err, isError: true };
loaderHelpers.abort_startup(errMessage, true);
throw err;
const arrayBuffer = await node_fs.promises.readFile(url);
return <Response><any>{
ok: true,
- headers: [],
+ headers: {
+ length: 0,
+ get: () => null
+ },
url,
arrayBuffer: () => arrayBuffer,
json: () => JSON.parse(arrayBuffer),
return <Response><any>{
ok: true,
url,
+ headers: {
+ length: 0,
+ get: () => null
+ },
arrayBuffer: () => {
return new Uint8Array(read(url, "binary"));
},
ok: false,
url,
status: 500,
+ headers: {
+ length: 0,
+ get: () => null
+ },
statusText: "ERR28: " + e,
arrayBuffer: () => { throw e; },
json: () => { throw e; },
if (!module.configSrc && (!module.config || Object.keys(module.config).length === 0 || !module.config.assets)) {
// if config file location nor assets are provided
- if (loaderHelpers.scriptDirectory.indexOf("/_framework") == -1) {
- // we are not inside _framework (= wasm template)
- module.configSrc = "./_framework/blazor.boot.json";
- } else {
- // blazor app
- module.configSrc = "./blazor.boot.json";
- }
+ module.configSrc = "./blazor.boot.json";
}
// download config
export function preAllocatePThreadWorkerPool(defaultPthreadPoolSize: number, config: MonoConfig): void {
const poolSizeSpec = config?.pthreadPoolSize;
let n: number;
- if (poolSizeSpec === undefined) {
+ if (poolSizeSpec === undefined || poolSizeSpec === null) {
n = defaultPthreadPoolSize;
} else {
mono_assert(typeof poolSizeSpec === "number", "pthreadPoolSize must be a number");
readonly resources: ResourceGroups;
/** Gets a value that determines if this boot config was produced from a non-published build (i.e. dotnet build or dotnet run) */
readonly debugBuild: boolean;
+ readonly debugLevel: number;
readonly linkerEnabled: boolean;
readonly cacheBootResources: boolean;
readonly config: string[];
readonly icuDataMode: ICUDataMode;
readonly startupMemoryCache: boolean | undefined;
readonly runtimeOptions: string[] | undefined;
+ readonly environmentVariables?: { [name: string]: string };
+ readonly diagnosticTracing?: boolean;
+ readonly pthreadPoolSize: number;
// These properties are tacked on, and not found in the boot.json file
modifiableAssemblies: string | null;
export type BootJsonDataExtension = { [extensionName: string]: ResourceList };
export interface ResourceGroups {
+ readonly hash?: string;
readonly assembly: ResourceList;
readonly lazyAssembly: ResourceList;
readonly pdb?: ResourceList;
readonly libraryInitializers?: ResourceList,
readonly extensions?: BootJsonDataExtension
readonly runtimeAssets: ExtendedResourceList;
+ readonly vfs?: { [virtualPath: string]: ResourceList };
}
export type ResourceList = { [name: string]: string };
forwardConsoleLogsToWS?: boolean,
asyncFlushOnExit?: boolean
exitAfterSnapshot?: number,
- startupOptions?: Partial<WebAssemblyStartOptions>
+ startupOptions?: Partial<WebAssemblyStartOptions>,
+ loadAllSatelliteResources?: boolean
};
export type RunArguments = {
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import { dotnet } from './dotnet.js'
+import { dotnet } from './_framework/dotnet.js'
const { setModuleImports, getAssemblyExports, getConfig } = await dotnet
.withDiagnosticTracing(false)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import { dotnet } from './dotnet.js'
+import { dotnet } from './_framework/dotnet.js'
const { setModuleImports, getAssemblyExports, getConfig } = await dotnet
.withDiagnosticTracing(false)
//
// Run runtime tests under a JS shell or a browser
//
-import { dotnet, exit } from './dotnet.js';
+import { dotnet, exit } from './_framework/dotnet.js';
/*****************************************************************************
.withDiagnosticTracing(runArgs.diagnosticTracing)
.withExitOnUnhandledError()
.withExitCodeLogging()
- .withElementOnExit();
+ .withElementOnExit()
+ .withConfig({
+ loadAllSatelliteResources: true
+ });
if (is_node) {
dotnet
try {
console.log("Silently starting separate runtime instance as another ES6 module to populate caches...");
// this separate instance of the ES6 module, in which we just populate the caches
- const { dotnet } = await import('./dotnet.js?dry_run=true');
+ const { dotnet } = await import('./_framework/dotnet.js?dry_run=true');
configureRuntime(dotnet, runArgs);
// silent minimal startup
await dotnet.withConfig({
namespace Microsoft.NET.Sdk.WebAssembly;
+#nullable disable
+
/// <summary>
/// Defines the structure of a Blazor boot JSON file
/// </summary>
public bool debugBuild { get; set; }
/// <summary>
+ /// Gets a value that determines what level of debugging is configured.
+ /// </summary>
+ public int debugLevel { get; set; }
+
+ /// <summary>
/// Gets a value that determines if the linker is enabled.
/// </summary>
public bool linkerEnabled { get; set; }
/// Gets or sets configuration extensions.
/// </summary>
public Dictionary<string, Dictionary<string, object>> extensions { get; set; }
+
+ /// <summary>
+ /// Gets or sets environment variables.
+ /// </summary>
+ public object environmentVariables { get; set; }
+
+ /// <summary>
+ /// Gets or sets diagnostic tracing.
+ /// </summary>
+ public object diagnosticTracing { get; set; }
+
+ /// <summary>
+ /// Gets or sets pthread pool size.
+ /// </summary>
+ public int? pthreadPoolSize { get; set; }
}
public class ResourcesData
{
/// <summary>
+ /// Gets a hash of all resources
+ /// </summary>
+ public string hash { get; set; }
+
+ /// <summary>
/// .NET Wasm runtime resources (dotnet.wasm, dotnet.js) etc.
/// </summary>
public ResourceHashesByNameDictionary runtime { get; set; } = new ResourceHashesByNameDictionary();
[DataMember(EmitDefaultValue = false)]
public Dictionary<string, AdditionalAsset> runtimeAssets { get; set; }
+ [DataMember(EmitDefaultValue = false)]
+ public Dictionary<string, ResourceHashesByNameDictionary> vfs { get; set; }
+
+ [DataMember(EmitDefaultValue = false)]
+ public List<string> remoteSources { get; set; }
+
}
public enum ICUDataMode : int
public class AdditionalAsset
{
[DataMember(Name = "hash")]
- public string Hash { get; set; }
+ public string hash { get; set; }
[DataMember(Name = "behavior")]
- public string Behavior { get; set; }
+ public string behavior { get; set; }
}
entryAssembly = entryAssemblyName,
cacheBootResources = CacheBootResources,
debugBuild = DebugBuild,
+ debugLevel = DebugBuild ? 1 : 0,
linkerEnabled = LinkerEnabled,
resources = new ResourcesData(),
config = new List<string>(),
Log.LogMessage(MessageImportance.Low, "Added resource '{0}' to the list of additional assets in the manifest.", resource.ItemSpec);
additionalResources.Add(resourceName, new AdditionalAsset
{
- Hash = $"sha256-{resource.GetMetadata("FileHash")}",
- Behavior = behavior
+ hash = $"sha256-{resource.GetMetadata("FileHash")}",
+ behavior = behavior
});
}
}
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Text.Json.Serialization;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
+using Microsoft.NET.Sdk.WebAssembly;
namespace Microsoft.WebAssembly.Build.Tasks;
public bool IncludeThreadsWorker { get; set; }
public int PThreadPoolSize { get; set; }
public bool UseWebcil { get; set; }
+ public bool WasmIncludeFullIcuData { get; set; }
+ public string? WasmIcuDataFileName { get; set; }
+ public string? RuntimeAssetsLocation { get; set; }
// <summary>
// Extra json elements to add to _framework/blazor.boot.json
// </summary>
public ITaskItem[]? ExtraConfig { get; set; }
- private sealed class WasmAppConfig
- {
- [JsonPropertyName("mainAssemblyName")]
- public string? MainAssemblyName { get; set; }
- [JsonPropertyName("assemblyRootFolder")]
- public string AssemblyRootFolder { get; set; } = "managed";
- [JsonPropertyName("debugLevel")]
- public int DebugLevel { get; set; } = 0;
- [JsonPropertyName("assets")]
- public List<object> Assets { get; } = new List<object>();
- [JsonPropertyName("remoteSources")]
- public List<string> RemoteSources { get; set; } = new List<string>();
- [JsonPropertyName("globalizationMode")]
- public string? GlobalizationMode { get; set; }
- [JsonExtensionData]
- public Dictionary<string, object?> Extra { get; set; } = new();
- [JsonPropertyName("assetsHash")]
- public string AssetsHash { get; set; } = "none";
- }
-
- private class AssetEntry
- {
- protected AssetEntry(string name, string hash, string behavior)
- {
- Name = name;
- Behavior = behavior;
- Hash = hash;
- }
- [JsonPropertyName("behavior")]
- public string Behavior { get; init; }
- [JsonPropertyName("name")]
- public string Name { get; init; }
- [JsonPropertyName("hash")]
- public string? Hash { get; set; }
- }
-
- private sealed class WasmEntry : AssetEntry
- {
- public WasmEntry(string name, string hash) : base(name, hash, "dotnetwasm") { }
- }
-
- private sealed class LoaderJsEntry : AssetEntry
- {
- public LoaderJsEntry(string name, string hash) : base(name, hash, "js-module-dotnet") { }
- }
-
- private sealed class NativeJsEntry : AssetEntry
- {
- public NativeJsEntry(string name, string hash) : base(name, hash, "js-module-native") { }
- }
-
- private sealed class RuntimeJsEntry : AssetEntry
- {
- public RuntimeJsEntry(string name, string hash) : base(name, hash, "js-module-runtime") { }
- }
-
- private sealed class ThreadsWorkerEntry : AssetEntry
- {
- public ThreadsWorkerEntry(string name, string hash) : base(name, hash, "js-module-threads") { }
- }
-
- private sealed class AssemblyEntry : AssetEntry
- {
- public AssemblyEntry(string name, string hash) : base(name, hash, "assembly") { }
- }
-
- private sealed class PdbEntry : AssetEntry
- {
- public PdbEntry(string name, string hash) : base(name, hash, "pdb") { }
- }
-
- private sealed class SatelliteAssemblyEntry : AssetEntry
- {
- public SatelliteAssemblyEntry(string name, string hash, string culture) : base(name, hash, "resource")
- {
- CultureName = culture;
- }
-
- [JsonPropertyName("culture")]
- public string CultureName { get; set; }
- }
-
- private sealed class VfsEntry : AssetEntry
- {
- public VfsEntry(string name, string hash) : base(name, hash, "vfs") { }
- [JsonPropertyName("virtualPath")]
- public string? VirtualPath { get; set; }
- }
-
- private sealed class IcuData : AssetEntry
- {
- public IcuData(string name, string hash) : base(name, hash, "icu") { }
- [JsonPropertyName("loadRemote")]
- public bool LoadRemote { get; set; }
- }
-
- private sealed class SymbolsData : AssetEntry
- {
- public SymbolsData(string name, string hash) : base(name, hash, "symbols") { }
- }
-
protected override bool ValidateArguments()
{
if (!base.ValidateArguments())
return true;
}
+ private ICUDataMode GetICUDataMode()
+ {
+ // Invariant has always precedence
+ if (InvariantGlobalization)
+ return ICUDataMode.Invariant;
+
+ // If user provided a path to a custom ICU data file, use it
+ if (!string.IsNullOrEmpty(WasmIcuDataFileName))
+ return ICUDataMode.Custom;
+
+ // Hybrid mode
+ if (HybridGlobalization)
+ return ICUDataMode.Hybrid;
+
+ // If user requested to include full ICU data, use it
+ if (WasmIncludeFullIcuData)
+ return ICUDataMode.All;
+
+ // Otherwise, use sharded mode
+ return ICUDataMode.Sharded;
+ }
+
protected override bool ExecuteInternal()
{
if (!ValidateArguments())
}
MainAssemblyName = Path.GetFileName(MainAssemblyName);
- var config = new WasmAppConfig()
+ var bootConfig = new BootJsonData()
{
- MainAssemblyName = MainAssemblyName,
- GlobalizationMode = InvariantGlobalization ? "invariant" : HybridGlobalization ? "hybrid" : "icu"
+ config = new(),
+ entryAssembly = MainAssemblyName,
+ icuDataMode = GetICUDataMode()
};
// Create app
- var asmRootPath = Path.Combine(AppDir, config.AssemblyRootFolder);
+ var runtimeAssetsPath = !string.IsNullOrEmpty(RuntimeAssetsLocation)
+ ? Path.Combine(AppDir, RuntimeAssetsLocation)
+ : AppDir;
+
+ Log.LogMessage(MessageImportance.Low, $"Runtime assets output path {runtimeAssetsPath}");
+
Directory.CreateDirectory(AppDir!);
- Directory.CreateDirectory(asmRootPath);
+ Directory.CreateDirectory(runtimeAssetsPath);
+
if (UseWebcil)
Log.LogMessage(MessageImportance.Normal, "Converting assemblies to Webcil");
+
foreach (var assembly in _assemblies)
{
if (UseWebcil)
var tmpWebcil = Path.GetTempFileName();
var webcilWriter = Microsoft.WebAssembly.Build.Tasks.WebcilConverter.FromPortableExecutable(inputPath: assembly, outputPath: tmpWebcil, logger: Log);
webcilWriter.ConvertToWebcil();
- var finalWebcil = Path.Combine(asmRootPath, Path.ChangeExtension(Path.GetFileName(assembly), Utils.WebcilInWasmExtension));
+ var finalWebcil = Path.Combine(runtimeAssetsPath, Path.ChangeExtension(Path.GetFileName(assembly), Utils.WebcilInWasmExtension));
if (Utils.CopyIfDifferent(tmpWebcil, finalWebcil, useHash: true))
Log.LogMessage(MessageImportance.Low, $"Generated {finalWebcil} .");
else
}
else
{
- FileCopyChecked(assembly, Path.Combine(asmRootPath, Path.GetFileName(assembly)), "Assemblies");
+ FileCopyChecked(assembly, Path.Combine(runtimeAssetsPath, Path.GetFileName(assembly)), "Assemblies");
}
if (DebugLevel != 0)
{
var pdb = assembly;
pdb = Path.ChangeExtension(pdb, ".pdb");
if (File.Exists(pdb))
- FileCopyChecked(pdb, Path.Combine(asmRootPath, Path.GetFileName(pdb)), "Assemblies");
+ FileCopyChecked(pdb, Path.Combine(runtimeAssetsPath, Path.GetFileName(pdb)), "Assemblies");
}
}
foreach (ITaskItem item in NativeAssets)
{
var name = Path.GetFileName(item.ItemSpec);
- var dest = Path.Combine(AppDir!, name);
+ var dest = Path.Combine(runtimeAssetsPath, name);
if (!FileCopyChecked(item.ItemSpec, dest, "NativeAssets"))
return false;
- if (name == "dotnet.js")
- {
- config.Assets.Add(new LoaderJsEntry (name, Utils.ComputeIntegrity(item.ItemSpec)) );
- }
- else if (name == "dotnet.native.wasm")
- {
- config.Assets.Add(new WasmEntry(name, Utils.ComputeIntegrity(item.ItemSpec)));
- }
- else if (name == "dotnet.native.js")
- {
- config.Assets.Add(new NativeJsEntry (name, Utils.ComputeIntegrity(item.ItemSpec)) );
- }
- else if (name == "dotnet.runtime.js")
- {
- config.Assets.Add(new RuntimeJsEntry (name, Utils.ComputeIntegrity(item.ItemSpec)) );
- }
- else if (IncludeThreadsWorker && name == "dotnet.native.worker.js")
- {
- config.Assets.Add(new ThreadsWorkerEntry(name, Utils.ComputeIntegrity(item.ItemSpec)));
- }
- else if (name == "dotnet.native.js.symbols")
+
+ if (!IncludeThreadsWorker && name == "dotnet.native.worker.js")
+ continue;
+
+ var itemHash = Utils.ComputeIntegrity(item.ItemSpec);
+
+ if (name.StartsWith("dotnet", StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetExtension(name), ".wasm", StringComparison.OrdinalIgnoreCase))
{
- config.Assets.Add(new SymbolsData(name, Utils.ComputeIntegrity(item.ItemSpec)));
+ if (bootConfig.resources.runtimeAssets == null)
+ bootConfig.resources.runtimeAssets = new();
+
+ bootConfig.resources.runtimeAssets[name] = new()
+ {
+ hash = itemHash,
+ behavior = "dotnetwasm"
+ };
}
+
+ bootConfig.resources.runtime[name] = itemHash;
}
string packageJsonPath = Path.Combine(AppDir, "package.json");
{
if (UseWebcil)
{
- assemblyPath = Path.Combine(asmRootPath, Path.ChangeExtension(Path.GetFileName(assembly), Utils.WebcilInWasmExtension));
+ assemblyPath = Path.Combine(runtimeAssetsPath, Path.ChangeExtension(Path.GetFileName(assembly), Utils.WebcilInWasmExtension));
// For the hash, read the bytes from the webcil file, not the dll file.
bytes = File.ReadAllBytes(assemblyPath);
}
- config.Assets.Add(new AssemblyEntry(Path.GetFileName(assemblyPath), Utils.ComputeIntegrity(bytes)));
+ bootConfig.resources.assembly[Path.GetFileName(assemblyPath)] = Utils.ComputeIntegrity(bytes);
if (DebugLevel != 0)
{
- var pdb = assembly;
- pdb = Path.ChangeExtension(pdb, ".pdb");
+ var pdb = Path.ChangeExtension(assembly, ".pdb");
if (File.Exists(pdb))
- config.Assets.Add(new PdbEntry(Path.GetFileName(pdb), Utils.ComputeIntegrity(pdb)));
+ {
+ if (bootConfig.resources.pdb == null)
+ bootConfig.resources.pdb = new();
+
+ bootConfig.resources.pdb[Path.GetFileName(pdb)] = Utils.ComputeIntegrity(pdb);
+ }
}
}
}
- config.DebugLevel = DebugLevel;
+ bootConfig.debugBuild = DebugLevel > 0;
+ bootConfig.debugLevel = DebugLevel;
ProcessSatelliteAssemblies(args =>
{
+ if (bootConfig.resources.satelliteResources == null)
+ bootConfig.resources.satelliteResources = new();
+
string name = Path.GetFileName(args.fullPath);
- string directory = Path.Combine(AppDir, config.AssemblyRootFolder, args.culture);
- Directory.CreateDirectory(directory);
+ string cultureDirectory = Path.Combine(runtimeAssetsPath, args.culture);
+ Directory.CreateDirectory(cultureDirectory);
if (UseWebcil)
{
var tmpWebcil = Path.GetTempFileName();
var webcilWriter = Microsoft.WebAssembly.Build.Tasks.WebcilConverter.FromPortableExecutable(inputPath: args.fullPath, outputPath: tmpWebcil, logger: Log);
webcilWriter.ConvertToWebcil();
- var finalWebcil = Path.Combine(directory, Path.ChangeExtension(name, Utils.WebcilInWasmExtension));
+ var finalWebcil = Path.Combine(cultureDirectory, Path.ChangeExtension(name, Utils.WebcilInWasmExtension));
if (Utils.CopyIfDifferent(tmpWebcil, finalWebcil, useHash: true))
Log.LogMessage(MessageImportance.Low, $"Generated {finalWebcil} .");
else
Log.LogMessage(MessageImportance.Low, $"Skipped generating {finalWebcil} as the contents are unchanged.");
_fileWrites.Add(finalWebcil);
- config.Assets.Add(new SatelliteAssemblyEntry(Path.GetFileName(finalWebcil), Utils.ComputeIntegrity(finalWebcil), args.culture));
+
+ if (!bootConfig.resources.satelliteResources.TryGetValue(args.culture, out var cultureSatelliteResources))
+ bootConfig.resources.satelliteResources[args.culture] = cultureSatelliteResources = new();
+
+ cultureSatelliteResources[Path.GetFileName(finalWebcil)] = Utils.ComputeIntegrity(finalWebcil);
}
else
{
- var satellitePath = Path.Combine(directory, name);
+ var satellitePath = Path.Combine(cultureDirectory, name);
FileCopyChecked(args.fullPath, satellitePath, "SatelliteAssemblies");
- config.Assets.Add(new SatelliteAssemblyEntry(name, Utils.ComputeIntegrity(satellitePath), args.culture));
+
+ if (!bootConfig.resources.satelliteResources.TryGetValue(args.culture, out var cultureSatelliteResources))
+ bootConfig.resources.satelliteResources[args.culture] = cultureSatelliteResources = new();
+
+ cultureSatelliteResources[name] = Utils.ComputeIntegrity(satellitePath);
}
});
if (FilesToIncludeInFileSystem.Length > 0)
{
- string supportFilesDir = Path.Combine(AppDir, "supportFiles");
+ string supportFilesDir = Path.Combine(runtimeAssetsPath, "supportFiles");
Directory.CreateDirectory(supportFilesDir);
var i = 0;
StringDictionary targetPathTable = new();
+ var vfs = new Dictionary<string, Dictionary<string, string>>();
foreach (var item in FilesToIncludeInFileSystem)
{
string? targetPath = item.GetMetadata("TargetPath");
var vfsPath = Path.Combine(supportFilesDir, generatedFileName);
FileCopyChecked(item.ItemSpec, vfsPath, "FilesToIncludeInFileSystem");
- var asset = new VfsEntry($"supportFiles/{generatedFileName}", Utils.ComputeIntegrity(vfsPath))
+ vfs[targetPath] = new()
{
- VirtualPath = targetPath
+ [$"supportFiles/{generatedFileName}"] = Utils.ComputeIntegrity(vfsPath)
};
- config.Assets.Add(asset);
}
+
+ if (vfs.Count > 0)
+ bootConfig.resources.vfs = vfs;
}
if (!InvariantGlobalization)
Log.LogError($"Expected the file defined as ICU resource: {idfn} to exist but it does not.");
return false;
}
- config.Assets.Add(new IcuData(Path.GetFileName(idfn), Utils.ComputeIntegrity(idfn)) { LoadRemote = loadRemote });
+
+ bootConfig.resources.runtime[Path.GetFileName(idfn)] = Utils.ComputeIntegrity(idfn);
}
}
if (RemoteSources?.Length > 0)
{
+ bootConfig.resources.remoteSources = new();
foreach (var source in RemoteSources)
if (source != null && source.ItemSpec != null)
- config.RemoteSources.Add(source.ItemSpec);
+ bootConfig.resources.remoteSources.Add(source.ItemSpec);
}
+ var extraConfiguration = new Dictionary<string, object?>();
+
if (PThreadPoolSize < -1)
{
throw new LogAsErrorException($"PThreadPoolSize must be -1, 0 or positive, but got {PThreadPoolSize}");
}
else if (PThreadPoolSize > -1)
{
- config.Extra["pthreadPoolSize"] = PThreadPoolSize;
+ bootConfig.pthreadPoolSize = PThreadPoolSize;
}
foreach (ITaskItem extra in ExtraConfig ?? Enumerable.Empty<ITaskItem>())
if (!TryParseExtraConfigValue(extra, out object? valueObject))
return false;
- config.Extra[name] = valueObject;
+ if (string.Equals(name, nameof(BootJsonData.environmentVariables), StringComparison.OrdinalIgnoreCase))
+ {
+ bootConfig.environmentVariables = valueObject;
+ }
+ else if (string.Equals(name, nameof(BootJsonData.diagnosticTracing), StringComparison.OrdinalIgnoreCase))
+ {
+ if (valueObject is bool boolValue || (valueObject is string stringValue && bool.TryParse(stringValue, out boolValue)))
+ bootConfig.diagnosticTracing = boolValue;
+ else
+ throw new LogAsErrorException($"Unsupported value '{valueObject}' of type '{valueObject?.GetType()?.FullName}' for extra config 'diagnosticTracing'.");
+ }
+ else
+ {
+ extraConfiguration[name] = valueObject;
+ }
+ }
+
+ if (extraConfiguration.Count > 0)
+ {
+ bootConfig.extensions = new()
+ {
+ ["extra"] = extraConfiguration
+ };
}
string tmpMonoConfigPath = Path.GetTempFileName();
using (var sw = File.CreateText(tmpMonoConfigPath))
{
var sb = new StringBuilder();
- foreach (AssetEntry asset in config.Assets)
+
+ static void AddDictionary(StringBuilder sb, Dictionary<string, string> res)
{
- sb.Append(asset.Hash);
+ foreach (var asset in res)
+ sb.Append(asset.Value);
}
- config.AssetsHash = Utils.ComputeTextIntegrity(sb.ToString());
- var json = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true });
+ AddDictionary(sb, bootConfig.resources.assembly);
+ AddDictionary(sb, bootConfig.resources.runtime);
+
+ if (bootConfig.resources.lazyAssembly != null)
+ AddDictionary(sb, bootConfig.resources.lazyAssembly);
+
+ if (bootConfig.resources.satelliteResources != null)
+ {
+ foreach (var culture in bootConfig.resources.satelliteResources)
+ AddDictionary(sb, culture.Value);
+ }
+
+ if (bootConfig.resources.vfs != null)
+ {
+ foreach (var entry in bootConfig.resources.vfs)
+ AddDictionary(sb, entry.Value);
+ }
+
+ bootConfig.resources.hash = Utils.ComputeTextIntegrity(sb.ToString());
+
+ var json = JsonSerializer.Serialize(bootConfig, new JsonSerializerOptions { WriteIndented = true });
sw.Write(json);
}
- string monoConfigDir = Path.Combine(AppDir, "_framework");
- if (!Directory.Exists(monoConfigDir))
- Directory.CreateDirectory(monoConfigDir);
-
- string monoConfigPath = Path.Combine(monoConfigDir, "blazor.boot.json"); // TODO: Unify with Wasm SDK
+ string monoConfigPath = Path.Combine(runtimeAssetsPath, "blazor.boot.json"); // TODO: Unify with Wasm SDK
Utils.CopyIfDifferent(tmpMonoConfigPath, monoConfigPath, useHash: false);
_fileWrites.Add(monoConfigPath);
<ItemGroup>
<Compile Include="..\Common\Utils.cs" />
<Compile Include="..\Common\LogAsErrorException.cs" />
+ <Compile Include="..\Microsoft.NET.Sdk.WebAssembly.Pack.Tasks\BootJsonData.cs" />
<PackageReference Include="Microsoft.Build" Version="$(MicrosoftBuildVersion)" />
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="$(MicrosoftBuildTasksCoreVersion)" />
-import { dotnet } from './dotnet.js'
+import { dotnet } from './_framework/dotnet.js'
function wasm_exit(exit_code) {
var tests_done_elem = document.createElement("label");
-import { dotnet } from './dotnet.js'
+import { dotnet } from './_framework/dotnet.js'
function wasm_exit(exit_code) {
var tests_done_elem = document.createElement("label");
-import { dotnet } from './dotnet.js'
+import { dotnet } from './_framework/dotnet.js'
function wasm_exit(exit_code) {
var tests_done_elem = document.createElement("label");