From f2b203175029e57fce2bf0ceeb5bf93e40cbbfca Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sun, 24 May 2026 00:40:18 +0300 Subject: [PATCH 1/4] implement --- .../GenerateJS/GenerateJSTest.cs | 3 +- .../GenerateJS/ResourceTest.cs | 19 ++++ .../GenerateJS/GenerateJS.cs | 3 +- .../GenerateJS/ResourceGenerator.cs | 86 ++++++++++++------- src/cs/Bootsharp/Build/Bootsharp.props | 4 +- src/cs/Bootsharp/Build/Bootsharp.targets | 13 +-- src/cs/Directory.Build.props | 2 +- src/js/package.json | 8 +- src/js/src/boot.mts | 12 +-- src/js/src/config.mts | 10 ++- src/js/src/generated/resources.g.mts | 5 +- src/js/src/instances.mts | 4 +- src/js/src/resources.mts | 18 ++-- src/js/src/serialization/writer.mts | 3 +- src/js/test/cs/Test/Test.csproj | 1 + src/js/test/spec/boot.spec.ts | 18 +++- 16 files changed, 138 insertions(+), 71 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/GenerateJS/GenerateJSTest.cs b/src/cs/Bootsharp.Publish.Test/GenerateJS/GenerateJSTest.cs index eaa33eaa..36d6d593 100644 --- a/src/cs/Bootsharp.Publish.Test/GenerateJS/GenerateJSTest.cs +++ b/src/cs/Bootsharp.Publish.Test/GenerateJS/GenerateJSTest.cs @@ -42,6 +42,7 @@ protected override void AddAssembly (string assemblyName, params MockSource[] so BuildEngine = Engine, Globalization = false, LLVM = false, - Debug = false + Debug = false, + Embed = false }; } diff --git a/src/cs/Bootsharp.Publish.Test/GenerateJS/ResourceTest.cs b/src/cs/Bootsharp.Publish.Test/GenerateJS/ResourceTest.cs index 956fb2dc..fd82327a 100644 --- a/src/cs/Bootsharp.Publish.Test/GenerateJS/ResourceTest.cs +++ b/src/cs/Bootsharp.Publish.Test/GenerateJS/ResourceTest.cs @@ -55,4 +55,23 @@ public void WhenGlobalizationDisabledIcuNotIncluded () Execute(); DoesNotContain("icudt.dat"); } + + [Fact] + public void WhenEmbedEnabledBinaryContentEmbeddedAsBase64 () + { + Task.Embed = true; + Task.Globalization = true; + Task.Debug = true; + AddAssembly("Foo.dll"); + Project.WriteFile("Foo.wasm", "MockFooContent"u8); + Project.WriteFile("icudt.dat", "MockIcuContent"u8); + Project.WriteFile("Foo.pdb", "MockPdbContent"u8); + Project.WriteFile("dotnet.native.js.symbols", "MockSymbolsContent"u8); + Execute(); + Contains($"wasm: \"{Convert.ToBase64String(MockWasmBinary)}\""); + Contains($"{{ name: \"Foo.wasm\", content: \"{Convert.ToBase64String("MockFooContent"u8)}\" }}"); + Contains($"{{ name: \"icudt.dat\", content: \"{Convert.ToBase64String("MockIcuContent"u8)}\" }}"); + Contains($"{{ name: \"Foo.pdb\", content: \"{Convert.ToBase64String("MockPdbContent"u8)}\" }}"); + Contains($"{{ name: \"dotnet.native.js.symbols\", content: \"{Convert.ToBase64String("MockSymbolsContent"u8)}\" }}"); + } } diff --git a/src/cs/Bootsharp.Publish/GenerateJS/GenerateJS.cs b/src/cs/Bootsharp.Publish/GenerateJS/GenerateJS.cs index 849458a8..03b3709a 100644 --- a/src/cs/Bootsharp.Publish/GenerateJS/GenerateJS.cs +++ b/src/cs/Bootsharp.Publish/GenerateJS/GenerateJS.cs @@ -12,6 +12,7 @@ public sealed class GenerateJS : Microsoft.Build.Utilities.Task public required bool Globalization { get; set; } public required bool LLVM { get; set; } public required bool Debug { get; set; } + public required bool Embed { get; set; } public override bool Execute () { @@ -83,7 +84,7 @@ private void GenerateDeclarations (SolutionInspection spec, JSModules mds) private void GenerateResources (SolutionInspection spec) { - var generator = new ResourceGenerator(EntryAssemblyName, Debug, Globalization); + var generator = new ResourceGenerator(EntryAssemblyName, Debug, Globalization, Embed); var content = generator.Generate(BuildDirectory, DebugDirectory); WriteGenerated("resources.g.mjs", content); } diff --git a/src/cs/Bootsharp.Publish/GenerateJS/ResourceGenerator.cs b/src/cs/Bootsharp.Publish/GenerateJS/ResourceGenerator.cs index c368dc0a..13231c91 100644 --- a/src/cs/Bootsharp.Publish/GenerateJS/ResourceGenerator.cs +++ b/src/cs/Bootsharp.Publish/GenerateJS/ResourceGenerator.cs @@ -1,9 +1,10 @@ namespace Bootsharp.Publish; /// -/// Generates a manifest listing resources required to initialize the .NET runtime. +/// Generates a manifest listing resources required to initialize the .NET runtime, +/// optionally embedding the binary content as base64 strings. /// -internal sealed class ResourceGenerator (string entryAssemblyName, bool debug, bool g11n) +internal sealed class ResourceGenerator (string entryAssemblyName, bool debug, bool g11n, bool embed) { private readonly List assemblies = []; private readonly List symbols = []; @@ -14,43 +15,64 @@ internal sealed class ResourceGenerator (string entryAssemblyName, bool debug, b public string Generate (string buildDir, string debugDir) { foreach (var path in Directory.GetFiles(buildDir, "*.wasm").Order()) - if (path.EndsWith("dotnet.native.wasm")) wasm = BuildResourceName(path); - else assemblies.Add(BuildResourceName(path)); + if (path.EndsWith("dotnet.native.wasm")) wasm = Path.GetFileName(path); + else assemblies.Add(Path.GetFileName(path)); if (g11n) - { foreach (var path in Directory.GetFiles(buildDir, "*.dat").Order()) - icu.Add(BuildResourceName(path)); - } + icu.Add(Path.GetFileName(path)); if (debug) { foreach (var path in Directory.GetFiles(debugDir, "*.symbols").Order()) - symbols.Add(BuildResourceName(path)); + symbols.Add(Path.GetFileName(path)); foreach (var path in Directory.GetFiles(debugDir, "*.pdb").Order()) - pdb.Add(BuildResourceName(path)); + pdb.Add(Path.GetFileName(path)); } - return - $$""" - export default { - wasm: {{wasm}}, - assemblies: [ - {{Fmt(assemblies, 2, ",\n")}} - ], - icu: [ - {{Fmt(icu, 2, ",\n")}} - ], - symbols: [ - {{Fmt(symbols, 2, ",\n")}} - ], - pdb: [ - {{Fmt(pdb, 2, ",\n")}} - ], - entryAssemblyName: "{{entryAssemblyName}}" - }; - """; + return $"{GenerateManifest()}\n\n{GenerateEmbedded(buildDir, debugDir)}"; } - private string BuildResourceName (string path) - { - return $"\"{Path.GetFileName(path)}\""; - } + private string GenerateManifest () => + $$""" + export const manifest = { + wasm: "{{wasm}}", + assemblies: [ + {{FmtNames(assemblies)}} + ], + icu: [ + {{FmtNames(icu)}} + ], + symbols: [ + {{FmtNames(symbols)}} + ], + pdb: [ + {{FmtNames(pdb)}} + ], + entryAssemblyName: "{{entryAssemblyName}}" + }; + """; + + private string GenerateEmbedded (string buildDir, string debugDir) => embed ? + $$""" + export const embedded = { + wasm: "{{ReadBase64(buildDir, wasm)}}", + assemblies: [ + {{FmtBins(buildDir, assemblies)}} + ], + icu: [ + {{FmtBins(buildDir, icu)}} + ], + symbols: [ + {{FmtBins(debugDir, symbols)}} + ], + pdb: [ + {{FmtBins(debugDir, pdb)}} + ] + }; + """ : "export const embedded = undefined;"; + + private static string FmtNames (IEnumerable names) => + Fmt(names.Select(n => $"\"{n}\""), 2, ",\n"); + private static string FmtBins (string dir, IEnumerable names) => + Fmt(names.Select(n => $"{{ name: \"{n}\", content: \"{ReadBase64(dir, n)}\" }}"), 2, ",\n"); + private static string ReadBase64 (string dir, string name) => + Convert.ToBase64String(File.ReadAllBytes(Path.Combine(dir, name))); } diff --git a/src/cs/Bootsharp/Build/Bootsharp.props b/src/cs/Bootsharp/Build/Bootsharp.props index 5ffb38cc..9dd13658 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.props +++ b/src/cs/Bootsharp/Build/Bootsharp.props @@ -17,10 +17,10 @@ bootsharp + + - - diff --git a/src/cs/Bootsharp/Build/Bootsharp.targets b/src/cs/Bootsharp/Build/Bootsharp.targets index 7db46e8f..3ab2735d 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.targets +++ b/src/cs/Bootsharp/Build/Bootsharp.targets @@ -13,6 +13,7 @@ $(AssemblyName).dll CopyNativeBinary WasmNestedPublishApp + $([System.String]::IsNullOrEmpty('$(BootsharpBinariesDirectory)')) @@ -90,7 +91,6 @@ $(WasmAppDir)/$(WasmRuntimeAssetsLocation) $(BaseOutputPath.Replace('\', '/')) $(BsBaseOutputPath)$(BootsharpName) - $(BootsharpPublishDirectory)/bin $(MSBuildProjectDirectory) $([System.String]::Equals('$(InvariantGlobalization)', 'false')) @@ -122,7 +122,8 @@ EntryAssemblyName="$(BsEntryAssembly)" Globalization="$(BsGlobalization)" LLVM="$(BsLlvm)" - Debug="$(BsDebug)"/> + Debug="$(BsDebug)" + Embed="$(BsEmbed)"/> @@ -137,8 +138,8 @@ - - + + @@ -152,8 +153,8 @@ - - + + diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index 412582df..634e1dcb 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,7 +1,7 @@ - 0.8.0-alpha.353 + 0.8.0-alpha.361 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com diff --git a/src/js/package.json b/src/js/package.json index 63c129c2..75700894 100644 --- a/src/js/package.json +++ b/src/js/package.json @@ -7,10 +7,10 @@ }, "devDependencies": { "typescript": "6.0.3", - "@types/node": "25.6.2", + "@types/node": "25.9.1", "@types/ws": "8.18.1", - "vitest": "4.1.5", - "@vitest/coverage-v8": "4.1.5", - "ws": "8.20.0" + "vitest": "4.1.7", + "@vitest/coverage-v8": "4.1.7", + "ws": "8.21.0" } } diff --git a/src/js/src/boot.mts b/src/js/src/boot.mts index 55ca497d..0299e3d6 100644 --- a/src/js/src/boot.mts +++ b/src/js/src/boot.mts @@ -36,11 +36,11 @@ export function getStatus(): BootStatus { return status; } -/** Initializes the runtime and binds C# APIs. +/** Initializes the runtime. When not in embedded mode, resources parameter has to be specified. * @param resources Either URL to the boot resources root (eg, /bin) or the preloaded content. * @param options Allows customizing the boot process and the runtime behaviour. * @return Promise that resolves into the runtime instance when the initialization is finished. */ -export async function boot(resources: string | BootResources, options?: BootOptions): Promise { +export async function boot(resources?: string | BootResources, options?: BootOptions): Promise { if (status === BootStatus.Booted) throw Error("Failed to boot the C# runtime: already booted."); if (status === BootStatus.Booting) throw Error("Failed to boot the C# runtime: already booting."); status = BootStatus.Booting; @@ -53,16 +53,16 @@ export async function boot(resources: string | BootResources, options?: BootOpti * @param code Exit code; will use 0 (normal exit) by default. * @param reason Exit reason description (optional). */ export async function exit(code?: number, reason?: string): Promise { - /* v8 ignore start -- @preserve */ // Uncoverable, as exit terminates the host test process. + /* v8 ignore start -- uncoverable, as exit terminates the host test process */ if (status !== BootStatus.Booted) throw Error("Failed to exit the C# runtime: not booted."); try { app.exit(code ?? 0, reason); } catch { } finally { status = BootStatus.Standby; } - /* v8 ignore stop -- @preserve */ + /* v8 ignore stop */ } -async function createRuntime(res: string | BootResources, opt: BootOptions) { - const cfg = opt.config ?? buildConfig(typeof res === "string" ? await fetchResources(res) : res); +async function createRuntime(res: string | BootResources | undefined, opt: BootOptions) { + const cfg = opt.config ?? buildConfig(typeof res === "object" ? res : await fetchResources(res)); const runtime = await opt.create?.(cfg) || await app.dotnet.withConfig(cfg).create(); setRuntime(runtime); if (opt.import) await opt.import(runtime); else bindImports(runtime); diff --git a/src/js/src/config.mts b/src/js/src/config.mts index a3ac1f77..2f0fbb8c 100644 --- a/src/js/src/config.mts +++ b/src/js/src/config.mts @@ -25,12 +25,13 @@ export function buildConfig(resources: BootResources): RuntimeConfig { } function resolveAsset(res: BinaryResource): T { - return { name: res.name, virtualPath: res.name, buffer: res.content } as Asset as T; + const buffer = resolveBinary(res.content); + return { name: res.name, virtualPath: res.name, buffer } as Asset as T; } function resolveSymbols(res: BinaryResource): SymbolsAsset { // Use 'resolveAsset()' once https://github.com/dotnet/runtime/pull/127087 is merged. - const txt = new TextDecoder("utf-8").decode(res.content); + const txt = new TextDecoder("utf-8").decode(resolveBinary(res.content)); return { name: res.name, pendingDownload: { @@ -46,4 +47,9 @@ export function buildConfig(resources: BootResources): RuntimeConfig { if (resources.icu.some(res => res.name === "icudt.dat")) return "all" as never; return "sharded" as never; } + + function resolveBinary(content: ArrayBuffer | string): ArrayBuffer { + if (typeof content !== "string") return content; + return Uint8Array.fromBase64(content).buffer; + } } diff --git a/src/js/src/generated/resources.g.mts b/src/js/src/generated/resources.g.mts index e03f5467..17b372fd 100644 --- a/src/js/src/generated/resources.g.mts +++ b/src/js/src/generated/resources.g.mts @@ -1,3 +1,4 @@ -import { BootManifest } from "../resources.mjs"; +import { BootManifest, BootResources } from "../resources.mjs"; -export default {} as BootManifest; +export const manifest = {} as BootManifest; +export const embedded: BootResources | undefined = undefined; diff --git a/src/js/src/instances.mts b/src/js/src/instances.mts index 8da30a71..8dda024d 100644 --- a/src/js/src/instances.mts +++ b/src/js/src/instances.mts @@ -51,9 +51,9 @@ export const instances = { } }; -/* v8 ignore start -- @preserve */ // Uncoverable, as finalization in Node is not controllable. +/* v8 ignore start -- uncoverable, as finalization in Node is not controllable */ function finalizeExported(id: number) { exportedById.delete(id); (exports as { disposeExported: (id: number) => void }).disposeExported(id); } -/* v8 ignore stop -- @preserve */ +/* v8 ignore stop */ diff --git a/src/js/src/resources.mts b/src/js/src/resources.mts index e01277e1..0bcc4e9b 100644 --- a/src/js/src/resources.mts +++ b/src/js/src/resources.mts @@ -1,4 +1,4 @@ -import generated from "./generated/resources.g.mjs"; +import * as generated from "./generated/resources.g.mjs"; /** Lists resource file names (including extension) required to boot the runtime. */ export type BootManifest = Readonly<{ @@ -18,8 +18,8 @@ export type BootManifest = Readonly<{ /** Resources required to boot the runtime. */ export type BootResources = Readonly<{ - /** Binary content of the compiled WASM runtime module. */ - wasm: ArrayBuffer; + /** Compiled WASM content: either raw bytes or base64 encoded string. */ + wasm: ArrayBuffer | string; /** Compiled runtime assemblies. */ assemblies?: BinaryResource[]; /** Globalization data. */ @@ -34,15 +34,17 @@ export type BootResources = Readonly<{ export type BinaryResource = Readonly<{ /** Name of the file, including extension. */ name: string; - /** Binary content of the file. */ - content: ArrayBuffer; + /** Binary content of the file: either raw bytes or base64 encoded string. */ + content: ArrayBuffer | string; }>; /** Lists resource names required to boot the runtime. */ -export const manifest: BootManifest = generated; +export const manifest: BootManifest = generated.manifest; -/** Fetches required boot resources from the specified root URL. */ -export async function fetchResources(root: string): Promise { +/** Fetches resources from the specified root URL or from the embedded resources when in embedded mode. */ +export async function fetchResources(root?: string): Promise { + /* v8 ignore next -- embedded mode is covered in samples */ + if (generated.embedded != null) return generated.embedded; const [wasm, assemblies, icu, symbols, pdb] = await Promise.all([ fetchResource(manifest.wasm), Promise.all(manifest.assemblies.map(fetchResource)), diff --git a/src/js/src/serialization/writer.mts b/src/js/src/serialization/writer.mts index 95bdd1ee..793b28c4 100644 --- a/src/js/src/serialization/writer.mts +++ b/src/js/src/serialization/writer.mts @@ -131,9 +131,8 @@ export class Writer { private refreshHeapView(): void { const heap = getHeap(); - /* v8 ignore start -- @preserve */ // Uncoverable, as WASM heap growth is not controllable. + /* v8 ignore next -- uncoverable, as WASM heap growth is not controllable */ if (this.heap === heap) return; - /* v8 ignore stop -- @preserve */ this.heap = heap; this.view = new DataView(heap.buffer, heap.byteOffset); } diff --git a/src/js/test/cs/Test/Test.csproj b/src/js/test/cs/Test/Test.csproj index a402c60b..d4503809 100644 --- a/src/js/test/cs/Test/Test.csproj +++ b/src/js/test/cs/Test/Test.csproj @@ -4,6 +4,7 @@ net10.0 enable browser-wasm + bin/bootsharp/bin true true bin/codegen diff --git a/src/js/test/spec/boot.spec.ts b/src/js/test/spec/boot.spec.ts index a2c6f80a..07c2b315 100644 --- a/src/js/test/spec/boot.spec.ts +++ b/src/js/test/spec/boot.spec.ts @@ -105,12 +105,12 @@ describe("boot", () => { }], wasmNative: [{ name: "dotnet.native.wasm", - buffer: resources.wasm + buffer: resources.wasm as ArrayBuffer }], assembly: resources.assemblies?.map(a => ({ name: a.name, virtualPath: a.name, - buffer: a.content! + buffer: a.content as ArrayBuffer })) } }, @@ -125,6 +125,20 @@ describe("boot", () => { expect(customs.run).toHaveBeenCalledOnce(); expect(customs.export).toHaveBeenCalledOnce(); }); + it("can boot with base64 resources", async () => { + const { bootsharp, resources, Program } = await setup(); + const encode = (buf: ArrayBuffer) => Buffer.from(buf).toString("base64"); + const encodeAll = (bins: typeof resources.assemblies) => + bins?.map(b => ({ name: b.name, content: encode(b.content as ArrayBuffer) })); + await bootsharp.boot({ + wasm: encode(resources.wasm as ArrayBuffer), + assemblies: encodeAll(resources.assemblies), + icu: encodeAll(resources.icu), + symbols: encodeAll(resources.symbols), + pdb: encodeAll(resources.pdb) + }); + expect(Program.onMainInvoked).toHaveBeenCalledOnce(); + }); it("can boot when program has no exports", async () => { const { bootsharp, resources } = await setup(); const options: BootOptions = { From e890e2d8b19b78d5d27a6a9c48fd365fd164786f Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sun, 24 May 2026 02:05:22 +0300 Subject: [PATCH 2/4] use embed in samples --- samples/bench/bootsharp/init.mjs | 2 +- samples/minimal/README.md | 2 +- samples/minimal/cs/package.json | 2 +- samples/minimal/index.html | 2 +- samples/minimal/main.mjs | 2 +- samples/trimming/cs/Trimming.csproj | 2 ++ samples/trimming/cs/package.json | 2 +- 7 files changed, 8 insertions(+), 6 deletions(-) diff --git a/samples/bench/bootsharp/init.mjs b/samples/bench/bootsharp/init.mjs index f4ada4ce..2d5ecfe4 100644 --- a/samples/bench/bootsharp/init.mjs +++ b/samples/bench/bootsharp/init.mjs @@ -6,7 +6,7 @@ export async function init() { IImported.getNumber = getNumber; IImported.getStruct = getStruct; - await bootsharp.boot(import.meta.resolve("./bin/bootsharp/bin")); + await bootsharp.boot(); return { ...IExported }; } diff --git a/samples/minimal/README.md b/samples/minimal/README.md index ccadab74..e4ca0f95 100644 --- a/samples/minimal/README.md +++ b/samples/minimal/README.md @@ -1,7 +1,7 @@ Minimal example on using Bootsharp in web browsers and popular JavaScript runtimes. - Run `dotnet publish cs`; -- Run `node main.mjs` to test in [Node](https://nodejs.org) (blocker: https://github.com/elringus/bootsharp/pull/203); +- Run `node main.mjs` to test in [Node](https://nodejs.org); - Run `deno run main.mjs` to test in [Deno](https://deno.com); - Run `bun main.mjs` to test in [Bun](https://bun.sh); - Run an HTML server (eg, `npx serve`) to test in browser. diff --git a/samples/minimal/cs/package.json b/samples/minimal/cs/package.json index 92794570..4d5f0987 100644 --- a/samples/minimal/cs/package.json +++ b/samples/minimal/cs/package.json @@ -3,7 +3,7 @@ "type": "module", "exports": { ".": "./bin/bootsharp/index.mjs", - "./*": "./bin/bootsharp/generated/*.g.mjs" + "./*": "./bin/bootsharp/generated/modules/*.g.mjs" }, "browser": { "node:fs": false, diff --git a/samples/minimal/index.html b/samples/minimal/index.html index d37f3af8..072a237d 100644 --- a/samples/minimal/index.html +++ b/samples/minimal/index.html @@ -14,7 +14,7 @@

Loading...

Program.onMainInvoked.subscribe(console.log); // Initializing dotnet runtime and invoking entry point. -await bootsharp.boot("/cs/bin/bootsharp/bin"); +await bootsharp.boot(); // Invoking 'Program.GetBackendName' C# method. document.getElementById("msg").innerHTML = `Hello ${Program.getBackendName()}!`; diff --git a/samples/minimal/main.mjs b/samples/minimal/main.mjs index 5c56a5b8..9d597e41 100644 --- a/samples/minimal/main.mjs +++ b/samples/minimal/main.mjs @@ -12,7 +12,7 @@ Program.getFrontendName = () => Program.onMainInvoked.subscribe(console.log); // Initializing dotnet runtime and invoking entry point. -await bootsharp.boot(import.meta.resolve("./cs/bin/bootsharp/bin")); +await bootsharp.boot(); // Invoking 'Program.GetBackendName' C# method. console.log(`Hello ${Program.getBackendName()}!`); diff --git a/samples/trimming/cs/Trimming.csproj b/samples/trimming/cs/Trimming.csproj index 17a4c421..09115d92 100644 --- a/samples/trimming/cs/Trimming.csproj +++ b/samples/trimming/cs/Trimming.csproj @@ -3,6 +3,8 @@ net10.0 browser-wasm + + bin/bootsharp/bin diff --git a/samples/trimming/cs/package.json b/samples/trimming/cs/package.json index 92794570..4d5f0987 100644 --- a/samples/trimming/cs/package.json +++ b/samples/trimming/cs/package.json @@ -3,7 +3,7 @@ "type": "module", "exports": { ".": "./bin/bootsharp/index.mjs", - "./*": "./bin/bootsharp/generated/*.g.mjs" + "./*": "./bin/bootsharp/generated/modules/*.g.mjs" }, "browser": { "node:fs": false, From a637928cd2bed66d66a94e2f79a778f2b3dc0759 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sun, 24 May 2026 02:12:38 +0300 Subject: [PATCH 3/4] update docs --- docs/.vitepress/config.ts | 1 + docs/guide/build-config.md | 12 ++++++------ docs/guide/getting-started.md | 2 +- docs/guide/index.md | 2 +- docs/guide/sideloading.md | 35 +++++++++++++++++++++++++++++++++++ 5 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 docs/guide/sideloading.md diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 3965862c..246560ba 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -60,6 +60,7 @@ export default defineConfig({ { text: "Interop Instances", link: "/guide/interop-instances" }, { text: "Preferences", link: "/guide/preferences" }, { text: "Build Configuration", link: "/guide/build-config" }, + { text: "Sideloading", link: "/guide/sideloading" }, { text: "NativeAOT-LLVM", link: "/guide/llvm" } ] }, diff --git a/docs/guide/build-config.md b/docs/guide/build-config.md index 222dfb79..12e1f758 100644 --- a/docs/guide/build-config.md +++ b/docs/guide/build-config.md @@ -2,12 +2,12 @@ Build and publish related options are configured in `.csproj` file via MSBuild properties. -| Property | Default | Description | -|----------------------------|------------------|---------------------------------------------------| -| BootsharpName | bootsharp | Name of the generated JavaScript module. | -| BootsharpPublishDirectory | /bin/bootsharp | Directory to publish generated JavaScript module. | -| BootsharpBinariesDirectory | publish-dir/bin | Directory to publish binaries. | -| BootsharpPackageDirectory | project-dir | Directory to publish `package.json` file. | +| Property | Default | Description | +|----------------------------|------------------|-----------------------------------------------------------------------------------| +| BootsharpName | bootsharp | Name of the generated JavaScript module. | +| BootsharpPublishDirectory | /bin/bootsharp | Directory to publish generated JavaScript module. | +| BootsharpBinariesDirectory | (empty) | Directory to publish binaries; when empty, binaries are embedded (see [Sideloading](sideloading)). | +| BootsharpPackageDirectory | project-dir | Directory to publish `package.json` file. | Below is an example configuration, which will make Bootsharp name the compiled module "backend" (instead of the default "bootsharp"), publish the `package.json` under the solution directory root and emit the runtime binaries into a "public/bin" directory one level above the solution root: diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index f87d1ba6..e399fdc1 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -104,7 +104,7 @@ console.log(`Hello ${Program.getBackendName()}!`); Program.onMainInvoked.subscribe(console.log); // Initializing dotnet runtime and invoking entry point. - await bootsharp.boot("/bin"); + await bootsharp.boot(); // Invoking 'Program.GetBackendName' C# method. console.log(`Hello ${Program.getBackendName()}!`); diff --git a/docs/guide/index.md b/docs/guide/index.md index f47e01e3..eadbbdd1 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -43,7 +43,7 @@ Bootsharp will automatically build and bundle the JavaScript package when publis import bootsharp, { Backend, Frontend } from "backend"; // Boot C# WASM module. -await bootsharp.boot("/bin"); +await bootsharp.boot(); // Subscribe to C# event. Frontend.onUserChanged.subscribe(updateUserUI); diff --git a/docs/guide/sideloading.md b/docs/guide/sideloading.md new file mode 100644 index 00000000..226775f1 --- /dev/null +++ b/docs/guide/sideloading.md @@ -0,0 +1,35 @@ +# Sideloading Binaries + +By default, Bootsharp embeds the .NET WASM runtime and the solution's assemblies into the generated JavaScript module as base64 strings. This is convenient — `bootsharp.boot()` works with no arguments and no extra files to serve — and required in some environments (eg, VS Code web extensions). The trade-off is roughly 30% extra bundle size due to the base64 encoding. + +To disable embedding, set `BootsharpBinariesDirectory` to the directory where the binaries should be published: + +```xml + + bin/$(BootsharpName)/bin + +``` + +The `dotnet.native.wasm`, solution assemblies, ICU data and (in debug builds) debug symbols will be emitted to that directory as separate files instead of being inlined into the module. You then have two ways to feed them to `boot`: + +Pass a root URL to fetch the resources from at runtime: + +```ts +// Assuming the binaries are served from "/bin" under the website root. +await bootsharp.boot("/bin"); +``` + +Or load the binaries yourself and pass them as a `BootResources` object: + +```ts +import { readFileSync } from "node:fs"; + +const wasm = readFileSync("bin/dotnet.native.wasm"); +await bootsharp.boot({ wasm }); +``` + +This way the binary files can be streamed directly from the server, cached separately, or loaded from any source you control — useful for trimming initial bundle size or sharing the runtime across multiple modules. + +::: tip EXAMPLE +Find sideloading example in the [minimal sample](https://github.com/elringus/bootsharp/blob/main/samples/minimal). +::: From 58560ffa9620f58b70cd17e2b8ef83021a812e53 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sun, 24 May 2026 02:20:14 +0300 Subject: [PATCH 4/4] fix ci --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 63a7870b..ca670583 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: dotnet-version: 10 - uses: actions/setup-node@v6 with: - node-version: 24 + node-version: 26 - name: cover run: | cd src/js