diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000000..038ad50aa8c Binary files /dev/null and b/.DS_Store differ diff --git a/Cargo.toml b/Cargo.toml index b77314cebb1..6b1fdf56a0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,6 +89,7 @@ members = [ "examples/webxr", "examples/without-a-bundler", "examples/without-a-bundler-no-modules", + "examples/synchronous-instantiation", "tests/no-std", ] diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 3b737fea633..624536aa630 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -469,8 +469,8 @@ impl<'a> Context<'a> { OutputMode::Web => { self.imports_post.push_str("let wasm;\n"); init = self.gen_init(needs_manual_start, Some(&mut imports))?; - footer.push_str("export { getImports, initMemory, finalizeInit }\n"); - footer.push_str("export default init;\n"); + footer.push_str("export { initSync }\n"); + footer.push_str("export default init;"); } } @@ -638,34 +638,34 @@ impl<'a> Context<'a> { // So using "declare" everywhere for at least the NoModules option. // Also in (at least) the NoModules, the `init()` method is renamed to `wasm_bindgen()`. let setup_function_declaration; - let internal_helper_functions; + let sync_init_function; let declare_or_export; if self.config.mode.no_modules() { declare_or_export = "declare"; - internal_helper_functions = ""; + sync_init_function = ""; setup_function_declaration = "declare function wasm_bindgen"; } else { declare_or_export = "export"; - internal_helper_functions = "\ - export function getImports(): ModuleImports;\n\ - export function initMemory(imports: ModuleImports, maybe_memory?: WebAssembly.Memory): void;\n\ - export function finalizeInit(instance: WebAssembly.Instance, module: WebAssembly.Module): InitOutput;\n\ + sync_init_function = "\ + /**\n\ + * Synchronously compiles the given `bytes` and instantiates the WebAssembly module.\n\ + *\n\ + * @param {{BufferSource}} bytes\n\ + *\n\ + * @returns {{InitOutput}}\n\ + */\n\ + export function initSync(bytes: BufferSource): InitOutput;\n\n\ "; setup_function_declaration = "export default function init"; } Ok(format!( "\n\ - type ExportValue = Function | WebAssembly.Global | WebAssembly.Memory | WebAssembly.Table;\n\ - type ImportValue = ExportValue | number;\n\ - type Imports = Record;\n\ - type ModuleImports = Record;\n\ - \n\ {declare_or_export} type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;\n\ \n\ {declare_or_export} interface InitOutput {{\n\ {output}}}\n\ \n\ - {internal_helper_functions}\n\ + {sync_init_function}\ /**\n\ * If `module_or_path` is {{RequestInfo}} or {{URL}}, makes a request and\n\ * for everything else, calls `WebAssembly.instantiate` directly.\n\ @@ -679,8 +679,8 @@ impl<'a> Context<'a> { (module_or_path{}: InitInput | Promise{}): Promise;\n", memory_doc, arg_optional, memory_param, output = output, + sync_init_function = sync_init_function, declare_or_export = declare_or_export, - internal_helper_functions = internal_helper_functions, setup_function_declaration = setup_function_declaration, )) } @@ -748,11 +748,11 @@ impl<'a> Context<'a> { // Initialize the `imports` object for all import definitions that we're // directed to wire up. let mut imports_init = String::new(); - if self.wasm_import_definitions.len() > 0 { - imports_init.push_str("imports."); - imports_init.push_str(module_name); - imports_init.push_str(" = {};\n"); - } + + imports_init.push_str("imports."); + imports_init.push_str(module_name); + imports_init.push_str(" = {};\n"); + for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) { let import = self.module.imports.get_mut(*id); import.module = module_name.to_string(); @@ -842,12 +842,22 @@ impl<'a> Context<'a> { function finalizeInit(instance, module) {{ wasm = instance.exports; init.__wbindgen_wasm_module = module; - {post_instantiate} {start} return wasm; }} + function initSync(bytes{init_memory_arg}) {{ + const imports = getImports(); + + initMemory(imports{init_memory_arg}); + + const module = new WebAssembly.Module(bytes); + const instance = new WebAssembly.Instance(module, imports); + + return finalizeInit(instance, module); + }} + async function init(input{init_memory_arg}) {{ {default_module_path} const imports = getImports(); diff --git a/crates/cli/tests/wasm-bindgen/main.rs b/crates/cli/tests/wasm-bindgen/main.rs index d534b5a9724..b52f14e64c3 100644 --- a/crates/cli/tests/wasm-bindgen/main.rs +++ b/crates/cli/tests/wasm-bindgen/main.rs @@ -289,7 +289,7 @@ fn omit_default_module_path_target_web() { "\ async function init(input) { - const imports = {};", + const imports = getImports();", )); } @@ -309,7 +309,7 @@ fn omit_default_module_path_target_no_modules() { "\ async function init(input) { - const imports = {};", + const imports = getImports();", )); } diff --git a/examples/synchronous-instantiation/Cargo.toml b/examples/synchronous-instantiation/Cargo.toml new file mode 100644 index 00000000000..f68b62f9b9d --- /dev/null +++ b/examples/synchronous-instantiation/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "synchronous-instantiation" +version = "0.1.0" +authors = ["The wasm-bindgen Developers"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wasm-bindgen = "0.2.80" diff --git a/examples/synchronous-instantiation/README.md b/examples/synchronous-instantiation/README.md new file mode 100644 index 00000000000..9c0bc743ed1 --- /dev/null +++ b/examples/synchronous-instantiation/README.md @@ -0,0 +1,23 @@ +# Synchronous Instantiation + +[View documentation for this example online][dox] + +[dox]: https://rustwasm.github.io/docs/wasm-bindgen/examples/synchronous-instantiation.html + +You can build the example locally with: + +``` +$ wasm-pack build --target web +``` + +Then serve this directory in your favourite webserver and navigate to `host:port` +to open the index.html in your browser: + +``` +# static server from https://crates.io/crates/https +http + +# or use python +python2 -m SimpleHTTPServer +python3 -m http.server +``` diff --git a/examples/synchronous-instantiation/build.sh b/examples/synchronous-instantiation/build.sh new file mode 100755 index 00000000000..cb05aadb1a9 --- /dev/null +++ b/examples/synchronous-instantiation/build.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +set -ex + +wasm-pack build --target web +python3 -m http.server diff --git a/examples/synchronous-instantiation/index.html b/examples/synchronous-instantiation/index.html new file mode 100644 index 00000000000..f62e8efb274 --- /dev/null +++ b/examples/synchronous-instantiation/index.html @@ -0,0 +1,46 @@ + + + + + + + Document + + + + + diff --git a/examples/synchronous-instantiation/src/lib.rs b/examples/synchronous-instantiation/src/lib.rs new file mode 100644 index 00000000000..e5afafcb563 --- /dev/null +++ b/examples/synchronous-instantiation/src/lib.rs @@ -0,0 +1,12 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn log(value: &str); +} + +#[wasm_bindgen] +pub fn greet(name: &str) { + log(&format!("Hello, {}!", name)); +} diff --git a/examples/synchronous-instantiation/worker.js b/examples/synchronous-instantiation/worker.js new file mode 100644 index 00000000000..1163234e301 --- /dev/null +++ b/examples/synchronous-instantiation/worker.js @@ -0,0 +1,23 @@ +import * as wasm from "./pkg/synchronous_instantiation.js"; + +self.onmessage = ({ data: bytes }) => { + /** + * When we receive the bytes as an `ArrayBuffer` we can use that to + * synchronously initialize the module as opposed to asynchronously + * via the default export. The synchronous method internally uses + * `new WebAssembly.Module()` and `new WebAssembly.Instance()`. + */ + wasm.initSync(bytes); + + /** + * Once initialized we can call our exported `greet()` functions. + */ + wasm.greet("Dominic"); +}; + +/** + * Once the Web Worker was spwaned we ask the main thread to fetch the bytes + * for the WebAssembly module. Once fetched it will send the bytes back via + * a `postMessage` (see above). + */ +self.postMessage({ type: "FETCH_WASM" }); diff --git a/guide/src/examples/synchronous-instantiation.md b/guide/src/examples/synchronous-instantiation.md new file mode 100644 index 00000000000..087558537dd --- /dev/null +++ b/guide/src/examples/synchronous-instantiation.md @@ -0,0 +1,33 @@ +# Synchronous Instantiation + +[View full source code][code] + +[code]: https://github.com/rustwasm/wasm-bindgen/tree/master/examples/synchronous-instantiation + +This example shows how to synchronously initialize a WebAssembly module as opposed to [asynchronously][without-bundler]. In most cases, the default way of asynchronously initializing a module will suffice. However, there might be use cases where you'd like to lazy load a module on demand and synchronously compile and instantiate it. Note that this only works off the main thread and since compilation and instantiation of large modules can be expensive you should only use this method if it's absolutely required in your use case. Otherwise you should use the [default method][without-bundler]. + +For this deployment strategy bundlers like Webpack are not required. For more information on deployment see the [dedicated +documentation][deployment]. + +First let's take a look at our tiny lib: + +```rust +{{#include ../../../examples/synchronous-instantiation/src/lib.rs}} +``` + +Next, let's have a look at the `index.html`: + +```html +{{#include ../../../examples/synchronous-instantiation/index.html}} +``` + +Otherwise the rest of the magic happens in `worker.js`: + +```js +{{#include ../../../examples/synchronous-instantiation/worker.js}} +``` + +And that's it! Be sure to read up on the [deployment options][deployment] to see what it means to deploy without a bundler. + +[deployment]: ../reference/deployment.html +[without-bundler]: ./without-a-bundler.html