1
0
mirror of https://github.com/fluencelabs/wasm-bindgen synced 2025-04-11 06:36:05 +00:00

Merge pull request from alexcrichton/js-snippets

Implement the local JS snippets RFC
This commit is contained in:
Alex Crichton 2019-03-05 17:18:04 -06:00 committed by GitHub
commit 20c25ca922
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 1221 additions and 475 deletions

@ -81,6 +81,7 @@ members = [
"examples/webaudio", "examples/webaudio",
"examples/webgl", "examples/webgl",
"examples/without-a-bundler", "examples/without-a-bundler",
"examples/without-a-bundler-no-modules",
"tests/no-std", "tests/no-std",
] ]
exclude = ['crates/typescript'] exclude = ['crates/typescript']

@ -40,10 +40,15 @@ jobs:
- template: ci/azure-install-sccache.yml - template: ci/azure-install-sccache.yml
- script: cargo test --target wasm32-unknown-unknown - script: cargo test --target wasm32-unknown-unknown
displayName: "wasm-bindgen test suite" displayName: "wasm-bindgen test suite"
env:
RUST_LOG: wasm_bindgen_test_runner
GECKODRIVER_ARGS: --log trace
- script: cargo test --target wasm32-unknown-unknown -p js-sys - script: cargo test --target wasm32-unknown-unknown -p js-sys
displayName: "js-sys test suite" displayName: "js-sys test suite"
- script: cargo test --target wasm32-unknown-unknown -p webidl-tests - script: cargo test --target wasm32-unknown-unknown -p webidl-tests
displayName: "webidl-tests test suite" displayName: "webidl-tests test suite"
env:
WBINDGEN_I_PROMISE_JS_SYNTAX_WORKS_IN_NODE: 1
- script: cargo build --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --features "Node Window Document" - script: cargo build --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --features "Node Window Document"
displayName: "web-sys build" displayName: "web-sys build"
@ -94,6 +99,8 @@ jobs:
- template: ci/azure-install-sccache.yml - template: ci/azure-install-sccache.yml
- script: cargo test -p wasm-bindgen-webidl - script: cargo test -p wasm-bindgen-webidl
- script: cargo test -p webidl-tests --target wasm32-unknown-unknown - script: cargo test -p webidl-tests --target wasm32-unknown-unknown
env:
WBINDGEN_I_PROMISE_JS_SYNTAX_WORKS_IN_NODE: 1
- job: test_ui - job: test_ui
displayName: "Run UI tests" displayName: "Run UI tests"

@ -12,3 +12,14 @@ steps:
Write-Host "##vso[task.setvariable variable=GECKODRIVER;]$pwd\geckodriver.exe" Write-Host "##vso[task.setvariable variable=GECKODRIVER;]$pwd\geckodriver.exe"
displayName: "Download Geckodriver (Windows)" displayName: "Download Geckodriver (Windows)"
condition: eq( variables['Agent.OS'], 'Windows_NT' ) condition: eq( variables['Agent.OS'], 'Windows_NT' )
# It turns out that geckodriver.exe will fail if firefox takes too long to
# start, and for whatever reason the first execution of `firefox.exe` can
# take upwards of a mimute. It seems that subsequent executions are much
# faster, so have a dedicated step to run Firefox once which should I
# guess warm some cache somewhere so the headless tests later on all
# finish successfully
- script: |
"C:\Program Files\Mozilla Firefox\firefox.exe" --version
displayName: "Load firefox.exe into cache (presumably?)"
condition: eq( variables['Agent.OS'], 'Windows_NT' )

@ -15,6 +15,7 @@ spans = []
extra-traits = ["syn/extra-traits"] extra-traits = ["syn/extra-traits"]
[dependencies] [dependencies]
bumpalo = "2.1"
lazy_static = "1.0.0" lazy_static = "1.0.0"
log = "0.4" log = "0.4"
proc-macro2 = "0.4.8" proc-macro2 = "0.4.8"

@ -2,6 +2,7 @@ use proc_macro2::{Ident, Span};
use shared; use shared;
use syn; use syn;
use Diagnostic; use Diagnostic;
use std::hash::{Hash, Hasher};
/// An abstract syntax tree representing a rust program. Contains /// An abstract syntax tree representing a rust program. Contains
/// extra information for joining up this rust code with javascript. /// extra information for joining up this rust code with javascript.
@ -24,6 +25,8 @@ pub struct Program {
pub dictionaries: Vec<Dictionary>, pub dictionaries: Vec<Dictionary>,
/// custom typescript sections to be included in the definition file /// custom typescript sections to be included in the definition file
pub typescript_custom_sections: Vec<String>, pub typescript_custom_sections: Vec<String>,
/// Inline JS snippets
pub inline_js: Vec<String>,
} }
/// A rust to js interface. Allows interaction with rust objects/functions /// A rust to js interface. Allows interaction with rust objects/functions
@ -66,11 +69,37 @@ pub enum MethodSelf {
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
#[derive(Clone)] #[derive(Clone)]
pub struct Import { pub struct Import {
pub module: Option<String>, pub module: ImportModule,
pub js_namespace: Option<Ident>, pub js_namespace: Option<Ident>,
pub kind: ImportKind, pub kind: ImportKind,
} }
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
#[derive(Clone)]
pub enum ImportModule {
None,
Named(String, Span),
Inline(usize, Span),
}
impl Hash for ImportModule {
fn hash<H: Hasher>(&self, h: &mut H) {
match self {
ImportModule::None => {
0u8.hash(h);
}
ImportModule::Named(name, _) => {
1u8.hash(h);
name.hash(h);
}
ImportModule::Inline(idx, _) => {
2u8.hash(h);
idx.hash(h);
}
}
}
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
#[derive(Clone)] #[derive(Clone)]
pub enum ImportKind { pub enum ImportKind {

@ -94,25 +94,45 @@ impl TryToTokens for ast::Program {
shared::SCHEMA_VERSION, shared::SCHEMA_VERSION,
shared::version() shared::version()
); );
let encoded = encode::encode(self)?;
let mut bytes = Vec::new(); let mut bytes = Vec::new();
bytes.push((prefix_json.len() >> 0) as u8); bytes.push((prefix_json.len() >> 0) as u8);
bytes.push((prefix_json.len() >> 8) as u8); bytes.push((prefix_json.len() >> 8) as u8);
bytes.push((prefix_json.len() >> 16) as u8); bytes.push((prefix_json.len() >> 16) as u8);
bytes.push((prefix_json.len() >> 24) as u8); bytes.push((prefix_json.len() >> 24) as u8);
bytes.extend_from_slice(prefix_json.as_bytes()); bytes.extend_from_slice(prefix_json.as_bytes());
bytes.extend_from_slice(&encode::encode(self)?); bytes.extend_from_slice(&encoded.custom_section);
let generated_static_length = bytes.len(); let generated_static_length = bytes.len();
let generated_static_value = syn::LitByteStr::new(&bytes, Span::call_site()); let generated_static_value = syn::LitByteStr::new(&bytes, Span::call_site());
// We already consumed the contents of included files when generating
// the custom section, but we want to make sure that updates to the
// generated files will cause this macro to rerun incrementally. To do
// that we use `include_str!` to force rustc to think it has a
// dependency on these files. That way when the file changes Cargo will
// automatically rerun rustc which will rerun this macro. Other than
// this we don't actually need the results of the `include_str!`, so
// it's just shoved into an anonymous static.
let file_dependencies = encoded.included_files
.iter()
.map(|file| {
let file = file.to_str().unwrap();
quote! { include_str!(#file) }
});
(quote! { (quote! {
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[link_section = "__wasm_bindgen_unstable"] #[link_section = "__wasm_bindgen_unstable"]
#[doc(hidden)] #[doc(hidden)]
#[allow(clippy::all)] #[allow(clippy::all)]
pub static #generated_static_name: [u8; #generated_static_length] = pub static #generated_static_name: [u8; #generated_static_length] = {
*#generated_static_value; static _INCLUDED_FILES: &[&str] = &[#(#file_dependencies),*];
*#generated_static_value
};
}) })
.to_tokens(tokens); .to_tokens(tokens);

@ -1,40 +1,96 @@
use proc_macro2::{Ident, Span};
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::env;
use proc_macro2::{Ident, Span}; use std::fs;
use std::path::PathBuf;
use util::ShortHash;
use ast; use ast;
use Diagnostic; use Diagnostic;
pub fn encode(program: &ast::Program) -> Result<Vec<u8>, Diagnostic> { pub struct EncodeResult {
pub custom_section: Vec<u8>,
pub included_files: Vec<PathBuf>,
}
pub fn encode(program: &ast::Program) -> Result<EncodeResult, Diagnostic> {
let mut e = Encoder::new(); let mut e = Encoder::new();
let i = Interner::new(); let i = Interner::new();
shared_program(program, &i)?.encode(&mut e); shared_program(program, &i)?.encode(&mut e);
Ok(e.finish()) let custom_section = e.finish();
let included_files = i.files.borrow().values().map(|p| &p.path).cloned().collect();
Ok(EncodeResult { custom_section, included_files })
} }
struct Interner { struct Interner {
map: RefCell<HashMap<Ident, String>>, bump: bumpalo::Bump,
files: RefCell<HashMap<String, LocalFile>>,
root: PathBuf,
crate_name: String,
}
struct LocalFile {
path: PathBuf,
definition: Span,
new_identifier: String,
} }
impl Interner { impl Interner {
fn new() -> Interner { fn new() -> Interner {
Interner { Interner {
map: RefCell::new(HashMap::new()), bump: bumpalo::Bump::new(),
files: RefCell::new(HashMap::new()),
root: env::var_os("CARGO_MANIFEST_DIR").unwrap().into(),
crate_name: env::var("CARGO_PKG_NAME").unwrap(),
} }
} }
fn intern(&self, s: &Ident) -> &str { fn intern(&self, s: &Ident) -> &str {
let mut map = self.map.borrow_mut(); self.intern_str(&s.to_string())
if let Some(s) = map.get(s) {
return unsafe { &*(&**s as *const str) };
}
map.insert(s.clone(), s.to_string());
unsafe { &*(&*map[s] as *const str) }
} }
fn intern_str(&self, s: &str) -> &str { fn intern_str(&self, s: &str) -> &str {
self.intern(&Ident::new(s, Span::call_site())) // NB: eventually this could be used to intern `s` to only allocate one
// copy, but for now let's just "transmute" `s` to have the same
// lifetmie as this struct itself (which is our main goal here)
bumpalo::collections::String::from_str_in(s, &self.bump).into_bump_str()
}
/// Given an import to a local module `id` this generates a unique module id
/// to assign to the contents of `id`.
///
/// Note that repeated invocations of this function will be memoized, so the
/// same `id` will always return the same resulting unique `id`.
fn resolve_import_module(&self, id: &str, span: Span) -> Result<&str, Diagnostic> {
let mut files = self.files.borrow_mut();
if let Some(file) = files.get(id) {
return Ok(self.intern_str(&file.new_identifier))
}
let path = if id.starts_with("/") {
self.root.join(&id[1..])
} else if id.starts_with("./") || id.starts_with("../") {
let msg = "relative module paths aren't supported yet";
return Err(Diagnostic::span_error(span, msg))
} else {
return Ok(self.intern_str(&id))
};
// Generate a unique ID which is somewhat readable as well, so mix in
// the crate name, hash to make it unique, and then the original path.
let new_identifier = format!("{}{}", self.unique_crate_identifier(), id);
let file = LocalFile {
path,
definition: span,
new_identifier,
};
files.insert(id.to_string(), file);
drop(files);
self.resolve_import_module(id, span)
}
fn unique_crate_identifier(&self) -> String {
format!("{}-{}", self.crate_name, ShortHash(0))
} }
} }
@ -64,8 +120,30 @@ fn shared_program<'a>(
.iter() .iter()
.map(|x| -> &'a str { &x }) .map(|x| -> &'a str { &x })
.collect(), .collect(),
// version: shared::version(), local_modules: intern
// schema_version: shared::SCHEMA_VERSION.to_string(), .files
.borrow()
.values()
.map(|file| {
fs::read_to_string(&file.path)
.map(|s| {
LocalModule {
identifier: intern.intern_str(&file.new_identifier),
contents: intern.intern_str(&s),
}
})
.map_err(|e| {
let msg = format!("failed to read file `{}`: {}", file.path.display(), e);
Diagnostic::span_error(file.definition, msg)
})
})
.collect::<Result<Vec<_>, _>>()?,
inline_js: prog
.inline_js
.iter()
.map(|js| intern.intern_str(js))
.collect(),
unique_crate_identifier: intern.intern_str(&intern.unique_crate_identifier()),
}) })
} }
@ -111,7 +189,13 @@ fn shared_variant<'a>(v: &'a ast::Variant, intern: &'a Interner) -> EnumVariant<
fn shared_import<'a>(i: &'a ast::Import, intern: &'a Interner) -> Result<Import<'a>, Diagnostic> { fn shared_import<'a>(i: &'a ast::Import, intern: &'a Interner) -> Result<Import<'a>, Diagnostic> {
Ok(Import { Ok(Import {
module: i.module.as_ref().map(|s| &**s), module: match &i.module {
ast::ImportModule::Named(m, span) => {
ImportModule::Named(intern.resolve_import_module(m, *span)?)
}
ast::ImportModule::Inline(idx, _) => ImportModule::Inline(*idx as u32),
ast::ImportModule::None => ImportModule::None,
},
js_namespace: i.js_namespace.as_ref().map(|s| intern.intern(s)), js_namespace: i.js_namespace.as_ref().map(|s| intern.intern(s)),
kind: shared_import_kind(&i.kind, intern)?, kind: shared_import_kind(&i.kind, intern)?,
}) })

@ -2,6 +2,7 @@
#![cfg_attr(feature = "extra-traits", deny(missing_debug_implementations))] #![cfg_attr(feature = "extra-traits", deny(missing_debug_implementations))]
#![doc(html_root_url = "https://docs.rs/wasm-bindgen-backend/0.2")] #![doc(html_root_url = "https://docs.rs/wasm-bindgen-backend/0.2")]
extern crate bumpalo;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
extern crate proc_macro2; extern crate proc_macro2;

@ -94,7 +94,7 @@ pub fn ident_ty(ident: Ident) -> syn::Type {
pub fn wrap_import_function(function: ast::ImportFunction) -> ast::Import { pub fn wrap_import_function(function: ast::ImportFunction) -> ast::Import {
ast::Import { ast::Import {
module: None, module: ast::ImportModule::None,
js_namespace: None, js_namespace: None,
kind: ast::ImportKind::Function(function), kind: ast::ImportKind::Function(function),
} }

@ -1,8 +1,9 @@
use crate::decode; use crate::decode;
use crate::descriptor::{Descriptor, VectorKind}; use crate::descriptor::{Descriptor, VectorKind};
use crate::{Bindgen, EncodeInto}; use crate::{Bindgen, EncodeInto, OutputMode};
use failure::{bail, Error, ResultExt}; use failure::{bail, Error, ResultExt};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::env;
use walrus::{MemoryId, Module}; use walrus::{MemoryId, Module};
use wasm_bindgen_wasm_interpreter::Interpreter; use wasm_bindgen_wasm_interpreter::Interpreter;
@ -33,7 +34,7 @@ pub struct Context<'a> {
/// from, `None` being the global module. The second key is a map of /// from, `None` being the global module. The second key is a map of
/// identifiers we've already imported from the module to what they're /// identifiers we've already imported from the module to what they're
/// called locally. /// called locally.
pub imported_names: HashMap<Option<&'a str>, HashMap<&'a str, String>>, pub imported_names: HashMap<ImportModule<'a>, HashMap<&'a str, String>>,
/// A set of all imported identifiers to the number of times they've been /// A set of all imported identifiers to the number of times they've been
/// imported, used to generate new identifiers. /// imported, used to generate new identifiers.
@ -53,6 +54,16 @@ pub struct Context<'a> {
pub interpreter: &'a mut Interpreter, pub interpreter: &'a mut Interpreter,
pub memory: MemoryId, pub memory: MemoryId,
/// A map of all local modules we've found, from the identifier they're
/// known as to their actual JS contents.
pub local_modules: HashMap<&'a str, &'a str>,
/// A map of how many snippets we've seen from each unique crate identifier,
/// used to number snippets correctly when writing them to the filesystem
/// when there's multiple snippets within one crate that aren't all part of
/// the same `Program`.
pub snippet_offsets: HashMap<&'a str, usize>,
pub anyref: wasm_bindgen_anyref_xform::Context, pub anyref: wasm_bindgen_anyref_xform::Context,
} }
@ -100,6 +111,20 @@ enum Import<'a> {
name: &'a str, name: &'a str,
field: Option<&'a str>, field: Option<&'a str>,
}, },
/// Same as `Module`, except we're importing from a local module defined in
/// a local JS snippet.
LocalModule {
module: &'a str,
name: &'a str,
field: Option<&'a str>,
},
/// Same as `Module`, except we're importing from an `inline_js` attribute
InlineJs {
unique_crate_identifier: &'a str,
snippet_idx_in_crate: usize,
name: &'a str,
field: Option<&'a str>,
},
/// A global import which may have a number of vendor prefixes associated /// A global import which may have a number of vendor prefixes associated
/// with it, like `webkitAudioPrefix`. The `name` is the name to test /// with it, like `webkitAudioPrefix`. The `name` is the name to test
/// whether it's prefixed. /// whether it's prefixed.
@ -124,19 +149,27 @@ impl<'a> Context<'a> {
self.globals.push_str(c); self.globals.push_str(c);
self.typescript.push_str(c); self.typescript.push_str(c);
} }
let global = if self.use_node_require() { let global = match self.config.mode {
OutputMode::Node {
experimental_modules: false,
} => {
if contents.starts_with("class") { if contents.starts_with("class") {
format!("{1}\nmodule.exports.{0} = {0};\n", name, contents) format!("{1}\nmodule.exports.{0} = {0};\n", name, contents)
} else { } else {
format!("module.exports.{} = {};\n", name, contents) format!("module.exports.{} = {};\n", name, contents)
} }
} else if self.config.no_modules { }
OutputMode::NoModules { .. } => {
if contents.starts_with("class") { if contents.starts_with("class") {
format!("{1}\n__exports.{0} = {0};\n", name, contents) format!("{1}\n__exports.{0} = {0};\n", name, contents)
} else { } else {
format!("__exports.{} = {};\n", name, contents) format!("__exports.{} = {};\n", name, contents)
} }
} else { }
OutputMode::Bundler
| OutputMode::Node {
experimental_modules: true,
} => {
if contents.starts_with("function") { if contents.starts_with("function") {
format!("export function {}{}\n", name, &contents[8..]) format!("export function {}{}\n", name, &contents[8..])
} else if contents.starts_with("class") { } else if contents.starts_with("class") {
@ -144,8 +177,34 @@ impl<'a> Context<'a> {
} else { } else {
format!("export const {} = {};\n", name, contents) format!("export const {} = {};\n", name, contents)
} }
}
OutputMode::Browser => {
// In browser mode there's no need to export the internals of
// wasm-bindgen as we're not using the module itself as the
// import object but rather the `__exports` map we'll be
// initializing below.
let export = if name.starts_with("__wbindgen")
|| name.starts_with("__wbg_")
|| name.starts_with("__widl_")
{
""
} else {
"export "
};
if contents.starts_with("function") {
format!("{}function {}{}\n", export, name, &contents[8..])
} else if contents.starts_with("class") {
format!("{}{}\n", export, contents)
} else {
format!("{}const {} = {};\n", export, name, contents)
}
}
}; };
self.global(&global); self.global(&global);
if self.config.mode.browser() {
self.global(&format!("__exports.{} = {0};", name));
}
} }
fn require_internal_export(&mut self, name: &'static str) -> Result<(), Error> { fn require_internal_export(&mut self, name: &'static str) -> Result<(), Error> {
@ -166,6 +225,170 @@ impl<'a> Context<'a> {
} }
pub fn finalize(&mut self, module_name: &str) -> Result<(String, String), Error> { pub fn finalize(&mut self, module_name: &str) -> Result<(String, String), Error> {
// Wire up all default intrinsics, those which don't have any sort of
// dependency on the clsoure/anyref/etc passes. This is where almost all
// intrinsics are wired up.
self.wire_up_initial_intrinsics()?;
// Next up, perform our closure rewriting pass. This is where we'll
// update invocations of the closure intrinsics we have to instead call
// appropriate JS functions which actually create closures.
closures::rewrite(self).with_context(|_| "failed to generate internal closure shims")?;
// Finalize all bindings for JS classes. This is where we'll generate JS
// glue for all classes as well as finish up a few final imports like
// `__wrap` and such.
self.write_classes()?;
// And now that we're almost ready, run the final "anyref" pass. This is
// where we transform a wasm module which doesn't actually use `anyref`
// anywhere to using the type internally. The transformation here is
// based off all the previous data we've collected so far for each
// import/export listed.
self.anyref.run(self.module)?;
// With our transforms finished, we can now wire up the final list of
// intrinsics which may depend on the passes run above.
self.wire_up_late_intrinsics()?;
// We're almost done here, so we can delete any internal exports (like
// `__wbindgen_malloc`) if none of our JS glue actually needed it.
self.unexport_unused_internal_exports();
// Handle the `start` function, if one was specified. If we're in a
// --test mode (such as wasm-bindgen-test-runner) then we skip this
// entirely. Otherwise we want to first add a start function to the
// `start` section if one is specified.
//
// Note that once a start function is added, if any, we immediately
// un-start it. This is done because we require that the JS glue
// initializes first, so we execute wasm startup manually once the JS
// glue is all in place.
let mut needs_manual_start = false;
if self.config.emit_start {
self.add_start_function()?;
needs_manual_start = self.unstart_start_function();
}
// If our JS glue needs to access the function table, then do so here.
// JS closure shim generation may access the function table as an
// example, but if there's no closures in the module there's no need to
// export it!
self.export_table()?;
// After all we've done, especially
// `unexport_unused_internal_exports()`, we probably have a bunch of
// garbage in the module that's no longer necessary, so delete
// everything that we don't actually need.
walrus::passes::gc::run(self.module);
// Almost there, but before we're done make sure to rewrite the `module`
// field of all imports in the wasm module. The field is currently
// always `__wbindgen_placeholder__` coming out of rustc, but we need to
// update that here to the shim file or an actual ES module.
self.rewrite_imports(module_name);
// We likely made a ton of modifications, so add ourselves to the
// producers section!
self.update_producers_section();
// Cause any future calls to `should_write_global` to panic, making sure
// we don't ask for items which we can no longer emit.
drop(self.exposed_globals.take().unwrap());
Ok(self.finalize_js(module_name, needs_manual_start))
}
/// Performs the task of actually generating the final JS module, be it
/// `--no-modules`, `--browser`, or for bundlers. This is the very last step
/// performed in `finalize`.
fn finalize_js(&mut self, module_name: &str, needs_manual_start: bool) -> (String, String) {
let mut js = String::new();
if self.config.mode.no_modules() {
js.push_str("(function() {\n");
}
// Depending on the output mode, generate necessary glue to actually
// import the wasm file in one way or another.
let mut init = String::new();
match &self.config.mode {
// In `--no-modules` mode we need to both expose a name on the
// global object as well as generate our own custom start function.
OutputMode::NoModules { global } => {
js.push_str("const __exports = {};\n");
js.push_str("let wasm;\n");
init = self.gen_init(&module_name, needs_manual_start);
self.footer.push_str(&format!(
"self.{} = Object.assign(init, __exports);\n",
global
));
}
// With normal CommonJS node we need to defer requiring the wasm
// until the end so most of our own exports are hooked up
OutputMode::Node {
experimental_modules: false,
} => {
self.footer
.push_str(&format!("wasm = require('./{}_bg');\n", module_name));
if needs_manual_start {
self.footer.push_str("wasm.__wbindgen_start();\n");
}
js.push_str("var wasm;\n");
}
// With Bundlers and modern ES6 support in Node we can simply import
// the wasm file as if it were an ES module and let the
// bundler/runtime take care of it.
OutputMode::Bundler
| OutputMode::Node {
experimental_modules: true,
} => {
js.push_str(&format!("import * as wasm from './{}_bg';\n", module_name));
if needs_manual_start {
self.footer.push_str("wasm.__wbindgen_start();\n");
}
}
// With a browser-native output we're generating an ES module, but
// browsers don't support natively importing wasm right now so we
// expose the same initialization function as `--no-modules` as the
// default export of the module.
OutputMode::Browser => {
js.push_str("const __exports = {};\n");
self.imports_post.push_str("let wasm;\n");
init = self.gen_init(&module_name, needs_manual_start);
self.footer.push_str("export default init;\n");
}
}
// Emit all the JS for importing all our functionality
js.push_str(&self.imports);
js.push_str("\n");
js.push_str(&self.imports_post);
js.push_str("\n");
// Emit all our exports from this module
js.push_str(&self.globals);
js.push_str("\n");
// Generate the initialization glue, if there was any
js.push_str(&init);
js.push_str("\n");
js.push_str(&self.footer);
js.push_str("\n");
if self.config.mode.no_modules() {
js.push_str("})();\n");
}
while js.contains("\n\n\n") {
js = js.replace("\n\n\n", "\n\n");
}
(js, self.typescript.clone())
}
fn wire_up_initial_intrinsics(&mut self) -> Result<(), Error> {
self.bind("__wbindgen_string_new", &|me| { self.bind("__wbindgen_string_new", &|me| {
me.anyref.import_xform( me.anyref.import_xform(
"__wbindgen_placeholder__", "__wbindgen_placeholder__",
@ -529,7 +752,7 @@ impl<'a> Context<'a> {
})?; })?;
self.bind("__wbindgen_module", &|me| { self.bind("__wbindgen_module", &|me| {
if !me.config.no_modules { if !me.config.mode.no_modules() && !me.config.mode.browser() {
bail!( bail!(
"`wasm_bindgen::module` is currently only supported with \ "`wasm_bindgen::module` is currently only supported with \
--no-modules" --no-modules"
@ -548,10 +771,28 @@ impl<'a> Context<'a> {
)) ))
})?; })?;
closures::rewrite(self).with_context(|_| "failed to generate internal closure shims")?; self.bind("__wbindgen_throw", &|me| {
self.write_classes()?; me.expose_get_string_from_wasm();
self.anyref.run(self.module)?; Ok(String::from(
"
function(ptr, len) {
throw new Error(getStringFromWasm(ptr, len));
}
",
))
})?;
Ok(())
}
/// Provide implementations of remaining intrinsics after initial passes
/// have been run on the wasm module.
///
/// The intrinsics implemented here are added very late in the process or
/// otherwise may be overwritten by passes (such as the anyref pass). As a
/// result they don't go into the initial list of intrinsics but go just at
/// the end.
fn wire_up_late_intrinsics(&mut self) -> Result<(), Error> {
// After the anyref pass has executed, if this intrinsic is needed then // After the anyref pass has executed, if this intrinsic is needed then
// we expose a function which initializes it // we expose a function which initializes it
self.bind("__wbindgen_init_anyref_table", &|me| { self.bind("__wbindgen_init_anyref_table", &|me| {
@ -589,84 +830,12 @@ impl<'a> Context<'a> {
Ok(String::from("function(i) { dropObject(i); }")) Ok(String::from("function(i) { dropObject(i); }"))
})?; })?;
self.unexport_unused_internal_exports(); Ok(())
// Handle the `start` function, if one was specified. If we're in a
// --test mode (such as wasm-bindgen-test-runner) then we skip this
// entirely. Otherwise we want to first add a start function to the
// `start` section if one is specified.
//
// Afterwards, we need to perform what's a bit of a hack. Right after we
// added the start function, we remove it again because no current
// strategy for bundlers and deployment works well enough with it. For
// `--no-modules` output we need to be sure to call the start function
// after our exports are wired up (or most imported functions won't
// work).
//
// For ESM outputs bundlers like webpack also don't work because
// currently they run the wasm initialization before the JS glue
// initialization, meaning that if the wasm start function calls
// imported functions the JS glue isn't ready to go just yet.
//
// To handle `--no-modules` we just unstart the start function and call
// it manually. To handle the ESM use case we switch the start function
// to calling an imported function which defers the start function via
// `Promise.resolve().then(...)` to execute on the next microtask tick.
let mut has_start_function = false;
if self.config.emit_start {
self.add_start_function()?;
has_start_function = self.unstart_start_function();
// In the "we're pretending to be an ES module use case if we've got
// a start function then we use an injected shim to actually execute
// the real start function on the next tick of the microtask queue
// (explained above)
if !self.config.no_modules && has_start_function {
self.inject_start_shim();
} }
} fn gen_init(&mut self, module_name: &str, needs_manual_start: bool) -> String {
self.export_table()?;
walrus::passes::gc::run(self.module);
// Note that it's important `throw` comes last *after* we gc. The
// `__wbindgen_malloc` function may call this but we only want to
// generate code for this if it's actually live (and __wbindgen_malloc
// isn't gc'd).
self.bind("__wbindgen_throw", &|me| {
me.expose_get_string_from_wasm();
Ok(String::from(
"
function(ptr, len) {
throw new Error(getStringFromWasm(ptr, len));
}
",
))
})?;
self.rewrite_imports(module_name);
self.update_producers_section();
// Cause any future calls to `should_write_global` to panic, making sure
// we don't ask for items which we can no longer emit.
drop(self.exposed_globals.take().unwrap());
let mut js = if self.config.threads.is_some() {
// TODO: It's not clear right now how to best use threads with
// bundlers like webpack. We need a way to get the existing
// module/memory into web workers for now and we don't quite know
// idiomatically how to do that! In the meantime, always require
// `--no-modules`
if !self.config.no_modules {
bail!("most use `--no-modules` with threads for now")
}
let mem = self.module.memories.get(self.memory); let mem = self.module.memories.get(self.memory);
if mem.import.is_none() { let (init_memory1, init_memory2) = if mem.import.is_some() {
bail!("must impot a shared memory with threads")
}
let mut memory = String::from("new WebAssembly.Memory({"); let mut memory = String::from("new WebAssembly.Memory({");
memory.push_str(&format!("initial:{}", mem.initial)); memory.push_str(&format!("initial:{}", mem.initial));
if let Some(max) = mem.maximum { if let Some(max) = mem.maximum {
@ -676,25 +845,28 @@ impl<'a> Context<'a> {
memory.push_str(",shared:true"); memory.push_str(",shared:true");
} }
memory.push_str("})"); memory.push_str("})");
self.imports_post.push_str("let memory;\n");
(
format!("memory = __exports.memory = maybe_memory;"),
format!("memory = __exports.memory = {};", memory),
)
} else {
(String::new(), String::new())
};
format!( format!(
"\ "\
(function() {{
var wasm;
var memory;
const __exports = {{}};
{globals}
function init(module_or_path, maybe_memory) {{ function init(module_or_path, maybe_memory) {{
let result; let result;
const imports = {{ './{module}': __exports }}; const imports = {{ './{module}': __exports }};
if (module_or_path instanceof WebAssembly.Module) {{ if (module_or_path instanceof WebAssembly.Module) {{
memory = __exports.memory = maybe_memory; {init_memory1}
result = WebAssembly.instantiate(module_or_path, imports) result = WebAssembly.instantiate(module_or_path, imports)
.then(instance => {{ .then(instance => {{
return {{ instance, module: module_or_path }} return {{ instance, module: module_or_path }};
}}); }});
}} else {{ }} else {{
memory = __exports.memory = {init_memory}; {init_memory2}
const response = fetch(module_or_path); const response = fetch(module_or_path);
if (typeof WebAssembly.instantiateStreaming === 'function') {{ if (typeof WebAssembly.instantiateStreaming === 'function') {{
result = WebAssembly.instantiateStreaming(response, imports) result = WebAssembly.instantiateStreaming(response, imports)
@ -715,119 +887,22 @@ impl<'a> Context<'a> {
}} }}
}} }}
return result.then(({{instance, module}}) => {{ return result.then(({{instance, module}}) => {{
wasm = init.wasm = instance.exports; wasm = instance.exports;
init.__wbindgen_wasm_instance = instance;
init.__wbindgen_wasm_module = module; init.__wbindgen_wasm_module = module;
init.__wbindgen_wasm_memory = __exports.memory;
{start} {start}
return wasm;
}}); }});
}}; }}
self.{global_name} = Object.assign(init, __exports); ",
}})();",
globals = self.globals,
module = module_name, module = module_name,
global_name = self init_memory1 = init_memory1,
.config init_memory2 = init_memory2,
.no_modules_global start = if needs_manual_start {
.as_ref()
.map(|s| &**s)
.unwrap_or("wasm_bindgen"),
init_memory = memory,
start = if has_start_function {
"wasm.__wbindgen_start();" "wasm.__wbindgen_start();"
} else { } else {
"" ""
}, },
) )
} else if self.config.no_modules {
format!(
"\
(function() {{
var wasm;
const __exports = {{}};
{globals}
function init(path_or_module) {{
let instantiation;
const imports = {{ './{module}': __exports }};
if (path_or_module instanceof WebAssembly.Module) {{
instantiation = WebAssembly.instantiate(path_or_module, imports)
.then(instance => {{
return {{ instance, module: path_or_module }}
}});
}} else {{
const data = fetch(path_or_module);
if (typeof WebAssembly.instantiateStreaming === 'function') {{
instantiation = WebAssembly.instantiateStreaming(data, imports)
.catch(e => {{
console.warn(\"`WebAssembly.instantiateStreaming` failed. Assuming this is \
because your server does not serve wasm with \
`application/wasm` MIME type. Falling back to \
`WebAssembly.instantiate` which is slower. Original \
error:\\n\", e);
return data
.then(r => r.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, imports));
}});
}} else {{
instantiation = data
.then(response => response.arrayBuffer())
.then(buffer => WebAssembly.instantiate(buffer, imports));
}}
}}
return instantiation.then(({{instance}}) => {{
wasm = init.wasm = instance.exports;
{start}
}});
}};
self.{global_name} = Object.assign(init, __exports);
}})();",
globals = self.globals,
module = module_name,
global_name = self
.config
.no_modules_global
.as_ref()
.map(|s| &**s)
.unwrap_or("wasm_bindgen"),
start = if has_start_function {
"wasm.__wbindgen_start();"
} else {
""
},
)
} else {
let import_wasm = if self.globals.len() == 0 {
String::new()
} else if self.use_node_require() {
self.footer
.push_str(&format!("wasm = require('./{}_bg');", module_name));
format!("var wasm;")
} else {
format!("import * as wasm from './{}_bg';", module_name)
};
format!(
"\
/* tslint:disable */\n\
{import_wasm}\n\
{imports}\n\
{imports_post}\n\
{globals}\n\
{footer}",
import_wasm = import_wasm,
globals = self.globals,
imports = self.imports,
imports_post = self.imports_post,
footer = self.footer,
)
};
while js.contains("\n\n\n") {
js = js.replace("\n\n\n", "\n\n");
}
Ok((js, self.typescript.clone()))
} }
fn bind( fn bind(
@ -1235,8 +1310,7 @@ impl<'a> Context<'a> {
passStringToWasm = function(arg) {{ {} }}; passStringToWasm = function(arg) {{ {} }};
}} }}
", ",
use_encode_into, use_encode_into, use_encode,
use_encode,
)); ));
} }
_ => { _ => {
@ -1364,14 +1438,14 @@ impl<'a> Context<'a> {
} }
fn expose_text_processor(&mut self, s: &str) { fn expose_text_processor(&mut self, s: &str) {
if self.config.nodejs_experimental_modules { if self.config.mode.nodejs_experimental_modules() {
self.imports self.imports
.push_str(&format!("import {{ {} }} from 'util';\n", s)); .push_str(&format!("import {{ {} }} from 'util';\n", s));
self.global(&format!("let cached{0} = new {0}('utf-8');", s)); self.global(&format!("let cached{0} = new {0}('utf-8');", s));
} else if self.config.nodejs { } else if self.config.mode.nodejs() {
self.global(&format!("const {0} = require('util').{0};", s)); self.global(&format!("const {0} = require('util').{0};", s));
self.global(&format!("let cached{0} = new {0}('utf-8');", s)); self.global(&format!("let cached{0} = new {0}('utf-8');", s));
} else if !(self.config.browser || self.config.no_modules) { } else if !self.config.mode.always_run_in_browser() {
self.global(&format!( self.global(&format!(
" "
const l{0} = typeof {0} === 'undefined' ? \ const l{0} = typeof {0} === 'undefined' ? \
@ -2008,7 +2082,7 @@ impl<'a> Context<'a> {
} }
fn use_node_require(&self) -> bool { fn use_node_require(&self) -> bool {
self.config.nodejs && !self.config.nodejs_experimental_modules self.config.mode.nodejs() && !self.config.mode.nodejs_experimental_modules()
} }
fn memory(&mut self) -> &'static str { fn memory(&mut self) -> &'static str {
@ -2052,23 +2126,41 @@ impl<'a> Context<'a> {
.or_insert_with(|| { .or_insert_with(|| {
let name = generate_identifier(import.name(), imported_identifiers); let name = generate_identifier(import.name(), imported_identifiers);
match &import { match &import {
Import::Module { module, .. } => { Import::Module { .. }
| Import::LocalModule { .. }
| Import::InlineJs { .. } => {
// When doing a modular import local snippets (either
// inline or not) are routed to a local `./snippets`
// directory which the rest of `wasm-bindgen` will fill
// in.
let path = match import {
Import::Module { module, .. } => module.to_string(),
Import::LocalModule { module, .. } => format!("./snippets/{}", module),
Import::InlineJs {
unique_crate_identifier,
snippet_idx_in_crate,
..
} => format!(
"./snippets/{}/inline{}.js",
unique_crate_identifier, snippet_idx_in_crate
),
_ => unreachable!(),
};
if use_node_require { if use_node_require {
imports.push_str(&format!( imports.push_str(&format!(
"const {} = require(String.raw`{}`).{};\n", "const {} = require(String.raw`{}`).{};\n",
name, name,
module, path,
import.name() import.name()
)); ));
} else if import.name() == name { } else if import.name() == name {
imports imports.push_str(&format!("import {{ {} }} from '{}';\n", name, path));
.push_str(&format!("import {{ {} }} from '{}';\n", name, module));
} else { } else {
imports.push_str(&format!( imports.push_str(&format!(
"import {{ {} as {} }} from '{}';\n", "import {{ {} as {} }} from '{}';\n",
import.name(), import.name(),
name, name,
module path
)); ));
} }
name name
@ -2290,24 +2382,6 @@ impl<'a> Context<'a> {
true true
} }
/// Injects a `start` function into the wasm module. This start function
/// calls a shim in the generated JS which defers the actual start function
/// to the next microtask tick of the event queue.
///
/// See docs above at callsite for why this happens.
fn inject_start_shim(&mut self) {
let body = "function() {
Promise.resolve().then(() => wasm.__wbindgen_start());
}";
self.export("__wbindgen_defer_start", body, None);
let ty = self.module.types.add(&[], &[]);
let id =
self.module
.add_import_func("__wbindgen_placeholder__", "__wbindgen_defer_start", ty);
assert!(self.module.start.is_none());
self.module.start = Some(id);
}
fn expose_anyref_table(&mut self) { fn expose_anyref_table(&mut self) {
assert!(self.config.anyref); assert!(self.config.anyref);
if !self.should_write_global("anyref_table") { if !self.should_write_global("anyref_table") {
@ -2368,6 +2442,14 @@ impl<'a> Context<'a> {
impl<'a, 'b> SubContext<'a, 'b> { impl<'a, 'b> SubContext<'a, 'b> {
pub fn generate(&mut self) -> Result<(), Error> { pub fn generate(&mut self) -> Result<(), Error> {
for m in self.program.local_modules.iter() {
// All local modules we find should be unique, but the same module
// may have showed up in a few different blocks. If that's the case
// all the same identifiers should have the same contents.
if let Some(prev) = self.cx.local_modules.insert(m.identifier, m.contents) {
assert_eq!(prev, m.contents);
}
}
for f in self.program.exports.iter() { for f in self.program.exports.iter() {
self.generate_export(f).with_context(|_| { self.generate_export(f).with_context(|_| {
format!( format!(
@ -2752,16 +2834,44 @@ impl<'a, 'b> SubContext<'a, 'b> {
) -> Result<Import<'b>, Error> { ) -> Result<Import<'b>, Error> {
// First up, imports don't work at all in `--no-modules` mode as we're // First up, imports don't work at all in `--no-modules` mode as we're
// not sure how to import them. // not sure how to import them.
if self.cx.config.no_modules { let is_local_snippet = match import.module {
if let Some(module) = &import.module { decode::ImportModule::Named(s) => self.cx.local_modules.contains_key(s),
decode::ImportModule::Inline(_) => true,
decode::ImportModule::None => false,
};
if self.cx.config.mode.no_modules() {
if is_local_snippet {
bail!(
"local JS snippets are not supported with `--no-modules`; \
use `--browser` or no flag instead",
);
}
if let decode::ImportModule::Named(module) = &import.module {
bail!( bail!(
"import from `{}` module not allowed with `--no-modules`; \ "import from `{}` module not allowed with `--no-modules`; \
use `--nodejs` or `--browser` instead", use `--nodejs`, `--browser`, or no flag instead",
module module
); );
} }
} }
// FIXME: currently we require that local JS snippets are written in ES
// module syntax for imports/exports, but nodejs uses CommonJS to handle
// this meaning that local JS snippets are basically guaranteed to be
// incompatible. We need to implement a pass that translates the ES
// module syntax in the snippet to a CommonJS module, which is in theory
// not that hard but is a chunk of work to do.
if is_local_snippet && self.cx.config.mode.nodejs() {
// have a small unergonomic escape hatch for our webidl-tests tests
if env::var("WBINDGEN_I_PROMISE_JS_SYNTAX_WORKS_IN_NODE").is_err() {
bail!(
"local JS snippets are not supported with `--nodejs`; \
see rustwasm/rfcs#6 for more details, but this restriction \
will be lifted in the future"
);
}
}
// Similar to `--no-modules`, only allow vendor prefixes basically for web // Similar to `--no-modules`, only allow vendor prefixes basically for web
// apis, shouldn't be necessary for things like npm packages or other // apis, shouldn't be necessary for things like npm packages or other
// imported items. // imported items.
@ -2769,7 +2879,15 @@ impl<'a, 'b> SubContext<'a, 'b> {
if let Some(vendor_prefixes) = vendor_prefixes { if let Some(vendor_prefixes) = vendor_prefixes {
assert!(vendor_prefixes.len() > 0); assert!(vendor_prefixes.len() > 0);
if let Some(module) = &import.module { if is_local_snippet {
bail!(
"local JS snippets do not support vendor prefixes for \
the import of `{}` with a polyfill of `{}`",
item,
&vendor_prefixes[0]
);
}
if let decode::ImportModule::Named(module) = &import.module {
bail!( bail!(
"import of `{}` from `{}` has a polyfill of `{}` listed, but "import of `{}` from `{}` has a polyfill of `{}` listed, but
vendor prefixes aren't supported when importing from modules", vendor prefixes aren't supported when importing from modules",
@ -2792,20 +2910,36 @@ impl<'a, 'b> SubContext<'a, 'b> {
}); });
} }
let name = import.js_namespace.as_ref().map(|s| &**s).unwrap_or(item); let (name, field) = match import.js_namespace {
let field = if import.js_namespace.is_some() { Some(ns) => (ns, Some(item)),
Some(item) None => (item, None),
} else {
None
}; };
Ok(match import.module { Ok(match import.module {
Some(module) => Import::Module { decode::ImportModule::Named(module) if is_local_snippet => Import::LocalModule {
module, module,
name, name,
field, field,
}, },
None => Import::Global { name, field }, decode::ImportModule::Named(module) => Import::Module {
module,
name,
field,
},
decode::ImportModule::Inline(idx) => {
let offset = *self
.cx
.snippet_offsets
.get(self.program.unique_crate_identifier)
.unwrap_or(&0);
Import::InlineJs {
unique_crate_identifier: self.program.unique_crate_identifier,
snippet_idx_in_crate: idx as usize + offset,
name,
field,
}
}
decode::ImportModule::None => Import::Global { name, field },
}) })
} }
@ -2815,17 +2949,34 @@ impl<'a, 'b> SubContext<'a, 'b> {
} }
} }
#[derive(Hash, Eq, PartialEq)]
pub enum ImportModule<'a> {
Named(&'a str),
Inline(&'a str, usize),
None,
}
impl<'a> Import<'a> { impl<'a> Import<'a> {
fn module(&self) -> Option<&'a str> { fn module(&self) -> ImportModule<'a> {
match self { match self {
Import::Module { module, .. } => Some(module), Import::Module { module, .. } | Import::LocalModule { module, .. } => {
_ => None, ImportModule::Named(module)
}
Import::InlineJs {
unique_crate_identifier,
snippet_idx_in_crate,
..
} => ImportModule::Inline(unique_crate_identifier, *snippet_idx_in_crate),
Import::Global { .. } | Import::VendorPrefixed { .. } => ImportModule::None,
} }
} }
fn field(&self) -> Option<&'a str> { fn field(&self) -> Option<&'a str> {
match self { match self {
Import::Module { field, .. } | Import::Global { field, .. } => *field, Import::Module { field, .. }
| Import::LocalModule { field, .. }
| Import::InlineJs { field, .. }
| Import::Global { field, .. } => *field,
Import::VendorPrefixed { .. } => None, Import::VendorPrefixed { .. } => None,
} }
} }
@ -2833,6 +2984,8 @@ impl<'a> Import<'a> {
fn name(&self) -> &'a str { fn name(&self) -> &'a str {
match self { match self {
Import::Module { name, .. } Import::Module { name, .. }
| Import::LocalModule { name, .. }
| Import::InlineJs { name, .. }
| Import::Global { name, .. } | Import::Global { name, .. }
| Import::VendorPrefixed { name, .. } => *name, | Import::VendorPrefixed { name, .. } => *name,
} }

@ -17,11 +17,7 @@ pub mod wasm2es6js;
pub struct Bindgen { pub struct Bindgen {
input: Input, input: Input,
out_name: Option<String>, out_name: Option<String>,
nodejs: bool, mode: OutputMode,
nodejs_experimental_modules: bool,
browser: bool,
no_modules: bool,
no_modules_global: Option<String>,
debug: bool, debug: bool,
typescript: bool, typescript: bool,
demangle: bool, demangle: bool,
@ -39,6 +35,13 @@ pub struct Bindgen {
encode_into: EncodeInto, encode_into: EncodeInto,
} }
enum OutputMode {
Bundler,
Browser,
NoModules { global: String },
Node { experimental_modules: bool },
}
enum Input { enum Input {
Path(PathBuf), Path(PathBuf),
Module(Module, String), Module(Module, String),
@ -56,11 +59,7 @@ impl Bindgen {
Bindgen { Bindgen {
input: Input::None, input: Input::None,
out_name: None, out_name: None,
nodejs: false, mode: OutputMode::Bundler,
nodejs_experimental_modules: false,
browser: false,
no_modules: false,
no_modules_global: None,
debug: false, debug: false,
typescript: false, typescript: false,
demangle: true, demangle: true,
@ -92,29 +91,66 @@ impl Bindgen {
return self; return self;
} }
pub fn nodejs(&mut self, node: bool) -> &mut Bindgen { fn switch_mode(&mut self, mode: OutputMode, flag: &str) -> Result<(), Error> {
self.nodejs = node; match self.mode {
self OutputMode::Bundler => self.mode = mode,
_ => bail!(
"cannot specify `{}` with another output mode already specified",
flag
),
}
Ok(())
} }
pub fn nodejs_experimental_modules(&mut self, node: bool) -> &mut Bindgen { pub fn nodejs(&mut self, node: bool) -> Result<&mut Bindgen, Error> {
self.nodejs_experimental_modules = node; if node {
self self.switch_mode(
OutputMode::Node {
experimental_modules: false,
},
"--nodejs",
)?;
}
Ok(self)
} }
pub fn browser(&mut self, browser: bool) -> &mut Bindgen { pub fn nodejs_experimental_modules(&mut self, node: bool) -> Result<&mut Bindgen, Error> {
self.browser = browser; if node {
self self.switch_mode(
OutputMode::Node {
experimental_modules: true,
},
"--nodejs-experimental-modules",
)?;
}
Ok(self)
} }
pub fn no_modules(&mut self, no_modules: bool) -> &mut Bindgen { pub fn browser(&mut self, browser: bool) -> Result<&mut Bindgen, Error> {
self.no_modules = no_modules; if browser {
self self.switch_mode(OutputMode::Browser, "--browser")?;
}
Ok(self)
} }
pub fn no_modules_global(&mut self, name: &str) -> &mut Bindgen { pub fn no_modules(&mut self, no_modules: bool) -> Result<&mut Bindgen, Error> {
self.no_modules_global = Some(name.to_string()); if no_modules {
self self.switch_mode(
OutputMode::NoModules {
global: "wasm_bindgen".to_string(),
},
"--no-modules",
)?;
}
Ok(self)
}
pub fn no_modules_global(&mut self, name: &str) -> Result<&mut Bindgen, Error> {
match &mut self.mode {
OutputMode::NoModules { global } => *global = name.to_string(),
_ => bail!("can only specify `--no-modules-global` with `--no-modules`"),
}
Ok(self)
} }
pub fn debug(&mut self, debug: bool) -> &mut Bindgen { pub fn debug(&mut self, debug: bool) -> &mut Bindgen {
@ -203,8 +239,10 @@ impl Bindgen {
// a module's start function, if any, because we assume start functions // a module's start function, if any, because we assume start functions
// only show up when injected on behalf of wasm-bindgen's passes. // only show up when injected on behalf of wasm-bindgen's passes.
if module.start.is_some() { if module.start.is_some() {
bail!("wasm-bindgen is currently incompatible with modules that \ bail!(
already have a start function"); "wasm-bindgen is currently incompatible with modules that \
already have a start function"
);
} }
let mut program_storage = Vec::new(); let mut program_storage = Vec::new();
@ -263,8 +301,10 @@ impl Bindgen {
imported_functions: Default::default(), imported_functions: Default::default(),
imported_statics: Default::default(), imported_statics: Default::default(),
direct_imports: Default::default(), direct_imports: Default::default(),
local_modules: Default::default(),
start: None, start: None,
anyref: Default::default(), anyref: Default::default(),
snippet_offsets: Default::default(),
}; };
cx.anyref.enabled = self.anyref; cx.anyref.enabled = self.anyref;
cx.anyref.prepare(cx.module)?; cx.anyref.prepare(cx.module)?;
@ -275,11 +315,37 @@ impl Bindgen {
vendor_prefixes: Default::default(), vendor_prefixes: Default::default(),
} }
.generate()?; .generate()?;
let offset = cx
.snippet_offsets
.entry(program.unique_crate_identifier)
.or_insert(0);
for js in program.inline_js.iter() {
let name = format!("inline{}.js", *offset);
*offset += 1;
let path = out_dir
.join("snippets")
.join(program.unique_crate_identifier)
.join(name);
fs::create_dir_all(path.parent().unwrap())?;
fs::write(&path, js)
.with_context(|_| format!("failed to write `{}`", path.display()))?;
} }
}
// Write out all local JS snippets to the final destination now that
// we've collected them from all the programs.
for (path, contents) in cx.local_modules.iter() {
let path = out_dir.join("snippets").join(path);
fs::create_dir_all(path.parent().unwrap())?;
fs::write(&path, contents)
.with_context(|_| format!("failed to write `{}`", path.display()))?;
}
cx.finalize(stem)? cx.finalize(stem)?
}; };
let extension = if self.nodejs_experimental_modules { let extension = if self.mode.nodejs_experimental_modules() {
"mjs" "mjs"
} else { } else {
"js" "js"
@ -296,7 +362,7 @@ impl Bindgen {
let wasm_path = out_dir.join(format!("{}_bg", stem)).with_extension("wasm"); let wasm_path = out_dir.join(format!("{}_bg", stem)).with_extension("wasm");
if self.nodejs { if self.mode.nodejs() {
let js_path = wasm_path.with_extension(extension); let js_path = wasm_path.with_extension(extension);
let shim = self.generate_node_wasm_import(&module, &wasm_path); let shim = self.generate_node_wasm_import(&module, &wasm_path);
fs::write(&js_path, shim) fs::write(&js_path, shim)
@ -325,7 +391,7 @@ impl Bindgen {
let mut shim = String::new(); let mut shim = String::new();
if self.nodejs_experimental_modules { if self.mode.nodejs_experimental_modules() {
for (i, module) in imports.iter().enumerate() { for (i, module) in imports.iter().enumerate() {
shim.push_str(&format!("import * as import{} from '{}';\n", i, module)); shim.push_str(&format!("import * as import{} from '{}';\n", i, module));
} }
@ -357,7 +423,7 @@ impl Bindgen {
} }
shim.push_str("let imports = {};\n"); shim.push_str("let imports = {};\n");
for (i, module) in imports.iter().enumerate() { for (i, module) in imports.iter().enumerate() {
if self.nodejs_experimental_modules { if self.mode.nodejs_experimental_modules() {
shim.push_str(&format!("imports['{}'] = import{};\n", module, i)); shim.push_str(&format!("imports['{}'] = import{};\n", module, i));
} else { } else {
shim.push_str(&format!("imports['{0}'] = require('{0}');\n", module)); shim.push_str(&format!("imports['{0}'] = require('{0}');\n", module));
@ -371,7 +437,7 @@ impl Bindgen {
", ",
)); ));
if self.nodejs_experimental_modules { if self.mode.nodejs_experimental_modules() {
for entry in m.exports.iter() { for entry in m.exports.iter() {
shim.push_str("export const "); shim.push_str("export const ");
shim.push_str(&entry.name); shim.push_str(&entry.name);
@ -567,3 +633,43 @@ fn demangle(module: &mut Module) {
} }
} }
} }
impl OutputMode {
fn nodejs_experimental_modules(&self) -> bool {
match self {
OutputMode::Node {
experimental_modules,
} => *experimental_modules,
_ => false,
}
}
fn nodejs(&self) -> bool {
match self {
OutputMode::Node { .. } => true,
_ => false,
}
}
fn no_modules(&self) -> bool {
match self {
OutputMode::NoModules { .. } => true,
_ => false,
}
}
fn always_run_in_browser(&self) -> bool {
match self {
OutputMode::Browser => true,
OutputMode::NoModules { .. } => true,
_ => false,
}
}
fn browser(&self) -> bool {
match self {
OutputMode::Browser => true,
_ => false,
}
}
}

@ -506,6 +506,7 @@ impl<'a> BackgroundChild<'a> {
cmd.stdout(Stdio::piped()) cmd.stdout(Stdio::piped())
.stderr(Stdio::piped()) .stderr(Stdio::piped())
.stdin(Stdio::null()); .stdin(Stdio::null());
log::debug!("executing {:?}", cmd);
let mut child = cmd let mut child = cmd
.spawn() .spawn()
.context(format!("failed to spawn {:?} binary", path))?; .context(format!("failed to spawn {:?} binary", path))?;

@ -107,7 +107,8 @@ fn rmain() -> Result<(), Error> {
shell.status("Executing bindgen..."); shell.status("Executing bindgen...");
let mut b = Bindgen::new(); let mut b = Bindgen::new();
b.debug(debug) b.debug(debug)
.nodejs(node) .nodejs(node)?
.browser(!node)?
.input_module(module, wasm) .input_module(module, wasm)
.keep_debug(false) .keep_debug(false)
.emit_start(false) .emit_start(false)

@ -5,7 +5,6 @@ use std::path::Path;
use failure::{format_err, Error, ResultExt}; use failure::{format_err, Error, ResultExt};
use rouille::{Request, Response, Server}; use rouille::{Request, Response, Server};
use wasm_bindgen_cli_support::wasm2es6js::Config;
pub fn spawn( pub fn spawn(
addr: &SocketAddr, addr: &SocketAddr,
@ -23,9 +22,9 @@ pub fn spawn(
__wbgtest_console_log, __wbgtest_console_log,
__wbgtest_console_info, __wbgtest_console_info,
__wbgtest_console_warn, __wbgtest_console_warn,
__wbgtest_console_error __wbgtest_console_error,
default as init,
}} from './{0}'; }} from './{0}';
import * as wasm from './{0}_bg';
// Now that we've gotten to the point where JS is executing, update our // Now that we've gotten to the point where JS is executing, update our
// status text as at this point we should be asynchronously fetching the // status text as at this point we should be asynchronously fetching the
@ -33,9 +32,7 @@ pub fn spawn(
document.getElementById('output').textContent = "Loading wasm module..."; document.getElementById('output').textContent = "Loading wasm module...";
async function main(test) {{ async function main(test) {{
// this is a facet of using wasm2es6js, a hack until browsers have const wasm = await init('./{0}_bg.wasm');
// native ESM support for wasm modules.
await wasm.booted;
const cx = new Context(); const cx = new Context();
window.on_console_debug = __wbgtest_console_debug; window.on_console_debug = __wbgtest_console_debug;
@ -65,25 +62,6 @@ pub fn spawn(
let js_path = tmpdir.join("run.js"); let js_path = tmpdir.join("run.js");
fs::write(&js_path, js_to_execute).context("failed to write JS file")?; fs::write(&js_path, js_to_execute).context("failed to write JS file")?;
// No browser today supports a wasm file as ES modules natively, so we need
// to shim it. Use `wasm2es6js` here to fetch an appropriate URL and look
// like an ES module with the wasm module under the hood.
//
// TODO: don't reparse the wasm module here, should pass the
// `Module struct` directly from the output of
// `wasm-bindgen` previously here and avoid unnecessary
// parsing.
let wasm_name = format!("{}_bg.wasm", module);
let wasm = fs::read(tmpdir.join(&wasm_name))?;
let output = Config::new()
.fetch(Some(format!("/{}", wasm_name)))
.generate(&wasm)?;
let (js, wasm) = output.js_and_wasm()?;
let wasm = wasm.unwrap();
fs::write(tmpdir.join(format!("{}_bg.js", module)), js).context("failed to write JS file")?;
fs::write(tmpdir.join(format!("{}_bg.wasm", module)), wasm)
.context("failed to write wasm file")?;
// For now, always run forever on this port. We may update this later! // For now, always run forever on this port. We may update this later!
let tmpdir = tmpdir.to_path_buf(); let tmpdir = tmpdir.to_path_buf();
let srv = Server::new(addr, move |request| { let srv = Server::new(addr, move |request| {

@ -88,9 +88,9 @@ fn rmain(args: &Args) -> Result<(), Error> {
let mut b = Bindgen::new(); let mut b = Bindgen::new();
b.input_path(input) b.input_path(input)
.nodejs(args.flag_nodejs) .nodejs(args.flag_nodejs)?
.browser(args.flag_browser) .browser(args.flag_browser)?
.no_modules(args.flag_no_modules) .no_modules(args.flag_no_modules)?
.debug(args.flag_debug) .debug(args.flag_debug)
.demangle(!args.flag_no_demangle) .demangle(!args.flag_no_demangle)
.keep_debug(args.flag_keep_debug) .keep_debug(args.flag_keep_debug)
@ -98,7 +98,7 @@ fn rmain(args: &Args) -> Result<(), Error> {
.remove_producers_section(args.flag_remove_producers_section) .remove_producers_section(args.flag_remove_producers_section)
.typescript(typescript); .typescript(typescript);
if let Some(ref name) = args.flag_no_modules_global { if let Some(ref name) = args.flag_no_modules_global {
b.no_modules_global(name); b.no_modules_global(name)?;
} }
if let Some(ref name) = args.flag_out_name { if let Some(ref name) = args.flag_out_name {
b.out_name(name); b.out_name(name);

@ -10,7 +10,7 @@ use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser); wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen(module = "./tests/headless.js")] #[wasm_bindgen(module = "/tests/headless.js")]
extern "C" { extern "C" {
fn is_array_values_supported() -> bool; fn is_array_values_supported() -> bool;
} }

@ -9,6 +9,7 @@ documentation = "https://docs.rs/wasm-bindgen"
description = """ description = """
The part of the implementation of the `#[wasm_bindgen]` attribute that is not in the shared backend crate The part of the implementation of the `#[wasm_bindgen]` attribute that is not in the shared backend crate
""" """
edition = '2018'
[features] [features]
spans = ["wasm-bindgen-backend/spans"] spans = ["wasm-bindgen-backend/spans"]

@ -12,8 +12,8 @@ extern crate wasm_bindgen_backend as backend;
extern crate wasm_bindgen_shared as shared; extern crate wasm_bindgen_shared as shared;
use backend::{Diagnostic, TryToTokens}; use backend::{Diagnostic, TryToTokens};
pub use parser::BindgenAttrs; pub use crate::parser::BindgenAttrs;
use parser::MacroParse; use crate::parser::MacroParse;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::ToTokens; use quote::ToTokens;
use quote::TokenStreamExt; use quote::TokenStreamExt;

@ -33,6 +33,7 @@ macro_rules! attrgen {
(static_method_of, StaticMethodOf(Span, Ident)), (static_method_of, StaticMethodOf(Span, Ident)),
(js_namespace, JsNamespace(Span, Ident)), (js_namespace, JsNamespace(Span, Ident)),
(module, Module(Span, String, Span)), (module, Module(Span, String, Span)),
(inline_js, InlineJs(Span, String, Span)),
(getter, Getter(Span, Option<Ident>)), (getter, Getter(Span, Option<Ident>)),
(setter, Setter(Span, Option<Ident>)), (setter, Setter(Span, Option<Ident>)),
(indexing_getter, IndexingGetter(Span)), (indexing_getter, IndexingGetter(Span)),
@ -339,12 +340,12 @@ impl<'a> ConvertToAst<BindgenAttrs> for &'a mut syn::ItemStruct {
} }
} }
impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn { impl<'a> ConvertToAst<(BindgenAttrs, &'a ast::ImportModule)> for syn::ForeignItemFn {
type Target = ast::ImportKind; type Target = ast::ImportKind;
fn convert( fn convert(
self, self,
(opts, module): (BindgenAttrs, &'a Option<String>), (opts, module): (BindgenAttrs, &'a ast::ImportModule),
) -> Result<Self::Target, Diagnostic> { ) -> Result<Self::Target, Diagnostic> {
let wasm = function_from_decl( let wasm = function_from_decl(
&self.ident, &self.ident,
@ -543,12 +544,12 @@ impl ConvertToAst<BindgenAttrs> for syn::ForeignItemType {
} }
} }
impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemStatic { impl<'a> ConvertToAst<(BindgenAttrs, &'a ast::ImportModule)> for syn::ForeignItemStatic {
type Target = ast::ImportKind; type Target = ast::ImportKind;
fn convert( fn convert(
self, self,
(opts, module): (BindgenAttrs, &'a Option<String>), (opts, module): (BindgenAttrs, &'a ast::ImportModule),
) -> Result<Self::Target, Diagnostic> { ) -> Result<Self::Target, Diagnostic> {
if self.mutability.is_some() { if self.mutability.is_some() {
bail_span!(self.mutability, "cannot import mutable globals yet") bail_span!(self.mutability, "cannot import mutable globals yet")
@ -1084,8 +1085,27 @@ impl MacroParse<BindgenAttrs> for syn::ItemForeignMod {
)); ));
} }
} }
for mut item in self.items.into_iter() { let module = match opts.module() {
if let Err(e) = item.macro_parse(program, &opts) { Some((name, span)) => {
if opts.inline_js().is_some() {
let msg = "cannot specify both `module` and `inline_js`";
errors.push(Diagnostic::span_error(span, msg));
}
ast::ImportModule::Named(name.to_string(), span)
}
None => {
match opts.inline_js() {
Some((js, span)) => {
let i = program.inline_js.len();
program.inline_js.push(js.to_string());
ast::ImportModule::Inline(i, span)
}
None => ast::ImportModule::None
}
}
};
for item in self.items.into_iter() {
if let Err(e) = item.macro_parse(program, module.clone()) {
errors.push(e); errors.push(e);
} }
} }
@ -1095,11 +1115,11 @@ impl MacroParse<BindgenAttrs> for syn::ItemForeignMod {
} }
} }
impl<'a> MacroParse<&'a BindgenAttrs> for syn::ForeignItem { impl MacroParse<ast::ImportModule> for syn::ForeignItem {
fn macro_parse( fn macro_parse(
mut self, mut self,
program: &mut ast::Program, program: &mut ast::Program,
opts: &'a BindgenAttrs, module: ast::ImportModule,
) -> Result<(), Diagnostic> { ) -> Result<(), Diagnostic> {
let item_opts = { let item_opts = {
let attrs = match self { let attrs = match self {
@ -1110,11 +1130,7 @@ impl<'a> MacroParse<&'a BindgenAttrs> for syn::ForeignItem {
}; };
BindgenAttrs::find(attrs)? BindgenAttrs::find(attrs)?
}; };
let module = item_opts let js_namespace = item_opts.js_namespace().cloned();
.module()
.or(opts.module())
.map(|s| s.0.to_string());
let js_namespace = item_opts.js_namespace().or(opts.js_namespace()).cloned();
let kind = match self { let kind = match self {
syn::ForeignItem::Fn(f) => f.convert((item_opts, &module))?, syn::ForeignItem::Fn(f) => f.convert((item_opts, &module))?,
syn::ForeignItem::Type(t) => t.convert(item_opts)?, syn::ForeignItem::Type(t) => t.convert(item_opts)?,

@ -0,0 +1,15 @@
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(module = "./foo.js")]
extern {
fn wut();
}
#[wasm_bindgen(module = "../foo.js")]
extern {
fn wut();
}
fn main() {}

@ -0,0 +1,14 @@
error: relative module paths aren't supported yet
--> $DIR/import-local.rs:5:25
|
5 | #[wasm_bindgen(module = "./foo.js")]
| ^^^^^^^^^^
error: relative module paths aren't supported yet
--> $DIR/import-local.rs:10:25
|
10 | #[wasm_bindgen(module = "../foo.js")]
| ^^^^^^^^^^^
error: aborting due to 2 previous errors

@ -14,16 +14,23 @@ macro_rules! shared_api {
imports: Vec<Import<'a>>, imports: Vec<Import<'a>>,
structs: Vec<Struct<'a>>, structs: Vec<Struct<'a>>,
typescript_custom_sections: Vec<&'a str>, typescript_custom_sections: Vec<&'a str>,
// version: &'a str, local_modules: Vec<LocalModule<'a>>,
// schema_version: &'a str, inline_js: Vec<&'a str>,
unique_crate_identifier: &'a str,
} }
struct Import<'a> { struct Import<'a> {
module: Option<&'a str>, module: ImportModule<'a>,
js_namespace: Option<&'a str>, js_namespace: Option<&'a str>,
kind: ImportKind<'a>, kind: ImportKind<'a>,
} }
enum ImportModule<'a> {
None,
Named(&'a str),
Inline(u32),
}
enum ImportKind<'a> { enum ImportKind<'a> {
Function(ImportFunction<'a>), Function(ImportFunction<'a>),
Static(ImportStatic<'a>), Static(ImportStatic<'a>),
@ -113,6 +120,11 @@ macro_rules! shared_api {
readonly: bool, readonly: bool,
comments: Vec<&'a str>, comments: Vec<&'a str>,
} }
struct LocalModule<'a> {
identifier: &'a str,
contents: &'a str,
}
} }
}; // end of mac case }; // end of mac case
} // end of mac definition } // end of mac definition

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlAnchorElement; use web_sys::HtmlAnchorElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_a() -> HtmlAnchorElement; fn new_a() -> HtmlAnchorElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlBodyElement; use web_sys::HtmlBodyElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_body() -> HtmlBodyElement; fn new_body() -> HtmlBodyElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlBrElement; use web_sys::HtmlBrElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_br() -> HtmlBrElement; fn new_br() -> HtmlBrElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::{HtmlButtonElement, HtmlFormElement, Node}; use web_sys::{HtmlButtonElement, HtmlFormElement, Node};
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_button() -> HtmlButtonElement; fn new_button() -> HtmlButtonElement;
fn new_form() -> HtmlFormElement; fn new_form() -> HtmlFormElement;

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlDivElement; use web_sys::HtmlDivElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_div() -> HtmlDivElement; fn new_div() -> HtmlDivElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::Element; use web_sys::Element;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_div() -> Element; fn new_div() -> Element;
} }

@ -6,7 +6,7 @@ use wasm_bindgen_futures::JsFuture;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::Event; use web_sys::Event;
#[wasm_bindgen(module = "./tests/wasm/event.js")] #[wasm_bindgen(module = "/tests/wasm/event.js")]
extern "C" { extern "C" {
fn new_event() -> Promise; fn new_event() -> Promise;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlHeadElement; use web_sys::HtmlHeadElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_head() -> HtmlHeadElement; fn new_head() -> HtmlHeadElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::Headers; use web_sys::Headers;
#[wasm_bindgen(module = "./tests/wasm/headers.js")] #[wasm_bindgen(module = "/tests/wasm/headers.js")]
extern "C" { extern "C" {
fn new_headers() -> Headers; fn new_headers() -> Headers;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlHeadingElement; use web_sys::HtmlHeadingElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_heading() -> HtmlHeadingElement; fn new_heading() -> HtmlHeadingElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlHrElement; use web_sys::HtmlHrElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_hr() -> HtmlHrElement; fn new_hr() -> HtmlHrElement;
} }

@ -3,7 +3,7 @@ use wasm_bindgen::JsCast;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlElement; use web_sys::HtmlElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_html() -> HtmlElement; fn new_html() -> HtmlElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlHtmlElement; use web_sys::HtmlHtmlElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_html() -> HtmlHtmlElement; fn new_html() -> HtmlHtmlElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlInputElement; use web_sys::HtmlInputElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_input() -> HtmlInputElement; fn new_input() -> HtmlInputElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen_test::*;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use web_sys::HtmlMenuElement; use web_sys::HtmlMenuElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_menu() -> HtmlMenuElement; fn new_menu() -> HtmlMenuElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen_test::*;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use web_sys::HtmlMenuItemElement; use web_sys::HtmlMenuItemElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_menuitem() -> HtmlMenuItemElement; fn new_menuitem() -> HtmlMenuItemElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlMetaElement; use web_sys::HtmlMetaElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_meta() -> HtmlMetaElement; fn new_meta() -> HtmlMetaElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlMeterElement; use web_sys::HtmlMeterElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_meter() -> HtmlMeterElement; fn new_meter() -> HtmlMeterElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlModElement; use web_sys::HtmlModElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_del() -> HtmlModElement; fn new_del() -> HtmlModElement;
fn new_ins() -> HtmlModElement; fn new_ins() -> HtmlModElement;

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlOListElement; use web_sys::HtmlOListElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_olist() -> HtmlOListElement; fn new_olist() -> HtmlOListElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlOptGroupElement; use web_sys::HtmlOptGroupElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_optgroup() -> HtmlOptGroupElement; fn new_optgroup() -> HtmlOptGroupElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlOptionsCollection; use web_sys::HtmlOptionsCollection;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_food_options_collection() -> HtmlOptionsCollection; fn new_food_options_collection() -> HtmlOptionsCollection;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlOutputElement; use web_sys::HtmlOutputElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_output() -> HtmlOutputElement; fn new_output() -> HtmlOutputElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlParagraphElement; use web_sys::HtmlParagraphElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_paragraph() -> HtmlParagraphElement; fn new_paragraph() -> HtmlParagraphElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlParamElement; use web_sys::HtmlParamElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_param() -> HtmlParamElement; fn new_param() -> HtmlParamElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlPreElement; use web_sys::HtmlPreElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_pre() -> HtmlPreElement; fn new_pre() -> HtmlPreElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlProgressElement; use web_sys::HtmlProgressElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_progress() -> HtmlProgressElement; fn new_progress() -> HtmlProgressElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlQuoteElement; use web_sys::HtmlQuoteElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_quote() -> HtmlQuoteElement; fn new_quote() -> HtmlQuoteElement;
} }

@ -10,7 +10,7 @@ use wasm_bindgen_futures::JsFuture;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::Response; use web_sys::Response;
#[wasm_bindgen(module = "./tests/wasm/response.js")] #[wasm_bindgen(module = "/tests/wasm/response.js")]
extern "C" { extern "C" {
fn new_response() -> Response; fn new_response() -> Response;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlScriptElement; use web_sys::HtmlScriptElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_script() -> HtmlScriptElement; fn new_script() -> HtmlScriptElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlSelectElement; use web_sys::HtmlSelectElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_select_with_food_opts() -> HtmlSelectElement; fn new_select_with_food_opts() -> HtmlSelectElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlSlotElement; use web_sys::HtmlSlotElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_slot() -> HtmlSlotElement; fn new_slot() -> HtmlSlotElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlSpanElement; use web_sys::HtmlSpanElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_span() -> HtmlSpanElement; fn new_span() -> HtmlSpanElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlStyleElement; use web_sys::HtmlStyleElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_style() -> HtmlStyleElement; fn new_style() -> HtmlStyleElement;
} }

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::{HtmlTableCaptionElement, HtmlTableElement, HtmlTableSectionElement}; use web_sys::{HtmlTableCaptionElement, HtmlTableElement, HtmlTableSectionElement};
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_table() -> HtmlTableElement; fn new_table() -> HtmlTableElement;
fn new_caption() -> HtmlTableCaptionElement; fn new_caption() -> HtmlTableCaptionElement;

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::HtmlTitleElement; use web_sys::HtmlTitleElement;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_title() -> HtmlTitleElement; fn new_title() -> HtmlTitleElement;
} }

@ -14,7 +14,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::WebGlRenderingContext; use web_sys::WebGlRenderingContext;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_webgl_rendering_context() -> WebGlRenderingContext; fn new_webgl_rendering_context() -> WebGlRenderingContext;
// TODO: Add a function to create another type to test here. // TODO: Add a function to create another type to test here.

@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
use web_sys::XPathResult; use web_sys::XPathResult;
#[wasm_bindgen(module = "./tests/wasm/element.js")] #[wasm_bindgen(module = "/tests/wasm/element.js")]
extern "C" { extern "C" {
fn new_xpath_result() -> XPathResult; fn new_xpath_result() -> XPathResult;
} }

@ -21,14 +21,13 @@ fn main() {
let out_file = out_dir.join(path.file_name().unwrap()).with_extension("rs"); let out_file = out_dir.join(path.file_name().unwrap()).with_extension("rs");
let js_file = path.with_extension("js").canonicalize().unwrap();
generated_rust.push_str(&format!( generated_rust.push_str(&format!(
r#" r#"
pub mod import_script {{ pub mod import_script {{
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
#[wasm_bindgen(module = r"{}")] #[wasm_bindgen(module = "/{}.js")]
extern "C" {{ extern "C" {{
fn not_actually_a_function{1}(x: &str); fn not_actually_a_function{1}(x: &str);
}} }}
@ -41,7 +40,7 @@ fn main() {
}} }}
}} }}
"#, "#,
js_file.display(), path.file_stem().unwrap().to_str().unwrap(),
i i
)); ));

@ -272,7 +272,7 @@ impl<'src> FirstPassRecord<'src> {
) { ) {
let variants = &enum_.values.body.list; let variants = &enum_.values.body.list;
program.imports.push(backend::ast::Import { program.imports.push(backend::ast::Import {
module: None, module: backend::ast::ImportModule::None,
js_namespace: None, js_namespace: None,
kind: backend::ast::ImportKind::Enum(backend::ast::ImportEnum { kind: backend::ast::ImportKind::Enum(backend::ast::ImportEnum {
vis: public(), vis: public(),
@ -463,7 +463,7 @@ impl<'src> FirstPassRecord<'src> {
self.append_required_features_doc(&import_function, &mut doc, extra); self.append_required_features_doc(&import_function, &mut doc, extra);
import_function.doc_comment = doc; import_function.doc_comment = doc;
module.imports.push(backend::ast::Import { module.imports.push(backend::ast::Import {
module: None, module: backend::ast::ImportModule::None,
js_namespace: Some(raw_ident(self_name)), js_namespace: Some(raw_ident(self_name)),
kind: backend::ast::ImportKind::Function(import_function), kind: backend::ast::ImportKind::Function(import_function),
}); });
@ -539,7 +539,7 @@ impl<'src> FirstPassRecord<'src> {
import_type.doc_comment = doc_comment; import_type.doc_comment = doc_comment;
program.imports.push(backend::ast::Import { program.imports.push(backend::ast::Import {
module: None, module: backend::ast::ImportModule::None,
js_namespace: None, js_namespace: None,
kind: backend::ast::ImportKind::Type(import_type), kind: backend::ast::ImportKind::Type(import_type),
}); });

@ -1,6 +1,6 @@
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
#[wasm_bindgen(module = "../defined-in-js")] #[wasm_bindgen(module = "/defined-in-js.js")]
extern "C" { extern "C" {
fn name() -> String; fn name() -> String;

2
examples/raytrace-parallel/.gitignore vendored Normal file

@ -0,0 +1,2 @@
raytrace_parallel.js
raytrace_parallel_bg.wasm

@ -0,0 +1,21 @@
[package]
name = "without-a-bundler-no-modules"
version = "0.1.0"
authors = ["The wasm-bindgen Developers"]
edition = "2018"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2.37"
[dependencies.web-sys]
version = "0.3.4"
features = [
'Document',
'Element',
'HtmlElement',
'Node',
'Window',
]

@ -0,0 +1,21 @@
# Without a Bundler Using `--no-modules`
[View documentation for this example online][dox]
[dox]: https://rustwasm.github.io/wasm-bindgen/examples/without-a-bundler-no-modules.html
You can build the example locally with:
```
$ wasm-pack build --target no-modules
```
and then opening `index.html` in a browser should run the example!
Note that this example is in contrast to the [without a bundler][wab] example
which performs a similar purpose except it uses `--no-modules` instead of
`--browser`. The main difference here is how the shim JS and module are loaded,
where this example uses old-school `script` tags while `--browser` uses ES
modules.
[wab]: https://github.com/rustwasm/wasm-bindgen/tree/master/examples/without-a-bundler

@ -0,0 +1,30 @@
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
</head>
<body>
<!-- Include the JS generated by `wasm-pack build` -->
<script src='pkg/without_a_bundler_no_modules.js'></script>
<script type=module>
// Like with the `--browser` output the exports are immediately available
// but they won't work until we initialize the module. Unlike `--browser`,
// however, the globals are all stored on a `wasm_bindgen` global. The
// global itself is the initialization function and then the properties of
// the global are all the exported functions.
//
// Note that the name `wasm_bindgen` can be configured with the
// `--no-modules-global` CLI flag
const { add } = wasm_bindgen;
async function run() {
await wasm_bindgen('./pkg/without_a_bundler_no_modules_bg.wasm');
const result = add(1, 2);
console.log(`1 + 2 = ${result}`);
}
run();
</script>
</body>
</html>

@ -0,0 +1,24 @@
use wasm_bindgen::prelude::*;
// Called when the wasm module is instantiated
#[wasm_bindgen(start)]
pub fn main() -> Result<(), JsValue> {
// Use `web_sys`'s global `window` function to get a handle on the global
// window object.
let window = web_sys::window().expect("no global `window` exists");
let document = window.document().expect("should have a document on window");
let body = document.body().expect("document should have a body");
// Manufacture the element we're gonna append
let val = document.create_element("p")?;
val.set_inner_html("Hello from Rust!");
body.append_child(&val)?;
Ok(())
}
#[wasm_bindgen]
pub fn add(a: u32, b: u32) -> u32 {
a + b
}

@ -7,7 +7,11 @@
You can build the example locally with: You can build the example locally with:
``` ```
$ wasm-pack build --target no-modules $ cargo build --target wasm32-unknown-unknown --release
$ cargo run -p wasm-bindgen-cli --bin wasm-bindgen -- \
../../target/wasm32-unknown-unknown/release/without_a_bundler.wasm \
--out-dir pkg \
--browser
``` ```
and then opening `index.html` in a browser should run the example! and then opening `index.html` in a browser should run the example!

@ -0,0 +1,15 @@
#!/bin/sh
set -ex
# Note that typically we'd use `wasm-pack` to build the crate, but the
# `--browser` flag is very new to `wasm-bindgen` and as such doesn't have
# support in `wasm-pack` yet. Support will be added soon though!
cargo build --target wasm32-unknown-unknown --release
cargo run --manifest-path ../../crates/cli/Cargo.toml \
--bin wasm-bindgen -- \
../../target/wasm32-unknown-unknown/release/without_a_bundler.wasm --out-dir pkg \
--browser
python3 -m http.server

@ -3,33 +3,29 @@
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/> <meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
</head> </head>
<body> <body>
<!-- <!-- Note the usage of `type=module` here as this is an ES6 module -->
This is the JS generated by the `wasm-pack` build command <script type="module">
// Use ES module import syntax to import functionality from the module
The script here will define a `wasm_bindgen` global where all // that we have compiled.
functionality can be accessed such as instantiation and the actual //
functions (examples below). // Note that the `default` import is an initialization function which
// will "boot" the module and make it ready to use. Currently browsers
You can customize the name of the file here with the `out-name` CLI flag // don't support natively imported WebAssembly as an ES module, but
to `wasm-bindgen`. You can also customize the name of the global exported // eventually the manual initialization won't be required!
here with the `no-modules-global` flag. import { add, default as init } from './pkg/without_a_bundler.js';
-->
<script src='./pkg/without_a_bundler.js'></script>
<script>
// Import functionality from the wasm module, but note that it's not quite
// ready to be used just yet.
const { add } = wasm_bindgen;
async function run() { async function run() {
// First up we need to actually load the wasm file, so we use the // First up we need to actually load the wasm file, so we use the
// exported global to inform it where the wasm file is located on the // default export to inform it where the wasm file is located on the
// server, and then we wait on the returned promies to wait for the // server, and then we wait on the returned promise to wait for the
// wasm to be loaded. // wasm to be loaded.
// //
// Note that instead of a string here you can also pass in an instance // Note that instead of a string here you can also pass in an instance
// of `WebAssembly.Module` which allows you to compile your own module. // of `WebAssembly.Module` which allows you to compile your own module.
await wasm_bindgen('./pkg/without_a_bundler_bg.wasm'); // Also note that the promise, when resolved, yields the wasm module's
// exports which is the same as importing the `*_bg` module in other
// modes
await init('./pkg/without_a_bundler_bg.wasm');
// And afterwards we can use all the functionality defined in wasm. // And afterwards we can use all the functionality defined in wasm.
const result = add(1, 2); const result = add(1, 2);

@ -27,6 +27,7 @@
- [web-sys: A TODO MVC App](./examples/todomvc.md) - [web-sys: A TODO MVC App](./examples/todomvc.md)
- [Reference](./reference/index.md) - [Reference](./reference/index.md)
- [Deployment](./reference/deployment.md) - [Deployment](./reference/deployment.md)
- [JS snippets](./reference/js-snippets.md)
- [Passing Rust Closures to JS](./reference/passing-rust-closures-to-js.md) - [Passing Rust Closures to JS](./reference/passing-rust-closures-to-js.md)
- [Receiving JS Closures in Rust](./reference/receiving-js-closures-in-rust.md) - [Receiving JS Closures in Rust](./reference/receiving-js-closures-in-rust.md)
- [`Promise`s and `Future`s](./reference/js-promises-and-rust-futures.md) - [`Promise`s and `Future`s](./reference/js-promises-and-rust-futures.md)

@ -4,12 +4,15 @@
[code]: https://github.com/rustwasm/wasm-bindgen/tree/master/examples/without-a-bundler [code]: https://github.com/rustwasm/wasm-bindgen/tree/master/examples/without-a-bundler
This example shows how the `--no-modules` flag can be used load code in a This example shows how the `--browser` flag can be used load code in a
browser directly. For this deployment strategy bundlers like Webpack are not browser directly. For this deployment strategy bundlers like Webpack are not
required. For more information on deployment see the [dedicated required. For more information on deployment see the [dedicated
documentation][deployment]. documentation][deployment].
First let's take a look at the code and see how when we're using `--no-modules` > **Note**: the `--browser` flag is quite new to `wasm-bindgen`, and does not
> currently have support in `wasm-pack` yet. Support will be added soon though!
First let's take a look at the code and see how when we're using `--browser`
we're not actually losing any functionality! we're not actually losing any functionality!
```rust ```rust
@ -22,7 +25,33 @@ Otherwise the rest of the deployment magic happens in `index.html`:
{{#include ../../../examples/without-a-bundler/index.html}} {{#include ../../../examples/without-a-bundler/index.html}}
``` ```
And that's it! Be sure to read up on the [deployment options][deployment] to see what it And that's it! Be sure to read up on the [deployment options][deployment] to see
means to deploy without a bundler. what it means to deploy without a bundler.
[deployment]: ../reference/deployment.html [deployment]: ../reference/deployment.html
## Using the older `--no-modules`
[View full source code][code]
[code]: https://github.com/rustwasm/wasm-bindgen/tree/master/examples/without-a-bundler-no-modules
The older version of using `wasm-bindgen` without a bundler is to use the
`--no-modules` flag to the `wasm-bindgen` CLI. This corresponds to `--target
no-modules` in `wasm-pack`.
While similar to the newer `--browser`, the `--no-modules` flag has a few
caveats:
* It does not support [local JS snippets][snippets]
* It does not generate an ES module
With that in mind the main difference is how the wasm/JS code is loaded, and
here's an example of loading the output of `wasm-pack` for the same module as
above.
```html
{{#include ../../../examples/without-a-bundler-no-modules/index.html}}
```
[snippets]: ../reference/js-snippets.html

@ -29,26 +29,29 @@ necessary.
If you're not using a bundler but you're still running code in a web browser, If you're not using a bundler but you're still running code in a web browser,
`wasm-bindgen` still supports this! For this use case you'll want to use the `wasm-bindgen` still supports this! For this use case you'll want to use the
`--no-modules` flag. You can check out a [full example][nomex] in the `--browser` flag. You can check out a [full example][nomex] in the
documentation, but the highlights of this output are: documentation, but the highlights of this output are:
* When using `wasm-pack` you'll pass `--target no-modules`, and when using * When using `wasm-bindgen` directly you'll pass `--browser`.
`wasm-bindgen` directly you'll pass `--no-modules`.
* The output can natively be included on a web page, and doesn't require any * The output can natively be included on a web page, and doesn't require any
further postprocessing. further postprocessing. The output is included as an ES module.
* The `--no-modules` mode is not able to use NPM dependencies nor local JS * The `--browser` mode is not able to use NPM dependencies.
snippets (both currently [proposed][rfc1] [features][rfc2])
* You'll want to review the [browser requirements] for `wasm-bindgen` because * You'll want to review the [browser requirements] for `wasm-bindgen` because
no polyfills will be available. no polyfills will be available.
> **Note**: currently `--browser` is not supported in `wasm-pack` because it is
> a very recent addition to `wasm-bindgen`, but support will be added soon!
[nomex]: ../examples/without-a-bundler.html [nomex]: ../examples/without-a-bundler.html
[rfc1]: https://github.com/rustwasm/rfcs/pull/6 [rfc1]: https://github.com/rustwasm/rfcs/pull/6
[rfc2]: https://github.com/rustwasm/rfcs/pull/8 [rfc2]: https://github.com/rustwasm/rfcs/pull/8
[browser requirements]: browser-support.html [browser requirements]: browser-support.html
Despite these limitations almost all code today is compatible with The `wasm-bindgen` CLI also supports an output mode called `--no-modules` which
`--no-modules`, but this area is actively being worked on to improve the is similar to `--browser` in that it requires manual initialization of the wasm
experience so the experience here may be tweaked over time! and is intended to be included in web pages without any further postprocessing.
See the [without a bundler example][nomex] for some more information about
`--no-modules`, which corresponds to `--target no-modules` in `wasm-pack`.
## Node.js ## Node.js

@ -0,0 +1,78 @@
# JS Snippets
Often when developing a crate you want to run on the web you'll want to include
some JS code here and there. While [`js-sys`](https://docs.rs/js-sys) and
[`web-sys`](https://docs.rs/web-sys) cover many needs they don't cover
everything, so `wasm-bindgen` supports the ability to write JS code next to your
Rust code and have it included in the final output artifact.
To include a local JS file, you'll use the `#[wasm_bindgen(module)]` macro:
```rust
#[wasm_bindgen(module = "/js/foo.js")]
extern "C" {
fn add(a: u32, b: u32) -> u32;
}
```
This declaration indicates that all the functions contained in the `extern`
block are imported from the file `/js/foo.js`, where the root is relative to the
crate root (where `Cargo.toml` is located).
The `/js/foo.js` file will make its way to the final output when `wasm-bindgen`
executes, so you can use the `module` annotation in a library without having to
worry users of your library!
The JS file itself must be written with ES module syntax:
```js
export function add(a, b) {
return a + b;
}
```
A full design of this feature can be found in [RFC 6] as well if you're
interested!
[RFC 6]: https://github.com/rustwasm/rfcs/pull/6
### Using `inline_js`
In addition to `module = "..."` if you're a macro author you also have the
ability to use the `inline_js` attribute:
```rust
#[wasm_bindgen(inline_js = "export function add(a, b) { return a + b; }")]
extern "C" {
fn add(a: u32, b: u32) -> u32;
}
```
Using `inline_js` indicates that the JS module is specified inline in the
attribute itself, and no files are loaded from the filesystem. They have the
same limitations and caveats as when using `module`, but can sometimes be easier
to generate for macros themselves. It's not recommended for hand-written code to
make use of `inline_js` but instead to leverage `module` where possible.
### Caveats
While quite useful local JS snippets currently suffer from a few caveats which
are important to be aware of. Many of these are temporary though!
* Currently `import` statements are not supported in the JS file. This is a
restriction we may lift in the future once we settle on a good way to support
this. For now, though, js snippets must be standalone modules and can't import
from anything else.
* Only `--browser` and the default bundler output mode are supported. To support
`--nodejs` we'd need to translate ES module syntax to CommonJS (this is
planned to be done, just hasn't been done yet). Additionally to support
`--no-modules` we'd have to similarly translate from ES modules to something
else.
* Paths in `module = "..."` must currently start with `/`, or be rooted at the
crate root. It is intended to eventually support relative paths like `./` and
`../`, but it's currently believed that this requires more support in
the Rust `proc_macro` crate.
As above, more detail about caveats can be found in [RFC 6].

@ -110,8 +110,11 @@ impl Slab {
} }
fn internal_error(msg: &str) -> ! { fn internal_error(msg: &str) -> ! {
let msg = if cfg!(debug_assertions) { msg } else { "" }; if cfg!(debug_assertions) {
super::throw_str(msg) super::throw_str(msg)
} else {
std::process::abort()
}
} }
// Whoa, there's two `tl` modules here! That's currently intention, but for sort // Whoa, there's two `tl` modules here! That's currently intention, but for sort

2
tests/headless.rs → tests/headless/main.rs Executable file → Normal file

@ -37,3 +37,5 @@ extern "C" {
fn can_log_html_strings() { fn can_log_html_strings() {
log("<script>alert('lol')</script>"); log("<script>alert('lol')</script>");
} }
pub mod snippets;

@ -0,0 +1,58 @@
use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*;
#[wasm_bindgen(module = "/tests/headless/snippets1.js")]
extern {
fn get_two() -> u32;
#[wasm_bindgen(js_name = get_stateful)]
fn get_stateful1() -> u32;
}
#[wasm_bindgen(module = "/tests/headless/snippets1.js")]
extern {
#[wasm_bindgen(js_name = get_stateful)]
fn get_stateful2() -> u32;
}
#[wasm_bindgen_test]
fn test_get_two() {
assert_eq!(get_two(), 2);
}
#[wasm_bindgen_test]
fn stateful_deduplicated() {
assert_eq!(get_stateful1(), 1);
assert_eq!(get_stateful2(), 2);
assert_eq!(get_stateful1(), 3);
assert_eq!(get_stateful2(), 4);
}
#[wasm_bindgen(inline_js = "export function get_three() { return 3; }")]
extern {
fn get_three() -> u32;
}
#[wasm_bindgen_test]
fn test_get_three() {
assert_eq!(get_three(), 3);
}
#[wasm_bindgen(inline_js = "let a = 0; export function get() { a += 1; return a; }")]
extern {
#[wasm_bindgen(js_name = get)]
fn duplicate1() -> u32;
}
#[wasm_bindgen(inline_js = "let a = 0; export function get() { a += 1; return a; }")]
extern {
#[wasm_bindgen(js_name = get)]
fn duplicate2() -> u32;
}
#[wasm_bindgen_test]
fn duplicate_inline_not_unified() {
assert_eq!(duplicate1(), 1);
assert_eq!(duplicate2(), 1);
assert_eq!(duplicate1(), 2);
assert_eq!(duplicate2(), 2);
}

@ -0,0 +1,9 @@
export function get_two() {
return 2;
}
let a = 0;
export function get_stateful() {
a += 1;
return a;
}