From c9e5ff8b3c4f2d42aedf3f610faf28336de4d8d8 Mon Sep 17 00:00:00 2001 From: zenith391 <39484230+zenith391@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:35:21 +0200 Subject: [PATCH] wasm: Add support for extra JS functions This allows to extend the capabilities of capy.js with access to other DOM APIs which can be used by external libraries --- build_capy.zig | 31 ++++++++++++++++++++++++------- src/backends/wasm/capy-worker.js | 13 +++++++++++++ src/backends/wasm/capy.js | 18 ++++++++++++++++-- 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/build_capy.zig b/build_capy.zig index 549e21dc..f91ce474 100644 --- a/build_capy.zig +++ b/build_capy.zig @@ -8,6 +8,7 @@ pub const CapyBuildOptions = struct { // TODO: disable android build if password is not set // TODO: use optional android: AndroidOptions = .{ .password = "foo", .package_name = "org.capyui.example" }, + wasm: WasmOptions = .{}, args: ?[]const []const u8 = &.{}, pub const AndroidOptions = struct { @@ -20,6 +21,11 @@ pub const CapyBuildOptions = struct { password: []const u8, }; + pub const WasmOptions = struct { + extras_js_file: ?[]const u8 = null, + debug_requests: bool = true, + }; + pub const LinuxOptions = struct {}; }; @@ -27,8 +33,9 @@ pub const CapyBuildOptions = struct { const WebServerStep = struct { step: std.build.Step, exe: *std.build.CompileStep, + options: CapyBuildOptions.WasmOptions, - pub fn create(owner: *std.build.Builder, exe: *std.build.LibExeObjStep) *WebServerStep { + pub fn create(owner: *std.build.Builder, exe: *std.build.LibExeObjStep, options: CapyBuildOptions.WasmOptions) *WebServerStep { const self = owner.allocator.create(WebServerStep) catch unreachable; self.* = .{ .step = std.build.Step.init(.{ @@ -38,6 +45,7 @@ const WebServerStep = struct { .makeFn = WebServerStep.make, }), .exe = exe, + .options = options, }; return self; } @@ -69,7 +77,8 @@ const WebServerStep = struct { fn handler(self: *WebServerStep, build: *std.Build, res: *Server.Response) !void { const allocator = build.allocator; - const build_root = build.build_root.path orelse unreachable; + const prefix = comptime std.fs.path.dirname(@src().file).? ++ std.fs.path.sep_str; + while (true) { defer _ = res.reset(); try res.wait(); @@ -82,20 +91,27 @@ const WebServerStep = struct { var file_path: []const u8 = ""; var content_type: []const u8 = "text/html"; if (std.mem.eql(u8, path, "/")) { - file_path = try std.fs.path.join(req_allocator, &.{ build_root, "src/backends/wasm/index.html" }); + file_path = try std.fs.path.join(req_allocator, &.{ prefix, "src/backends/wasm/index.html" }); content_type = "text/html"; } else if (std.mem.eql(u8, path, "/capy.js")) { - file_path = try std.fs.path.join(req_allocator, &.{ build_root, "src/backends/wasm/capy.js" }); + file_path = try std.fs.path.join(req_allocator, &.{ prefix, "src/backends/wasm/capy.js" }); content_type = "application/javascript"; } else if (std.mem.eql(u8, path, "/capy-worker.js")) { - file_path = try std.fs.path.join(req_allocator, &.{ build_root, "src/backends/wasm/capy-worker.js" }); + file_path = try std.fs.path.join(req_allocator, &.{ prefix, "src/backends/wasm/capy-worker.js" }); content_type = "application/javascript"; } else if (std.mem.eql(u8, path, "/zig-app.wasm")) { file_path = self.exe.getOutputSource().getPath2(build, &self.step); content_type = "application/wasm"; + } else if (std.mem.eql(u8, path, "/extras.js")) { + if (self.options.extras_js_file) |extras_path| { + file_path = extras_path; + content_type = "application/javascript"; + } } - std.log.info("{s}", .{path}); + if (self.options.debug_requests) { + std.log.debug("{s} -> {s}", .{ path, file_path }); + } const file: ?std.fs.File = std.fs.cwd().openFile(file_path, .{ .mode = .read_only }) catch |err| blk: { switch (err) { error.FileNotFound => break :blk null, @@ -107,6 +123,7 @@ const WebServerStep = struct { defer f.close(); break :blk try f.readToEndAlloc(req_allocator, std.math.maxInt(usize)); } else { + res.status = .not_found; break :blk "404 Not Found"; } }; @@ -301,7 +318,7 @@ pub fn install(step: *std.Build.CompileStep, options: CapyBuildOptions) !*std.Bu step.export_symbol_names = &.{"_start"}; step.import_memory = true; - const serve = WebServerStep.create(b, step); + const serve = WebServerStep.create(b, step, options.wasm); const install_step = b.addInstallArtifact(step, .{}); serve.step.dependOn(&install_step.step); return &serve.step; diff --git a/src/backends/wasm/capy-worker.js b/src/backends/wasm/capy-worker.js index 9df8d7e3..bfc896f3 100644 --- a/src/backends/wasm/capy-worker.js +++ b/src/backends/wasm/capy-worker.js @@ -226,7 +226,20 @@ const env = { }, }; +async function loadExtras() { + const obj = await import("./extras.js"); + for (const key in obj.envWorker) { + env[key] = obj.envWorker[key]; + } +} + (async function() { + try { + await loadExtras(); + } catch (e) { + console.debug("No extras.js (worker)"); + } + const importObject = { env: env, }; diff --git a/src/backends/wasm/capy.js b/src/backends/wasm/capy.js index 43a308cd..0bad17c2 100644 --- a/src/backends/wasm/capy.js +++ b/src/backends/wasm/capy.js @@ -72,7 +72,8 @@ function readString(addr, len) { return utf8Decoder.decode(view.slice(addr, addr + len)); } -const env = { + +let env = { jsPrint: function(arg, len) { console.log(readString(arg, len)); }, @@ -171,6 +172,7 @@ const env = { domObjects[root].style.width = "100%"; domObjects[root].style.height = "100%"; rootElementId = root; + window.onresize(); // call resize handler atleast once, to setup layout }, setText: function(element, text) { const elem = domObjects[element]; @@ -370,11 +372,24 @@ const env = { }, }; +async function loadExtras() { + const obj = await import("./extras.js"); + for (const key in obj.env) { + env[key] = obj.env[key]; + } +} (async function() { if (!window.Worker) { alert("Capy requires Web Workers until Zig supports async"); } + + try { + await loadExtras(); + } catch (e) { + console.debug("No extras.js"); + } + const wasmWorker = new Worker("capy-worker.js"); wasmWorker.postMessage("test"); wasmWorker.onmessage = (e) => { @@ -440,5 +455,4 @@ const env = { window.onresize = function() { pushEvent({ type: 0, target: rootElementId }); }; - window.onresize(); // call resize handler atleast once, to setup layout })();