-
-
Notifications
You must be signed in to change notification settings - Fork 59
Description
I am working on a smooth vite-based "hot-reload" + build experience for an ElementaryUI starter template. I have it working well now, but the topic of how to bootstrap the javascript parts is still messy. Currently, the only way to get to the required JavaScriptKit runtime imports reliably is by building through the js plugin. I think the js command plugin is a great choice for many cases, but it has issues.
For my "web app in the browser" use case:
- debug rebuild on file-change takes a lot longer (on my machine ~2 s vs ~0.9 s "raw" swift build - most of it probably just SwiftPM waking up)
- the js plugin simply checks a of things that I do not need every time
- the wasi-shim dependency is in a weird place (cdn is no good for "real" stuff, and depending on a local package that does not exist until after build is also weird)
- I don't love that I need a matching top-level JavaScriptKit dependency (especially since we are still in 0.x.x versions)
For all these reasons I decided to base my "swift wasm" vite plugin on "raw" SwiftPM build commands. With the SwiftWasm SDKs the build is really nothing I need a plugin for (only the linker bits for exec-model=reactor and main export are a bit rough, but very doable).
I currently see two and a half issues:
- How do I get the javascript runtime reliably?
- How do I deal with the bridge-js imports reliably?
- How to best deal with the WASI-shim setup (not a blocker really)
Here is my annotated WIP-version of the index.ts that would bootstrap an ElementaryUI app:
// index.ts
import { WASI, OpenFile, File, ConsoleStdout } from "@bjorn3/browser_wasi_shim";
// PROBLEM: typing bug makes this non-compatible with esbuild/rolldown
// - "export { SwiftRuntimeThreadChannel };",
// + "export type { SwiftRuntimeThreadChannel };"
import { SwiftRuntime } from "./.build/checkouts/JavaScriptKit/Runtime/src/index.ts";
// PROBLEM: these bjs imports live nowhere, only in the js plugin templates
import { bjs } from "./javascriptkit.ts";
import appInit from "./Package.swift"; // this is the cool part that the vite plugin does
// JavaScriptKit Swift runtime
const swiftRuntime = new SwiftRuntime();
// HALF-PROLEM: do we ever need to customize this, or should we just vendor a pre-configured version of this?
// Basic WASI setup
const wasi = new WASI(
[],
[],
[
new OpenFile(new File([])),
ConsoleStdout.lineBuffered(console.log),
ConsoleStdout.lineBuffered(console.error),
],
{ debug: false }
);
// Vite wasm init import (https://vite.dev/guide/features#webassembly)
const instance = await appInit({
javascript_kit: swiftRuntime.wasmImports,
wasi_snapshot_preview1: wasi.wasiImport,
bjs,
});
swiftRuntime.setInstance(instance);
wasi.initialize(instance);
// Run Swift's main entry point
swiftRuntime.main();// javascriptkit.ts
const unexpectedBjsCall = () => {
throw new Error("Unexpected call to BridgeJS function");
};
export const bjs = {
swift_js_return_string: unexpectedBjsCall,
swift_js_init_memory: unexpectedBjsCall,
swift_js_make_js_string: unexpectedBjsCall,
swift_js_init_memory_with_result: unexpectedBjsCall,
swift_js_throw: unexpectedBjsCall,
swift_js_retain: unexpectedBjsCall,
swift_js_release: unexpectedBjsCall,
swift_js_push_tag: unexpectedBjsCall,
swift_js_push_int: unexpectedBjsCall,
swift_js_push_f32: unexpectedBjsCall,
swift_js_push_f64: unexpectedBjsCall,
swift_js_push_string: unexpectedBjsCall,
swift_js_pop_param_int32: unexpectedBjsCall,
swift_js_pop_param_f32: unexpectedBjsCall,
swift_js_pop_param_f64: unexpectedBjsCall,
swift_js_return_optional_bool: unexpectedBjsCall,
swift_js_return_optional_int: unexpectedBjsCall,
swift_js_return_optional_string: unexpectedBjsCall,
swift_js_return_optional_double: unexpectedBjsCall,
swift_js_return_optional_float: unexpectedBjsCall,
swift_js_return_optional_heap_object: unexpectedBjsCall,
swift_js_return_optional_object: unexpectedBjsCall,
swift_js_get_optional_int_presence: unexpectedBjsCall,
swift_js_get_optional_int_value: unexpectedBjsCall,
swift_js_get_optional_string: unexpectedBjsCall,
swift_js_get_optional_float_presence: unexpectedBjsCall,
swift_js_get_optional_float_value: unexpectedBjsCall,
swift_js_get_optional_double_presence: unexpectedBjsCall,
swift_js_get_optional_double_value: unexpectedBjsCall,
swift_js_get_optional_heap_object_pointer: unexpectedBjsCall,
};I hope this illustrates my point of view.
One option could be to add a command plugin to generate the javascript bits, but not entangle build + generate + wasm-opt + all-the-things in one command. However, this plugin would add maintenance burden for things that any web-bundling tool already does better and faster. So I am not convinced...
One "fix" for now would be to
- fix the typing issues in the ts runtime code
- add a "makeEmptyBJSExport" function or similar to the runtime so it can be used outside of the JS plugin
- make a "promise" that the runtime stays under
Runtime/src/index.tsand is considered API(ish)
I am happy to provide a PR for this, but I wanted to discuss this more broadly first.