New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Expose method to allow synchronous initialization #2924
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -469,7 +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 default init;\n"); | ||
footer.push_str("export { initSync }\n"); | ||
footer.push_str("export default init;"); | ||
} | ||
} | ||
|
||
|
@@ -637,12 +638,29 @@ 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 mut sync_init_function = String::new(); | ||
let declare_or_export; | ||
if self.config.mode.no_modules() { | ||
declare_or_export = "declare"; | ||
setup_function_declaration = "declare function wasm_bindgen"; | ||
} else { | ||
declare_or_export = "export"; | ||
|
||
sync_init_function.push_str(&format!("\ | ||
/**\n\ | ||
* Synchronously compiles the given `bytes` and instantiates the WebAssembly module.\n\ | ||
*\n\ | ||
* @param {{BufferSource}} bytes\n\ | ||
{memory_doc}\ | ||
*\n\ | ||
* @returns {{InitOutput}}\n\ | ||
*/\n\ | ||
export function initSync(bytes: BufferSource{memory_param}): InitOutput;\n\n\ | ||
", | ||
memory_doc = memory_doc, | ||
memory_param = memory_param | ||
)); | ||
|
||
setup_function_declaration = "export default function init"; | ||
} | ||
Ok(format!( | ||
|
@@ -652,6 +670,7 @@ impl<'a> Context<'a> { | |
{declare_or_export} interface InitOutput {{\n\ | ||
{output}}}\n\ | ||
\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\ | ||
|
@@ -665,6 +684,7 @@ impl<'a> Context<'a> { | |
(module_or_path{}: InitInput | Promise<InitInput>{}): Promise<InitOutput>;\n", | ||
memory_doc, arg_optional, memory_param, | ||
output = output, | ||
sync_init_function = sync_init_function, | ||
declare_or_export = declare_or_export, | ||
setup_function_declaration = setup_function_declaration, | ||
)) | ||
|
@@ -733,11 +753,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"); | ||
Comment on lines
+757
to
+759
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ℹ️ I have noticed that if a module has no imports and we don't initialize the |
||
|
||
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(); | ||
|
@@ -814,26 +834,49 @@ impl<'a> Context<'a> { | |
}} | ||
}} | ||
|
||
async function init(input{init_memory_arg}) {{ | ||
{default_module_path} | ||
function getImports() {{ | ||
const imports = {{}}; | ||
{imports_init} | ||
return imports; | ||
}} | ||
|
||
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {{ | ||
input = fetch(input); | ||
}} | ||
|
||
function initMemory(imports, maybe_memory) {{ | ||
{init_memory} | ||
}} | ||
|
||
const {{ instance, module }} = await load(await input, imports); | ||
|
||
function finalizeInit(instance, module) {{ | ||
wasm = instance.exports; | ||
init.__wbindgen_wasm_module = module; | ||
|
||
{post_instantiate} | ||
{start} | ||
return wasm; | ||
}} | ||
|
||
function initSync(bytes{init_memory_arg}) {{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ℹ️ This function gets exported to synchronously initialize the module. |
||
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(); | ||
|
||
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {{ | ||
input = fetch(input); | ||
}} | ||
|
||
initMemory(imports{init_memory_arg}); | ||
|
||
const {{ instance, module }} = await load(await input, imports); | ||
|
||
return finalizeInit(instance, module); | ||
}} | ||
", | ||
init_memory_arg = init_memory_arg, | ||
default_module_path = default_module_path, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/bin/sh | ||
|
||
set -ex | ||
|
||
wasm-pack build --target web | ||
python3 -m http.server |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Document</title> | ||
</head> | ||
<body> | ||
<script> | ||
/** | ||
* First off we spawn a Web Worker. That's where our lib will be used. Note that | ||
* we set the `type` to `module` to enable support for ES modules. | ||
*/ | ||
const worker = new Worker("/worker.js", { type: "module" }); | ||
|
||
/** | ||
* Here we listen for messages from the worker. | ||
*/ | ||
worker.onmessage = ({ data }) => { | ||
const { type } = data; | ||
|
||
switch (type) { | ||
case "FETCH_WASM": { | ||
/** | ||
* The worker wants to fetch the bytes for the module and for that we can use the `fetch` API. | ||
* Then we convert the response into an `ArrayBuffer` and transfer the bytes back to the worker. | ||
* | ||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API | ||
* @see https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects | ||
*/ | ||
fetch("/pkg/synchronous_instantiation_bg.wasm") | ||
.then((response) => response.arrayBuffer()) | ||
.then((bytes) => { | ||
worker.postMessage(bytes, [bytes]); | ||
}); | ||
break; | ||
} | ||
default: { | ||
break; | ||
} | ||
} | ||
}; | ||
</script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" }); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ℹ️ I have noticed there was two newlines at the end of the generated JS so I removed one newline.