rustwasm / wasm-bindgen

Facilitating high-level interactions between Wasm modules and JavaScript
https://rustwasm.github.io/docs/wasm-bindgen/
Apache License 2.0
7.86k stars 1.08k forks source link

Dependent module support #3019

Open lukaslihotzki opened 2 years ago

lukaslihotzki commented 2 years ago

Motivation

Rust crates should be able to abstract worker and worklet handling without requiring extra effort by the application developer. For worker and worklet creation, a URL pointing to the worker module is needed. This worker module typically contains custom code supplied by the Rust crate that creates the corresponding worker. If the worker needs to execute the Rust code of the application, the worker module needs to import the main module generated by wasm_bindgen, so the WASM imports are available. Dependent module support would allow Rust crates to put their worker modules into the final package during the build process.

Proposed Solution

The macro wasm_bindgen::dependent_module! takes a local file name to a JS module as input. Additionally, the macro generates an import statement to the main module currently generated by wasm_bindgen code. This import statement is then prepended to the module code. The resulting file is put in the output directory during the build process, similarly to snippets. In contrast to snippets, these modules are not imported by the main module. Instead, the macro resolves to code that returns the URL string to the corresponding file during run time. The macro caller can then use this URL string to create workers or worklets.

Alternatives

Dependent module URLs can also be built at run time by querying the main module URL (for example, with import.meta.url), and then creating a blob or data URL with the import statement and constant code concatenated. The proposed wasm-audio-worklet example contains a concise implementation of this approach that only works with --target web. The wasm-mt and wasm_thread crates use similar, but more complex techniques that also work with --target no-modules by manually fetching the source code of the main module, so no import statements are needed.

Generating dependent modules at run time is the inferior solution, because it is opaque to wasm_bindgen. For example, Rust crates using wasm_bindgen::dependent_module! don't need to care about the different targets of wasm_bindgen. Also, build time bundlers can optimize or pack dependent modules. Additionally, run-time JS code generation introduces potential security vulnerabilities (like eval), so it should be avoided. In contrast, build-time dependent modules can even be used with restrictive CSP headers.

Additional Context

Packing is currently needed for worklets in Firefox, because import statements are currently broken there. As mentioned before, this problem can be solved by manually fetching code. However, the main module can also contain imports, so you would need to write a complete ES module packer which is executed at run time. When dependent modules are generated during the build, you can use existing tools for packing.

stefnotch commented 1 year ago

The Firefox issue has been closed and is going to ship with the next Firefox version. Would that let us close this issue?

daxpedda commented 1 year ago

Worker modules, which presumably will ship in Firefox v114, will make this feature not necessary to spawn conveniently spawn worker modules.

Though some code will still have to be generated during runtime, which this feature would avoid.