From eeb9429e2d1cfd937ca622eecc750b55ce64ba73 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Fri, 17 Apr 2026 17:19:31 +0300 Subject: [PATCH 1/9] pack debug artifacts --- .../Bootsharp.Publish.Test/Pack/PackTest.cs | 4 +- .../Pack/ResourceTest.cs | 24 ++++++++++ .../Bootsharp.Publish/Pack/BootsharpPack.cs | 4 +- .../Pack/ResourceGenerator.cs | 15 +++++- src/cs/Bootsharp/Build/Bootsharp.targets | 4 +- src/cs/Directory.Build.props | 2 +- src/js/src/config.ts | 46 +++++++++++++++---- src/js/src/modules.ts | 8 ++-- src/js/src/resources.ts | 2 + 9 files changed, 89 insertions(+), 20 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs index 896f09e3..ebb1f364 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs @@ -43,9 +43,9 @@ protected override void AddAssembly (string assemblyName, params MockSource[] so InspectedDirectory = Project.Root, EntryAssemblyName = "System.Runtime.dll", BuildEngine = Engine, - TrimmingEnabled = false, EmbedBinaries = false, Threading = false, - LLVM = false + LLVM = false, + Debug = false }; } diff --git a/src/cs/Bootsharp.Publish.Test/Pack/ResourceTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/ResourceTest.cs index af2edb48..c4bc34b5 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/ResourceTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/ResourceTest.cs @@ -31,4 +31,28 @@ public void BinariesNotEmbeddedWhenDisabled () Contains("""wasm: { name: "dotnet.native.wasm", content: undefined },"""); Contains("""{ name: "Foo.wasm", content: undefined"""); } + + [Fact] + public void WhenDebugEnabledDebugArtifactsIncluded () + { + Task.Debug = true; + AddAssembly("Foo.dll"); + Project.WriteFile("Foo.pdb", "MockPdbContent"); + Project.WriteFile("dotnet.native.js.symbols", "MockSymbolsContent"); + Execute(); + Contains("""{ name: "Foo.pdb", content: undefined }"""); + Contains("""{ name: "dotnet.native.js.symbols", content: undefined }"""); + } + + [Fact] + public void WhenDebugDisabledDebugArtifactsNotIncluded () + { + Task.Debug = false; + AddAssembly("Foo.dll"); + Project.WriteFile("Foo.pdb", "MockPdbContent"); + Project.WriteFile("dotnet.native.js.symbols", "MockSymbolsContent"); + Execute(); + DoesNotContain("""{ name: "Foo.pdb", content: undefined }"""); + DoesNotContain("""{ name: "dotnet.native.js.symbols", content: undefined }"""); + } } diff --git a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs index b17eb1d4..eb93e8e8 100644 --- a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs +++ b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs @@ -8,10 +8,10 @@ public sealed class BootsharpPack : Microsoft.Build.Utilities.Task public required string BuildDirectory { get; set; } public required string InspectedDirectory { get; set; } public required string EntryAssemblyName { get; set; } - public required bool TrimmingEnabled { get; set; } public required bool EmbedBinaries { get; set; } public required bool Threading { get; set; } public required bool LLVM { get; set; } + public required bool Debug { get; set; } public override bool Execute () { @@ -63,7 +63,7 @@ private void GenerateDeclarations (Preferences prefs, SolutionInspection inspect private void GenerateResources (SolutionInspection inspection) { - var generator = new ResourceGenerator(EntryAssemblyName, EmbedBinaries); + var generator = new ResourceGenerator(EntryAssemblyName, EmbedBinaries, Debug); var content = generator.Generate(BuildDirectory); File.WriteAllText(Path.Combine(BuildDirectory, "resources.g.js"), content); } diff --git a/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs b/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs index 9c928b42..b1dd5446 100644 --- a/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs @@ -1,15 +1,23 @@ namespace Bootsharp.Publish; -internal sealed class ResourceGenerator (string entryAssemblyName, bool embed) +internal sealed class ResourceGenerator (string entryAssemblyName, bool embed, bool debug) { private readonly List assemblies = []; + private readonly List debugging = []; private string wasm = null!; public string Generate (string buildDir) { - foreach (var path in Directory.GetFiles(buildDir, "*.wasm")) + foreach (var path in Directory.GetFiles(buildDir, "*.wasm").Order()) if (path.EndsWith("dotnet.native.wasm")) wasm = BuildBin(path); else assemblies.Add(BuildBin(path)); + if (debug) + { + foreach (var path in Directory.GetFiles(buildDir, "*.pdb").Order()) + debugging.Add(BuildBin(path)); + foreach (var path in Directory.GetFiles(buildDir, "*.symbols").Order()) + debugging.Add(BuildBin(path)); + } return $$""" export default { @@ -17,6 +25,9 @@ public string Generate (string buildDir) assemblies: [ {{JoinLines(assemblies, 2, ",\n")}} ], + debugging: [ + {{JoinLines(debugging, 2, ",\n")}} + ], entryAssemblyName: "{{entryAssemblyName}}" }; """; diff --git a/src/cs/Bootsharp/Build/Bootsharp.targets b/src/cs/Bootsharp/Build/Bootsharp.targets index 25ecb60d..14b076c1 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.targets +++ b/src/cs/Bootsharp/Build/Bootsharp.targets @@ -126,10 +126,10 @@ + LLVM="$(BootsharpLlvm)" + Debug="!$(BootsharpLlvm)"/> - 0.8.0-alpha.46 + 0.8.0-alpha.54 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com diff --git a/src/js/src/config.ts b/src/js/src/config.ts index 5c4dfe85..5c039f8c 100644 --- a/src/js/src/config.ts +++ b/src/js/src/config.ts @@ -1,4 +1,4 @@ -import { RuntimeConfig, RuntimeWasm, RuntimeModule, RuntimeAssembly, getRuntime, getNative } from "./modules"; +import { RuntimeConfig, WasmAsset, ModuleAsset, AssemblyAsset, PdbAsset, SymbolsAsset, getRuntime, getNative } from "./modules"; import { BinaryResource, BootResources } from "./resources"; import { decodeBase64 } from "./decoder"; @@ -8,11 +8,13 @@ import { decodeBase64 } from "./decoder"; export async function buildConfig(resources: BootResources, root?: string): Promise { const embed = root == null; const mt = !embed && (await import("./dotnet.g")).mt; - const [wasm, native, runtime, assemblies] = await Promise.all([ + const [wasm, native, runtime, assemblies, pdb, symbols] = await Promise.all([ resolveWasm(), resolveModule("dotnet.native.js", embed ? getNative : undefined), resolveModule("dotnet.runtime.js", embed ? getRuntime : undefined), - Promise.all(resources.assemblies.map(resolveAssembly)) + Promise.all(resources.assemblies.map(resolveAssembly)), + Promise.all(resources.debugging.filter(r => r.name.endsWith(".pdb")).map(resolvePdb)), + Promise.all(resources.debugging.filter(r => r.name.endsWith(".symbols")).map(resolveSymbols)) ]); return { resources: { @@ -20,26 +22,29 @@ export async function buildConfig(resources: BootResources, root?: string): Prom jsModuleNative: [native], jsModuleRuntime: [runtime], jsModuleWorker: mt ? [await resolveModule("dotnet.native.worker.mjs")] : undefined, - assembly: assemblies + assembly: assemblies, + wasmSymbols: symbols, + pdb: pdb }, - mainAssemblyName: resources.entryAssemblyName + mainAssemblyName: resources.entryAssemblyName, + debugLevel: resources.debugging.length > 0 ? -1 : undefined }; - async function resolveWasm(): Promise { + async function resolveWasm(): Promise { return { name: resources.wasm.name, buffer: await resolveBuffer(resources.wasm) }; } - async function resolveModule(name: string, embed?: () => Promise): Promise { + async function resolveModule(name: string, embed?: () => Promise): Promise { return { name, moduleExports: embed ? await embed() : undefined }; } - async function resolveAssembly(res: BinaryResource): Promise { + async function resolveAssembly(res: BinaryResource): Promise { return { name: res.name, virtualPath: res.name, @@ -47,6 +52,31 @@ export async function buildConfig(resources: BootResources, root?: string): Prom }; } + async function resolvePdb(res: BinaryResource): Promise { + return { + name: res.name, + virtualPath: res.name, + buffer: await resolveBuffer(res) + }; + } + + async function resolveSymbols(res: BinaryResource): Promise { + return { + name: res.name, + pendingDownload: { + name: res.name, + url: embed ? res.name : `${root}/${res.name}`, + response: fakeResponse() + } + }; + + // Due to a bug in .NET, symbols ignore 'buffer', so we have to fake an HTTP response. + async function fakeResponse(): Promise { + const text = new TextDecoder("utf-8").decode(await resolveBuffer(res)); + return new Response(text, { status: 200 }); + } + } + async function resolveBuffer(res: BinaryResource): Promise { if (typeof res.content === "string") return decodeBase64(res.content); if (res.content !== undefined) return res.content.buffer; diff --git a/src/js/src/modules.ts b/src/js/src/modules.ts index 4a7bc9d2..02435911 100644 --- a/src/js/src/modules.ts +++ b/src/js/src/modules.ts @@ -3,9 +3,11 @@ import type { ModuleAPI, MonoConfig } from "./dotnet.g.d.ts"; export type * from "./dotnet.g.d.ts"; export type RuntimeConfig = MonoConfig; export type RuntimeResources = NonNullable; -export type RuntimeWasm = NonNullable[number]; -export type RuntimeModule = NonNullable[number]; -export type RuntimeAssembly = NonNullable[number]; +export type WasmAsset = NonNullable[number]; +export type ModuleAsset = NonNullable[number]; +export type AssemblyAsset = NonNullable[number]; +export type PdbAsset = NonNullable[number]; +export type SymbolsAsset = NonNullable[number]; /** Fetches the main dotnet module (dotnet.js). */ export async function getMain(root?: string): Promise { diff --git a/src/js/src/resources.ts b/src/js/src/resources.ts index 3229c5f9..efa8da55 100644 --- a/src/js/src/resources.ts +++ b/src/js/src/resources.ts @@ -6,6 +6,8 @@ export type BootResources = { readonly wasm: BinaryResource; /** Compiled .NET assemblies. */ readonly assemblies: BinaryResource[]; + /** Debugging artifacts consumed by the runtime. */ + readonly debugging: BinaryResource[]; /** Name of the entry (main) assembly, with .dll extension. */ readonly entryAssemblyName: string; } From 04af8e2f7bddc832f3dfe88605d9b4f684aae886 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Fri, 17 Apr 2026 17:54:03 +0300 Subject: [PATCH 2/9] cover debug --- src/cs/Directory.Build.props | 2 +- src/js/src/config.ts | 10 +++------- src/js/test/spec/boot.spec.ts | 11 +++++++++++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index fba19fd1..30ae0a1a 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,7 +1,7 @@ - 0.8.0-alpha.54 + 0.8.0-alpha.56 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com diff --git a/src/js/src/config.ts b/src/js/src/config.ts index 5c039f8c..e1ed4cab 100644 --- a/src/js/src/config.ts +++ b/src/js/src/config.ts @@ -61,20 +61,16 @@ export async function buildConfig(resources: BootResources, root?: string): Prom } async function resolveSymbols(res: BinaryResource): Promise { + // Due to a bug in .NET, symbols ignore 'buffer', so we have to fake an HTTP response. + const txt = new TextDecoder("utf-8").decode(await resolveBuffer(res)); return { name: res.name, pendingDownload: { name: res.name, url: embed ? res.name : `${root}/${res.name}`, - response: fakeResponse() + response: Promise.resolve(new Response(txt, { status: 200 })) } }; - - // Due to a bug in .NET, symbols ignore 'buffer', so we have to fake an HTTP response. - async function fakeResponse(): Promise { - const text = new TextDecoder("utf-8").decode(await resolveBuffer(res)); - return new Response(text, { status: 200 }); - } } async function resolveBuffer(res: BinaryResource): Promise { diff --git a/src/js/test/spec/boot.spec.ts b/src/js/test/spec/boot.spec.ts index cbb7cd0f..4c1f4073 100644 --- a/src/js/test/spec/boot.spec.ts +++ b/src/js/test/spec/boot.spec.ts @@ -37,6 +37,17 @@ describe("boot", () => { expect(config.resources!.jsModuleNative[0].moduleExports).toBeDefined(); expect(config.resources!.jsModuleRuntime[0].moduleExports).toBeDefined(); }); + it("enables debugging when debugging resources exist", async () => { + const { side: { bootsharp }, root } = await setup(); + const config = await bootsharp.dotnet.buildConfig(bootsharp.resources, root); + expect(config.debugLevel).not.toBeUndefined(); + }); + it("doesn't enable debugging when missing debugging resources", async () => { + const { side: { bootsharp }, root, any } = await setup(); + const resources = any({ ...bootsharp.resources, debugging: [] }); + const config = await bootsharp.dotnet.buildConfig(resources, root); + expect(config.debugLevel).toBeUndefined(); + }); it("throws when missing boot resource", async () => { const { side: { bootsharp } } = await setup(); await expect(bootsharp.dotnet.buildConfig(bootsharp.resources)) From a1ea24e239277b371e13e7ca99800988ad1ecd9b Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Fri, 17 Apr 2026 18:17:06 +0300 Subject: [PATCH 3/9] fix debug dir --- src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs | 1 + src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs | 3 ++- src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs | 6 +++--- src/cs/Bootsharp/Build/Bootsharp.targets | 7 ++++--- src/cs/Directory.Build.props | 2 +- src/js/src/config.ts | 2 +- src/js/test/spec/boot.spec.ts | 4 ++-- 7 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs index ebb1f364..5a236306 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs @@ -40,6 +40,7 @@ protected override void AddAssembly (string assemblyName, params MockSource[] so private BootsharpPack CreateTask () => new() { BuildDirectory = Project.Root, + DebugDirectory = Project.Root, InspectedDirectory = Project.Root, EntryAssemblyName = "System.Runtime.dll", BuildEngine = Engine, diff --git a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs index eb93e8e8..2e3ccb36 100644 --- a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs +++ b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs @@ -6,6 +6,7 @@ namespace Bootsharp.Publish; public sealed class BootsharpPack : Microsoft.Build.Utilities.Task { public required string BuildDirectory { get; set; } + public required string DebugDirectory { get; set; } public required string InspectedDirectory { get; set; } public required string EntryAssemblyName { get; set; } public required bool EmbedBinaries { get; set; } @@ -64,7 +65,7 @@ private void GenerateDeclarations (Preferences prefs, SolutionInspection inspect private void GenerateResources (SolutionInspection inspection) { var generator = new ResourceGenerator(EntryAssemblyName, EmbedBinaries, Debug); - var content = generator.Generate(BuildDirectory); + var content = generator.Generate(BuildDirectory, DebugDirectory); File.WriteAllText(Path.Combine(BuildDirectory, "resources.g.js"), content); } diff --git a/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs b/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs index b1dd5446..34ea9dc7 100644 --- a/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs @@ -6,16 +6,16 @@ internal sealed class ResourceGenerator (string entryAssemblyName, bool embed, b private readonly List debugging = []; private string wasm = null!; - public string Generate (string buildDir) + public string Generate (string buildDir, string debugDir) { foreach (var path in Directory.GetFiles(buildDir, "*.wasm").Order()) if (path.EndsWith("dotnet.native.wasm")) wasm = BuildBin(path); else assemblies.Add(BuildBin(path)); if (debug) { - foreach (var path in Directory.GetFiles(buildDir, "*.pdb").Order()) + foreach (var path in Directory.GetFiles(debugDir, "*.pdb").Order()) debugging.Add(BuildBin(path)); - foreach (var path in Directory.GetFiles(buildDir, "*.symbols").Order()) + foreach (var path in Directory.GetFiles(debugDir, "*.symbols").Order()) debugging.Add(BuildBin(path)); } return diff --git a/src/cs/Bootsharp/Build/Bootsharp.targets b/src/cs/Bootsharp/Build/Bootsharp.targets index 14b076c1..254de374 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.targets +++ b/src/cs/Bootsharp/Build/Bootsharp.targets @@ -124,6 +124,7 @@ - - - + + + diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index 30ae0a1a..3c910af5 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,7 +1,7 @@ - 0.8.0-alpha.56 + 0.8.0-alpha.58 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com diff --git a/src/js/src/config.ts b/src/js/src/config.ts index e1ed4cab..96c24fc9 100644 --- a/src/js/src/config.ts +++ b/src/js/src/config.ts @@ -67,7 +67,7 @@ export async function buildConfig(resources: BootResources, root?: string): Prom name: res.name, pendingDownload: { name: res.name, - url: embed ? res.name : `${root}/${res.name}`, + url: res.name, response: Promise.resolve(new Response(txt, { status: 200 })) } }; diff --git a/src/js/test/spec/boot.spec.ts b/src/js/test/spec/boot.spec.ts index 4c1f4073..2f10e73d 100644 --- a/src/js/test/spec/boot.spec.ts +++ b/src/js/test/spec/boot.spec.ts @@ -43,8 +43,8 @@ describe("boot", () => { expect(config.debugLevel).not.toBeUndefined(); }); it("doesn't enable debugging when missing debugging resources", async () => { - const { side: { bootsharp }, root, any } = await setup(); - const resources = any({ ...bootsharp.resources, debugging: [] }); + const { side: { bootsharp }, root } = await setup(); + const resources = { ...bootsharp.resources, debugging: [] }; const config = await bootsharp.dotnet.buildConfig(resources, root); expect(config.debugLevel).toBeUndefined(); }); From 5d01437a050f9817a45056190fd6602c47809efc Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 18 Apr 2026 12:53:02 +0300 Subject: [PATCH 4/9] refactor props --- .../Mock/MockProject.cs | 6 + .../Pack/PatcherTest.cs | 26 +++ .../Bootsharp.Publish/Pack/BootsharpPack.cs | 2 +- .../Pack/ModulePatcher/ModulePatcher.cs | 4 +- src/cs/Bootsharp/Build/Bootsharp.props | 10 +- src/cs/Bootsharp/Build/Bootsharp.targets | 188 +++++++++--------- src/cs/Directory.Build.props | 2 +- 7 files changed, 135 insertions(+), 103 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs b/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs index 482f5f27..ae2fef10 100644 --- a/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs +++ b/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs @@ -34,6 +34,12 @@ public void WriteFile (string name, string content) File.WriteAllText(filePath, content); } + public string ReadFile (string name) + { + var filePath = Path.Combine(Root, name); + return File.ReadAllText(filePath); + } + private static string CreateUniqueRootDirectory () { var testAssembly = System.Reflection.Assembly.GetExecutingAssembly().Location; diff --git a/src/cs/Bootsharp.Publish.Test/Pack/PatcherTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/PatcherTest.cs index bb40a173..43e695d1 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/PatcherTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/PatcherTest.cs @@ -32,4 +32,30 @@ public void WhenTreadingEnabledFlagIsSet () Assert.Equal("export const embedded = false;\nexport const mt = true;", GeneratedRuntimeModule); Assert.Equal("export const embedded = false;\nexport const mt = true;", GeneratedNativeModule); } + + [Fact] + public void WhenDebugDisabledMapsAreRemoved () + { + Task.Debug = false; + Project.WriteFile("dotnet.js", "dotnet\n//# sourceMappingURL=dotnet.js.map\n"); + Project.WriteFile("dotnet.runtime.js", "runtime\n//# sourceMappingURL=dotnet.runtime.js.map\n"); + Project.WriteFile("dotnet.native.js", "native\n//# sourceMappingURL=dotnet.native.js.map\n"); + Execute(); + Assert.DoesNotContain("sourceMappingURL", Project.ReadFile("dotnet.js")); + Assert.DoesNotContain("sourceMappingURL", Project.ReadFile("dotnet.runtime.js")); + Assert.DoesNotContain("sourceMappingURL", Project.ReadFile("dotnet.runtime.js")); + } + + [Fact] + public void WhenDebugEnabledMapsArePreserved () + { + Task.Debug = true; + Project.WriteFile("dotnet.js", "dotnet\n//# sourceMappingURL=dotnet.js.map\n"); + Project.WriteFile("dotnet.runtime.js", "runtime\n//# sourceMappingURL=dotnet.runtime.js.map\n"); + Project.WriteFile("dotnet.native.js", "native\n//# sourceMappingURL=dotnet.native.js.map\n"); + Execute(); + Assert.Contains("sourceMappingURL", Project.ReadFile("dotnet.js")); + Assert.Contains("sourceMappingURL", Project.ReadFile("dotnet.runtime.js")); + Assert.Contains("sourceMappingURL", Project.ReadFile("dotnet.runtime.js")); + } } diff --git a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs index 2e3ccb36..0b931c4b 100644 --- a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs +++ b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs @@ -71,7 +71,7 @@ private void GenerateResources (SolutionInspection inspection) private void PatchModules () { - var patcher = new ModulePatcher(BuildDirectory, Threading, EmbedBinaries); + var patcher = new ModulePatcher(BuildDirectory, Threading, EmbedBinaries, Debug); patcher.Patch(); } } diff --git a/src/cs/Bootsharp.Publish/Pack/ModulePatcher/ModulePatcher.cs b/src/cs/Bootsharp.Publish/Pack/ModulePatcher/ModulePatcher.cs index 15450d64..265fde80 100644 --- a/src/cs/Bootsharp.Publish/Pack/ModulePatcher/ModulePatcher.cs +++ b/src/cs/Bootsharp.Publish/Pack/ModulePatcher/ModulePatcher.cs @@ -3,7 +3,7 @@ namespace Bootsharp.Publish; -internal sealed class ModulePatcher (string buildDir, bool thread, bool embed) +internal sealed class ModulePatcher (string buildDir, bool thread, bool embed, bool debug) { private readonly string dotnet = Path.Combine(buildDir, "dotnet.js"); private readonly string runtime = Path.Combine(buildDir, "dotnet.runtime.js"); @@ -16,7 +16,7 @@ public void Patch () { if (thread) PatchThreading(); if (embed) new InternalPatcher(dotnet, runtime, native).Patch(); - RemoveMaps(); + if (!debug) RemoveMaps(); RemoveWasmNag(); CopyInternals(); } diff --git a/src/cs/Bootsharp/Build/Bootsharp.props b/src/cs/Bootsharp/Build/Bootsharp.props index 422d84dd..98fcbeab 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.props +++ b/src/cs/Bootsharp/Build/Bootsharp.props @@ -30,11 +30,13 @@ + + $([MSBuild]::ValueOrDefault('$(Configuration)', '').Equals('Debug')) - $([MSBuild]::ValueOrDefault('$(Configuration)', '').Equals('Release')) + !$(BsDebug) - + true $(EmccFlags) -O3 @@ -77,7 +79,7 @@ false - + false false @@ -94,6 +96,6 @@ + Condition="$(BsLlvm)"/> diff --git a/src/cs/Bootsharp/Build/Bootsharp.targets b/src/cs/Bootsharp/Build/Bootsharp.targets index 254de374..468fe17a 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.targets +++ b/src/cs/Bootsharp/Build/Bootsharp.targets @@ -1,37 +1,37 @@ - $(MSBuildThisFileDirectory)../ - $(BootsharpRoot)/js - $(BootsharpRoot)/llvm - $(BootsharpRoot)/tasks/Bootsharp.Publish.dll - $(IntermediateOutputPath)bootsharp - $(BootsharpIntermediateDirectory)/Interfaces.g.cs - $(BootsharpIntermediateDirectory)/Dependencies.g.cs - $(BootsharpIntermediateDirectory)/Serializer.g.cs - $(BootsharpIntermediateDirectory)/Interop.g.cs - $(AssemblyName).dll - CopyNativeBinary - WasmNestedPublishApp + $(MSBuildThisFileDirectory)../ + $(BsRoot)/js + $(BsRoot)/llvm + $(BsRoot)/tasks/Bootsharp.Publish.dll + $(IntermediateOutputPath)bootsharp + $(BsIntermediateDir)/Interfaces.g.cs + $(BsIntermediateDir)/Dependencies.g.cs + $(BsIntermediateDir)/Serializer.g.cs + $(BsIntermediateDir)/Interop.g.cs + $(AssemblyName).dll + CopyNativeBinary + WasmNestedPublishApp - - + + + Include="$(BsLlvmDir)/runtime.win-x64.microsoft.dotnet.ilcompiler.llvm"> runtime.win-x64.Microsoft.DotNet.ILCompiler.LLVM - $(BootsharpLlvmDirectory)/runtime.win-x64.microsoft.dotnet.ilcompiler.llvm + $(BsLlvmDir)/runtime.win-x64.microsoft.dotnet.ilcompiler.llvm + Include="$(BsLlvmDir)/runtime.linux-x64.microsoft.dotnet.ilcompiler.llvm"> runtime.linux-x64.Microsoft.DotNet.ILCompiler.LLVM - $(BootsharpLlvmDirectory)/runtime.linux-x64.microsoft.dotnet.ilcompiler.llvm + $(BsLlvmDir)/runtime.linux-x64.microsoft.dotnet.ilcompiler.llvm - + runtime.browser-wasm.Microsoft.DotNet.ILCompiler.LLVM - $(BootsharpLlvmDirectory)/runtime.browser-wasm.microsoft.dotnet.ilcompiler.llvm + $(BsLlvmDir)/runtime.browser-wasm.microsoft.dotnet.ilcompiler.llvm @@ -41,148 +41,146 @@ + TaskName="Bootsharp.Publish.BootsharpEmit" AssemblyFile="$(BsPublishAssembly)"/> + TaskName="Bootsharp.Publish.BootsharpPack" AssemblyFile="$(BsPublishAssembly)"/> - - - - $(DefineConstants);BOOTSHARP_EMITTED - + - + + EntryAssemblyName="$(BsEntryAssembly)" + InterfacesFilePath="$(BsInterfacesFilePath)" + DependenciesFilePath="$(BsDependenciesFilePath)" + SerializerFilePath="$(BsSerializerFilePath)" + InteropFilePath="$(BsInteropFilePath)"/> - - - - - - - - + + + + + + + + - + - + - $(PublishDir) - $(WasmAppDir)/$(WasmRuntimeAssetsLocation) - $(BaseOutputPath.Replace('\', '/')) - $(BootSharpBaseOutputPath)$(BootsharpName) + $(PublishDir) + $(WasmAppDir)/$(WasmRuntimeAssetsLocation) + $(BaseOutputPath.Replace('\', '/')) + $(BsBaseOutputPath)$(BootsharpName) $(BootsharpPublishDirectory)/types $(BootsharpPublishDirectory)/bin $(BootsharpPublishDirectory) - npx --yes rollup index.js -o index.mjs -f es -e process,module,fs/promises --output.inlineDynamicImports - true - false - false + true + false + false + --sourcemap + npx --yes rollup index.js -o index.mjs -f es -e process,module,fs/promises --output.inlineDynamicImports $(BsBundleMapsArg) - + - + - - + - - + Threading="$(BsThreading)" + LLVM="$(BsLlvm)" + Debug="$(BsDebug)"/> - - - - - - - + + + + + + + - - - - - - - - + - - - + + + + - - - + + + + - + - - + - - - 0.8.0-alpha.61 + 0.8.0-alpha.65 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com diff --git a/src/js/test/cs/Test/Test.csproj b/src/js/test/cs/Test/Test.csproj index 89f1fe9f..7b4734a3 100644 --- a/src/js/test/cs/Test/Test.csproj +++ b/src/js/test/cs/Test/Test.csproj @@ -7,7 +7,7 @@ true bin/codegen false - npx --yes rollup index.js -d ./ -f es -e process,module,fs/promises --output.preserveModules --entryFileNames [name].mjs + npx --yes rollup index.js -d ./ -f es -e process,module,fs/promises --output.preserveModules --entryFileNames [name].mjs --sourcemap From d88014b48dff07df6c08508d1af60eb78da77397 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 18 Apr 2026 15:38:22 +0300 Subject: [PATCH 6/9] remove unused --- src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs b/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs index ae2fef10..482f5f27 100644 --- a/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs +++ b/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs @@ -34,12 +34,6 @@ public void WriteFile (string name, string content) File.WriteAllText(filePath, content); } - public string ReadFile (string name) - { - var filePath = Path.Combine(Root, name); - return File.ReadAllText(filePath); - } - private static string CreateUniqueRootDirectory () { var testAssembly = System.Reflection.Assembly.GetExecutingAssembly().Location; From bbf6b6164b506fce2f8e03e67fbd122bbe071d9b Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 18 Apr 2026 15:52:38 +0300 Subject: [PATCH 7/9] sharping --- .../Bootsharp.Publish/Pack/ResourceGenerator.cs | 16 ++++++++++------ src/cs/Directory.Build.props | 2 +- src/js/src/config.ts | 8 ++++---- src/js/src/resources.ts | 6 ++++-- src/js/test/spec/boot.spec.ts | 4 ++-- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs b/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs index 34ea9dc7..2806eab5 100644 --- a/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs @@ -3,7 +3,8 @@ namespace Bootsharp.Publish; internal sealed class ResourceGenerator (string entryAssemblyName, bool embed, bool debug) { private readonly List assemblies = []; - private readonly List debugging = []; + private readonly List symbols = []; + private readonly List pdb = []; private string wasm = null!; public string Generate (string buildDir, string debugDir) @@ -13,10 +14,10 @@ public string Generate (string buildDir, string debugDir) else assemblies.Add(BuildBin(path)); if (debug) { - foreach (var path in Directory.GetFiles(debugDir, "*.pdb").Order()) - debugging.Add(BuildBin(path)); foreach (var path in Directory.GetFiles(debugDir, "*.symbols").Order()) - debugging.Add(BuildBin(path)); + symbols.Add(BuildBin(path)); + foreach (var path in Directory.GetFiles(debugDir, "*.pdb").Order()) + pdb.Add(BuildBin(path)); } return $$""" @@ -25,8 +26,11 @@ public string Generate (string buildDir, string debugDir) assemblies: [ {{JoinLines(assemblies, 2, ",\n")}} ], - debugging: [ - {{JoinLines(debugging, 2, ",\n")}} + symbols: [ + {{JoinLines(symbols, 2, ",\n")}} + ], + pdb: [ + {{JoinLines(pdb, 2, ",\n")}} ], entryAssemblyName: "{{entryAssemblyName}}" }; diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index c4039ba5..530319c0 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,7 +1,7 @@ - 0.8.0-alpha.65 + 0.8.0-alpha.66 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com diff --git a/src/js/src/config.ts b/src/js/src/config.ts index 96c24fc9..c9d027ca 100644 --- a/src/js/src/config.ts +++ b/src/js/src/config.ts @@ -8,13 +8,13 @@ import { decodeBase64 } from "./decoder"; export async function buildConfig(resources: BootResources, root?: string): Promise { const embed = root == null; const mt = !embed && (await import("./dotnet.g")).mt; - const [wasm, native, runtime, assemblies, pdb, symbols] = await Promise.all([ + const [wasm, native, runtime, assemblies, symbols, pdb] = await Promise.all([ resolveWasm(), resolveModule("dotnet.native.js", embed ? getNative : undefined), resolveModule("dotnet.runtime.js", embed ? getRuntime : undefined), Promise.all(resources.assemblies.map(resolveAssembly)), - Promise.all(resources.debugging.filter(r => r.name.endsWith(".pdb")).map(resolvePdb)), - Promise.all(resources.debugging.filter(r => r.name.endsWith(".symbols")).map(resolveSymbols)) + Promise.all(resources.symbols.map(resolveSymbols)), + Promise.all(resources.pdb.map(resolvePdb)) ]); return { resources: { @@ -27,7 +27,7 @@ export async function buildConfig(resources: BootResources, root?: string): Prom pdb: pdb }, mainAssemblyName: resources.entryAssemblyName, - debugLevel: resources.debugging.length > 0 ? -1 : undefined + debugLevel: resources.symbols.length > 0 ? -1 : undefined }; async function resolveWasm(): Promise { diff --git a/src/js/src/resources.ts b/src/js/src/resources.ts index efa8da55..8a75280d 100644 --- a/src/js/src/resources.ts +++ b/src/js/src/resources.ts @@ -6,8 +6,10 @@ export type BootResources = { readonly wasm: BinaryResource; /** Compiled .NET assemblies. */ readonly assemblies: BinaryResource[]; - /** Debugging artifacts consumed by the runtime. */ - readonly debugging: BinaryResource[]; + /** WASM debug symbols. */ + readonly symbols: BinaryResource[]; + /** PDB debug artifacts. */ + readonly pdb: BinaryResource[]; /** Name of the entry (main) assembly, with .dll extension. */ readonly entryAssemblyName: string; } diff --git a/src/js/test/spec/boot.spec.ts b/src/js/test/spec/boot.spec.ts index 2f10e73d..9cc0ab0d 100644 --- a/src/js/test/spec/boot.spec.ts +++ b/src/js/test/spec/boot.spec.ts @@ -42,9 +42,9 @@ describe("boot", () => { const config = await bootsharp.dotnet.buildConfig(bootsharp.resources, root); expect(config.debugLevel).not.toBeUndefined(); }); - it("doesn't enable debugging when missing debugging resources", async () => { + it("doesn't enable debugging when missing debug artifacts", async () => { const { side: { bootsharp }, root } = await setup(); - const resources = { ...bootsharp.resources, debugging: [] }; + const resources = { ...bootsharp.resources, symbols: [], pdb: [] }; const config = await bootsharp.dotnet.buildConfig(resources, root); expect(config.debugLevel).toBeUndefined(); }); From 795665fbc432e117eb21b69deae6a0aad07085e3 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 18 Apr 2026 18:33:54 +0300 Subject: [PATCH 8/9] add error wrappers --- .../Pack/BindingTest.cs | 110 ++++++++++++------ .../Pack/BindingGenerator/BindingGenerator.cs | 44 +++++-- .../Bootsharp.Publish/Pack/BootsharpPack.cs | 2 +- src/cs/Directory.Build.props | 2 +- 4 files changed, 110 insertions(+), 48 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs index cc7c11d5..9a9cedfc 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs @@ -24,6 +24,40 @@ public void InteropFunctionsImported () """); } + [Fact] + public void WhenDebugEnabledEmitsAndUsesExportImportHelpers () + { + Task.Debug = true; + AddAssembly(WithClass( + """ + [JSInvokable] public static Task InvAsync () => Task.FromResult(0); + [JSFunction] public static void Fun () {} + """ + )); + Execute(); + Contains("function getExport"); + Contains("function getImport"); + Contains("""getExport("Class_InvAsync")"""); + Contains("""getImport(this.funHandler, this.funSerializedHandler, "Class.fun")"""); + } + + [Fact] + public void WhenDebugDisabledDoesntEmitAndDoesntUseExportImportHelpers () + { + Task.Debug = false; + AddAssembly(WithClass( + """ + [JSInvokable] public static Task InvAsync () => Task.FromResult(0); + [JSFunction] public static void Fun () {} + """ + )); + Execute(); + DoesNotContain("function getExport"); + DoesNotContain("function getImport"); + DoesNotContain("""getExport("Class_InvAsync")"""); + DoesNotContain("""getImport(this.funHandler, this.funSerializedHandler, "Class.fun")"""); + } + [Fact] public void BindingForInvokableMethodIsGenerated () { @@ -34,7 +68,7 @@ public void BindingForInvokableMethodIsGenerated () export const Foo = { Bar: { Class: { - nya: () => getExports().Foo_Bar_Class_Nya() + nya: () => exports.Foo_Bar_Class_Nya() } } }; @@ -53,7 +87,7 @@ public void BindingForFunctionMethodIsGenerated () Class: { get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { return getImport(this.funHandler, this.funSerializedHandler, "Foo.Bar.Class.fun"); } + get funSerialized() { return this.funSerializedHandler; } } } }; @@ -90,7 +124,7 @@ public void LibraryExportsNamespaceObject () """ export const Foo = { Class: { - bar: () => getExports().Foo_Class_Bar() + bar: () => exports.Foo_Class_Bar() } }; """); @@ -107,7 +141,7 @@ public void WhenSpaceContainDotsObjectCreatedForEachPart () Bar: { Nya: { Class: { - bar: () => getExports().Foo_Bar_Nya_Class_Bar() + bar: () => exports.Foo_Bar_Nya_Class_Bar() } } } @@ -129,13 +163,13 @@ public void WhenMultipleSpacesEachGetItsOwnObject () Class: { get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { return getImport(this.funHandler, this.funSerializedHandler, "Bar.Nya.Class.fun"); } + get funSerialized() { return this.funSerializedHandler; } } } }; export const Foo = { Class: { - foo: () => getExports().Foo_Class_Foo() + foo: () => exports.Foo_Class_Foo() } }; """); @@ -148,12 +182,12 @@ public void WhenMultipleAssembliesWithEqualSpaceObjectDeclaredOnlyOnce () AddAssembly(WithClass("Foo", "[JSFunction] public static void Fun () {}")); Execute(); Once("export const Foo"); - Contains("bar: () => getExports().Foo_Class_Bar()"); + Contains("bar: () => exports.Foo_Class_Bar()"); Contains( """ get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { return getImport(this.funHandler, this.funSerializedHandler, "Foo.Class.fun"); } + get funSerialized() { return this.funSerializedHandler; } """); } @@ -171,12 +205,12 @@ public void DifferentSpacesWithSameRootAssignedUnderSameObject () Class: { get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { return getImport(this.funHandler, this.funSerializedHandler, "Nya.Bar.Class.fun"); } + get funSerialized() { return this.funSerializedHandler; } } }, Foo: { Class: { - foo: () => getExports().Nya_Foo_Class_Foo() + foo: () => exports.Nya_Foo_Class_Foo() } } }; @@ -195,13 +229,13 @@ public void DifferentSpacesStartingEquallyAreNotAssignedToSameObject () """ export const Foo = { Class: { - method: () => getExports().Foo_Class_Method() + method: () => exports.Foo_Class_Method() } }; export const FooBar = { Baz: { Class: { - method: () => getExports().FooBar_Baz_Class_Method() + method: () => exports.FooBar_Baz_Class_Method() } } }; @@ -221,13 +255,13 @@ public void BindingsFromMultipleSpacesAssignedToRespectiveObjects () Class: { get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { return getImport(this.funHandler, this.funSerializedHandler, "Bar.Nya.Class.fun"); } + get funSerialized() { return this.funSerializedHandler; } } } }; export const Foo = { Class: { - foo: () => getExports().Foo_Class_Foo() + foo: () => exports.Foo_Class_Foo() } }; """); @@ -243,12 +277,12 @@ public void BindingsFromMultipleClassesAssignedToRespectiveObjects () Contains( """ export const ClassA = { - inv: () => getExports().ClassA_Inv() + inv: () => exports.ClassA_Inv() }; export const ClassB = { get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { return getImport(this.funHandler, this.funSerializedHandler, "ClassB.fun"); } + get funSerialized() { return this.funSerializedHandler; } }; """); } @@ -263,10 +297,10 @@ public void WhenNoSpaceBindingsAreAssignedToClassObject () Contains( """ export const Class = { - nya: () => getExports().Class_Nya(), + nya: () => exports.Class_Nya(), get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { return getImport(this.funHandler, this.funSerializedHandler, "Class.fun"); } + get funSerialized() { return this.funSerializedHandler; } }; """); } @@ -279,7 +313,7 @@ public void VariablesConflictingWithJSTypesAreRenamed () Contains( """ export const Class = { - fun: (fn) => getExports().Class_Fun(fn) + fun: (fn) => exports.Class_Fun(fn) }; """); } @@ -297,10 +331,10 @@ public void SerializesUserType () Contains( """ export const Class = { - foo: (i) => deserialize(getExports().Class_Foo(serialize(i, Info)), Info), + foo: (i) => deserialize(exports.Class_Foo(serialize(i, Info)), Info), get bar() { return this.barHandler; }, set bar(handler) { this.barHandler = handler; this.barSerializedHandler = (i) => serialize(this.barHandler(deserialize(i, Info)), Info); }, - get barSerialized() { return getImport(this.barHandler, this.barSerializedHandler, "Class.bar"); }, + get barSerialized() { return this.barSerializedHandler; }, baz: new Event(), bazSerialized: (i) => Class.baz.broadcast(deserialize(i, InfoArray) ?? undefined), yaz: new Event(), @@ -404,14 +438,14 @@ public void AwaitsWhenSerializingInAsyncFunctions () Contains( """ export const Class = { - foo: async (i) => deserialize(await getExports().Class_Foo(serialize(i, Info)), Info), + foo: async (i) => deserialize(await exports.Class_Foo(serialize(i, Info)), Info), get bar() { return this.barHandler; }, set bar(handler) { this.barHandler = handler; this.barSerializedHandler = async (i) => serialize(await this.barHandler(deserialize(i, Info)), Info); }, - get barSerialized() { return getImport(this.barHandler, this.barSerializedHandler, "Class.bar"); }, - baz: async () => deserialize(await getExports().Class_Baz(), System_Collections_Generic_IReadOnlyList_Of_Info), + get barSerialized() { return this.barSerializedHandler; }, + baz: async () => deserialize(await exports.Class_Baz(), System_Collections_Generic_IReadOnlyList_Of_Info), get yaz() { return this.yazHandler; }, set yaz(handler) { this.yazHandler = handler; this.yazSerializedHandler = async () => serialize(await this.yazHandler(), System_Collections_Generic_IReadOnlyList_Of_Info); }, - get yazSerialized() { return getImport(this.yazHandler, this.yazSerializedHandler, "Class.yaz"); } + get yazSerialized() { return this.yazSerializedHandler; } }; """); } @@ -427,7 +461,7 @@ public void ExportedEnumsAreDeclaredInJS () """ export const n = { Class: { - getFoo: () => deserialize(getExports().n_Class_GetFoo(), n_Foo), + getFoo: () => deserialize(exports.n_Class_GetFoo(), n_Foo), Foo: { "0": "A", "1": "B", "A": 0, "B": 1 } } }; @@ -460,7 +494,7 @@ public void CustomEnumIndexesArePreservedInJS () export const n = { Foo: { "1": "A", "6": "B", "A": 1, "B": 6 }, Class: { - getFoo: () => deserialize(getExports().n_Class_GetFoo(), n_Foo) + getFoo: () => deserialize(exports.n_Class_GetFoo(), n_Foo) } }; """); @@ -485,12 +519,12 @@ public void RespectsSpacePreference () Class: { get onFun() { return this.onFunHandler; }, set onFun(handler) { this.onFunHandler = handler; this.onFunSerializedHandler = () => this.onFunHandler(); }, - get onFunSerialized() { return getImport(this.onFunHandler, this.onFunSerializedHandler, "Fun.Class.onFun"); } + get onFunSerialized() { return this.onFunSerializedHandler; } } }; export const Nya = { Class: { - getNya: () => getExports().Foo_Bar_Nya_Class_GetNya() + getNya: () => exports.Foo_Bar_Nya_Class_GetNya() } }; """); @@ -508,7 +542,7 @@ public void RespectsFunctionPreference () """ export const Space = { Class: { - foo: () => getExports().Space_Class_Inv() + foo: () => exports.Space_Class_Inv() } }; """); @@ -548,12 +582,12 @@ public interface IImported { void Fun (string s, Enum e); void NotifyEvt (string export const Space = { Enum: { "0": "A", "1": "B", "A": 0, "B": 1 }, Exported: { - inv: (s, e) => getExports().Bootsharp_Generated_Exports_Space_JSExported_Inv(s, serialize(e, Space_Enum)) + inv: (s, e) => exports.Bootsharp_Generated_Exports_Space_JSExported_Inv(s, serialize(e, Space_Enum)) }, Imported: { get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = (s, e) => this.funHandler(s, deserialize(e, Space_Enum)); }, - get funSerialized() { return getImport(this.funHandler, this.funSerializedHandler, "Space.Imported.fun"); }, + get funSerialized() { return this.funSerializedHandler; }, onEvt: new Event(), onEvtSerialized: (s, e) => Space.Imported.onEvt.broadcast(s, deserialize(e, Space_Enum)) } @@ -581,10 +615,10 @@ public interface IImported { void Fun (string s, Enum e); void NotifyEvt (string Contains( """ export const Foo = { - inv: (s, e) => getExports().Bootsharp_Generated_Exports_Space_JSExported_Inv(s, serialize(e, Space_Enum)), + inv: (s, e) => exports.Bootsharp_Generated_Exports_Space_JSExported_Inv(s, serialize(e, Space_Enum)), get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = (s, e) => this.funHandler(s, deserialize(e, Space_Enum)); }, - get funSerialized() { return getImport(this.funHandler, this.funSerializedHandler, "Foo.fun"); }, + get funSerialized() { return this.funSerializedHandler; }, onEvt: new Event(), onEvtSerialized: (s, e) => Foo.onEvt.broadcast(s, deserialize(e, Space_Enum)), Enum: { "0": "A", "1": "B", "A": 0, "B": 1 } @@ -629,20 +663,20 @@ class JSExported { Contains( """ export const Class = { - getExported: async (inst) => new Space_JSExported(await getExports().Class_GetExported(registerInstance(inst))), + getExported: async (inst) => new Space_JSExported(await exports.Class_GetExported(registerInstance(inst))), get getImported() { return this.getImportedHandler; }, set getImported(handler) { this.getImportedHandler = handler; this.getImportedSerializedHandler = async (inst) => registerInstance(await this.getImportedHandler(new JSExported(inst))); }, - get getImportedSerialized() { return getImport(this.getImportedHandler, this.getImportedSerializedHandler, "Class.getImported"); } + get getImportedSerialized() { return this.getImportedSerializedHandler; } }; export const Exported = { - inv: (_id, str) => deserialize(getExports().Bootsharp_Generated_Exports_JSExported_Inv(_id, str), Enum) + inv: (_id, str) => deserialize(exports.Bootsharp_Generated_Exports_JSExported_Inv(_id, str), Enum) }; export const Imported = { onEvtSerialized: (_id, str) => getInstance(_id).onEvt.broadcast(str) }; export const Space = { Exported: { - inv: (_id, en) => getExports().Bootsharp_Generated_Exports_Space_JSExported_Inv(_id, serialize(en, Enum)) + inv: (_id, en) => exports.Bootsharp_Generated_Exports_Space_JSExported_Inv(_id, serialize(en, Enum)) }, Imported: { funSerialized: (_id, en) => serialize(getInstance(_id).fun(deserialize(en, Enum)), Enum) diff --git a/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs b/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs index b9b6d111..f52e73e2 100644 --- a/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs @@ -2,7 +2,7 @@ namespace Bootsharp.Publish; -internal sealed class BindingGenerator (Preferences prefs) +internal sealed class BindingGenerator (Preferences prefs, bool debug) { private record Binding (MethodMeta? Method, Type? Enum, string Namespace); @@ -35,6 +35,12 @@ public string Generate (SolutionInspection inspection) EmitImports(); builder.Append("\n\n"); + if (debug) + { + EmitDebugHelpers(); + builder.Append("\n\n"); + } + builder.Append(serdeGenerator.Generate(inspection.Serialized)); builder.Append('\n'); @@ -54,9 +60,30 @@ private void EmitImports () import { Event } from "./event"; import { registerInstance, getInstance, disposeOnFinalize } from "./instances"; import { serialize, deserialize, binary, types } from "./serialization"; + """ + ); + } - function getExports() { if (exports == null) throw Error("Boot the runtime before invoking C# APIs."); return exports; } - function getImport(handler, serializedHandler, name) { if (typeof handler !== "function") throw Error(`Failed to invoke '${name}' from C#. Make sure to assign the function in JavaScript.`); return serializedHandler; } + private void EmitDebugHelpers () + { + builder.Append( + """ + function getExport(name) { + return (...args) => { + if (exports == null) throw Error("Boot the runtime before invoking C# APIs."); + let result; + try { result = exports[name](...args); } + catch (error) { throw Error(`${error.message}\n${error.stack}`); } + if (typeof result?.then === "function") + return result.catch(error => { throw Error(`${error.message}\n${error.stack}`); }); + return result; + }; + } + + function getImport(handler, serializedHandler, name) { + if (typeof handler !== "function") throw Error(`Failed to invoke '${name}' from C#. Make sure to assign the function in JavaScript.`); + return serializedHandler; + } """ ); } @@ -123,7 +150,8 @@ private void EmitInvokable (MethodMeta method) { var instanced = IsInstanced(method); var wait = ShouldWait(method); - var endpoint = $"getExports().{method.Space.Replace('.', '_')}_{method.Name}"; + var fn = $"{method.Space.Replace('.', '_')}_{method.Name}"; + var endpoint = debug ? $"""getExport("{fn}")""" : $"exports.{fn}"; var funcArgs = string.Join(", ", method.Arguments.Select(a => a.JSName)); if (instanced) funcArgs = PrependInstanceIdArgName(funcArgs); var invArgs = string.Join(", ", method.Arguments.Select(BuildInvArg)); @@ -158,11 +186,11 @@ private void EmitFunction (MethodMeta method) if (instanced) builder.Append($"{Break()}{name}Serialized: {serdeHandler}"); else { - var set = $"{handler} = handler; this.{name}SerializedHandler = {serdeHandler};"; - var serde = $"return getImport({handler}, this.{name}SerializedHandler, \"{binding.Namespace}.{name}\");"; + var serde = $"this.{name}SerializedHandler"; + var serdeExp = debug ? $"getImport({handler}, {serde}, \"{binding.Namespace}.{name}\")" : serde; builder.Append($"{Break()}get {name}() {{ return {handler}; }}"); - builder.Append($"{Break()}set {name}(handler) {{ {set} }}"); - builder.Append($"{Break()}get {name}Serialized() {{ {serde} }}"); + builder.Append($"{Break()}set {name}(handler) {{ {handler} = handler; {serde} = {serdeHandler}; }}"); + builder.Append($"{Break()}get {name}Serialized() {{ return {serdeExp}; }}"); } string BuildInvArg (ArgumentMeta arg) diff --git a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs index 2e3ccb36..6db1da68 100644 --- a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs +++ b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs @@ -50,7 +50,7 @@ IEnumerable GetFiles () private void GenerateBindings (Preferences prefs, SolutionInspection inspection) { - var generator = new BindingGenerator(prefs); + var generator = new BindingGenerator(prefs, Debug); var content = generator.Generate(inspection); File.WriteAllText(Path.Combine(BuildDirectory, "bindings.g.js"), content); } diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index 530319c0..2c665fa9 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,7 +1,7 @@ - 0.8.0-alpha.66 + 0.8.0-alpha.69 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com From ba253ee9ade51189e142efbccb8acdb5baf06142 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 18 Apr 2026 18:38:57 +0300 Subject: [PATCH 9/9] etc --- src/js/src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/src/config.ts b/src/js/src/config.ts index c9d027ca..31ebbe05 100644 --- a/src/js/src/config.ts +++ b/src/js/src/config.ts @@ -61,7 +61,7 @@ export async function buildConfig(resources: BootResources, root?: string): Prom } async function resolveSymbols(res: BinaryResource): Promise { - // Due to a bug in .NET, symbols ignore 'buffer', so we have to fake an HTTP response. + // Use buffer similar to the other assets once https://github.com/dotnet/runtime/pull/127087 is merged. const txt = new TextDecoder("utf-8").decode(await resolveBuffer(res)); return { name: res.name,