Merge pull request #1295 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

View File

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

View File

@ -40,10 +40,15 @@ jobs:
- template: ci/azure-install-sccache.yml
- script: cargo test --target wasm32-unknown-unknown
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
displayName: "js-sys test suite"
- script: cargo test --target wasm32-unknown-unknown -p webidl-tests
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"
displayName: "web-sys build"
@ -94,6 +99,8 @@ jobs:
- template: ci/azure-install-sccache.yml
- script: cargo test -p wasm-bindgen-webidl
- script: cargo test -p webidl-tests --target wasm32-unknown-unknown
env:
WBINDGEN_I_PROMISE_JS_SYNTAX_WORKS_IN_NODE: 1
- job: test_ui
displayName: "Run UI tests"

View File

@ -12,3 +12,14 @@ steps:
Write-Host "##vso[task.setvariable variable=GECKODRIVER;]$pwd\geckodriver.exe"
displayName: "Download Geckodriver (Windows)"
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' )

View File

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

View File

@ -2,6 +2,7 @@ use proc_macro2::{Ident, Span};
use shared;
use syn;
use Diagnostic;
use std::hash::{Hash, Hasher};
/// An abstract syntax tree representing a rust program. Contains
/// extra information for joining up this rust code with javascript.
@ -24,6 +25,8 @@ pub struct Program {
pub dictionaries: Vec<Dictionary>,
/// custom typescript sections to be included in the definition file
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
@ -66,11 +69,37 @@ pub enum MethodSelf {
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
#[derive(Clone)]
pub struct Import {
pub module: Option<String>,
pub module: ImportModule,
pub js_namespace: Option<Ident>,
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))]
#[derive(Clone)]
pub enum ImportKind {

View File

@ -94,25 +94,45 @@ impl TryToTokens for ast::Program {
shared::SCHEMA_VERSION,
shared::version()
);
let encoded = encode::encode(self)?;
let mut bytes = Vec::new();
bytes.push((prefix_json.len() >> 0) as u8);
bytes.push((prefix_json.len() >> 8) as u8);
bytes.push((prefix_json.len() >> 16) as u8);
bytes.push((prefix_json.len() >> 24) as u8);
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_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! {
#[allow(non_upper_case_globals)]
#[cfg(target_arch = "wasm32")]
#[link_section = "__wasm_bindgen_unstable"]
#[doc(hidden)]
#[allow(clippy::all)]
pub static #generated_static_name: [u8; #generated_static_length] =
*#generated_static_value;
pub static #generated_static_name: [u8; #generated_static_length] = {
static _INCLUDED_FILES: &[&str] = &[#(#file_dependencies),*];
*#generated_static_value
};
})
.to_tokens(tokens);

View File

@ -1,40 +1,96 @@
use proc_macro2::{Ident, Span};
use std::cell::RefCell;
use std::collections::HashMap;
use proc_macro2::{Ident, Span};
use std::env;
use std::fs;
use std::path::PathBuf;
use util::ShortHash;
use ast;
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 i = Interner::new();
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 {
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 {
fn new() -> 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 {
let mut map = self.map.borrow_mut();
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) }
self.intern_str(&s.to_string())
}
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()
.map(|x| -> &'a str { &x })
.collect(),
// version: shared::version(),
// schema_version: shared::SCHEMA_VERSION.to_string(),
local_modules: intern
.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> {
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)),
kind: shared_import_kind(&i.kind, intern)?,
})

View File

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

View File

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

View File

@ -1,8 +1,9 @@
use crate::decode;
use crate::descriptor::{Descriptor, VectorKind};
use crate::{Bindgen, EncodeInto};
use crate::{Bindgen, EncodeInto, OutputMode};
use failure::{bail, Error, ResultExt};
use std::collections::{HashMap, HashSet};
use std::env;
use walrus::{MemoryId, Module};
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
/// identifiers we've already imported from the module to what they're
/// 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
/// imported, used to generate new identifiers.
@ -53,6 +54,16 @@ pub struct Context<'a> {
pub interpreter: &'a mut Interpreter,
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,
}
@ -100,6 +111,20 @@ enum Import<'a> {
name: &'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
/// with it, like `webkitAudioPrefix`. The `name` is the name to test
/// whether it's prefixed.
@ -124,28 +149,62 @@ impl<'a> Context<'a> {
self.globals.push_str(c);
self.typescript.push_str(c);
}
let global = if self.use_node_require() {
if contents.starts_with("class") {
format!("{1}\nmodule.exports.{0} = {0};\n", name, contents)
} else {
format!("module.exports.{} = {};\n", name, contents)
let global = match self.config.mode {
OutputMode::Node {
experimental_modules: false,
} => {
if contents.starts_with("class") {
format!("{1}\nmodule.exports.{0} = {0};\n", name, contents)
} else {
format!("module.exports.{} = {};\n", name, contents)
}
}
} else if self.config.no_modules {
if contents.starts_with("class") {
format!("{1}\n__exports.{0} = {0};\n", name, contents)
} else {
format!("__exports.{} = {};\n", name, contents)
OutputMode::NoModules { .. } => {
if contents.starts_with("class") {
format!("{1}\n__exports.{0} = {0};\n", name, contents)
} else {
format!("__exports.{} = {};\n", name, contents)
}
}
} else {
if contents.starts_with("function") {
format!("export function {}{}\n", name, &contents[8..])
} else if contents.starts_with("class") {
format!("export {}\n", contents)
} else {
format!("export const {} = {};\n", name, contents)
OutputMode::Bundler
| OutputMode::Node {
experimental_modules: true,
} => {
if contents.starts_with("function") {
format!("export function {}{}\n", name, &contents[8..])
} else if contents.starts_with("class") {
format!("export {}\n", contents)
} else {
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);
if self.config.mode.browser() {
self.global(&format!("__exports.{} = {0};", name));
}
}
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> {
// 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| {
me.anyref.import_xform(
"__wbindgen_placeholder__",
@ -529,7 +752,7 @@ impl<'a> Context<'a> {
})?;
self.bind("__wbindgen_module", &|me| {
if !me.config.no_modules {
if !me.config.mode.no_modules() && !me.config.mode.browser() {
bail!(
"`wasm_bindgen::module` is currently only supported with \
--no-modules"
@ -548,10 +771,28 @@ impl<'a> Context<'a> {
))
})?;
closures::rewrite(self).with_context(|_| "failed to generate internal closure shims")?;
self.write_classes()?;
self.anyref.run(self.module)?;
self.bind("__wbindgen_throw", &|me| {
me.expose_get_string_from_wasm();
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
// we expose a function which initializes it
self.bind("__wbindgen_init_anyref_table", &|me| {
@ -589,84 +830,12 @@ impl<'a> Context<'a> {
Ok(String::from("function(i) { dropObject(i); }"))
})?;
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.
//
// 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();
}
}
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);
if mem.import.is_none() {
bail!("must impot a shared memory with threads")
}
Ok(())
}
fn gen_init(&mut self, module_name: &str, needs_manual_start: bool) -> String {
let mem = self.module.memories.get(self.memory);
let (init_memory1, init_memory2) = if mem.import.is_some() {
let mut memory = String::from("new WebAssembly.Memory({");
memory.push_str(&format!("initial:{}", mem.initial));
if let Some(max) = mem.maximum {
@ -676,158 +845,64 @@ impl<'a> Context<'a> {
memory.push_str(",shared:true");
}
memory.push_str("})");
format!(
"\
(function() {{
var wasm;
var memory;
const __exports = {{}};
{globals}
function init(module_or_path, maybe_memory) {{
let result;
const imports = {{ './{module}': __exports }};
if (module_or_path instanceof WebAssembly.Module) {{
memory = __exports.memory = maybe_memory;
result = WebAssembly.instantiate(module_or_path, imports)
.then(instance => {{
return {{ instance, module: module_or_path }}
}});
}} else {{
memory = __exports.memory = {init_memory};
const response = fetch(module_or_path);
if (typeof WebAssembly.instantiateStreaming === 'function') {{
result = WebAssembly.instantiateStreaming(response, 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 response
.then(r => r.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, imports));
}});
}} else {{
result = response
.then(r => r.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, imports));
}}
}}
return result.then(({{instance, module}}) => {{
wasm = init.wasm = instance.exports;
init.__wbindgen_wasm_instance = instance;
init.__wbindgen_wasm_module = module;
init.__wbindgen_wasm_memory = __exports.memory;
{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"),
init_memory = memory,
start = if has_start_function {
"wasm.__wbindgen_start();"
} 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 {
""
},
self.imports_post.push_str("let memory;\n");
(
format!("memory = __exports.memory = maybe_memory;"),
format!("memory = __exports.memory = {};", memory),
)
} 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,
)
(String::new(), String::new())
};
while js.contains("\n\n\n") {
js = js.replace("\n\n\n", "\n\n");
}
Ok((js, self.typescript.clone()))
format!(
"\
function init(module_or_path, maybe_memory) {{
let result;
const imports = {{ './{module}': __exports }};
if (module_or_path instanceof WebAssembly.Module) {{
{init_memory1}
result = WebAssembly.instantiate(module_or_path, imports)
.then(instance => {{
return {{ instance, module: module_or_path }};
}});
}} else {{
{init_memory2}
const response = fetch(module_or_path);
if (typeof WebAssembly.instantiateStreaming === 'function') {{
result = WebAssembly.instantiateStreaming(response, 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 response
.then(r => r.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, imports));
}});
}} else {{
result = response
.then(r => r.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, imports));
}}
}}
return result.then(({{instance, module}}) => {{
wasm = instance.exports;
init.__wbindgen_wasm_module = module;
{start}
return wasm;
}});
}}
",
module = module_name,
init_memory1 = init_memory1,
init_memory2 = init_memory2,
start = if needs_manual_start {
"wasm.__wbindgen_start();"
} else {
""
},
)
}
fn bind(
@ -1235,8 +1310,7 @@ impl<'a> Context<'a> {
passStringToWasm = function(arg) {{ {} }};
}}
",
use_encode_into,
use_encode,
use_encode_into, use_encode,
));
}
_ => {
@ -1364,14 +1438,14 @@ impl<'a> Context<'a> {
}
fn expose_text_processor(&mut self, s: &str) {
if self.config.nodejs_experimental_modules {
if self.config.mode.nodejs_experimental_modules() {
self.imports
.push_str(&format!("import {{ {} }} from 'util';\n", 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!("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!(
"
const l{0} = typeof {0} === 'undefined' ? \
@ -2008,7 +2082,7 @@ impl<'a> Context<'a> {
}
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 {
@ -2052,23 +2126,41 @@ impl<'a> Context<'a> {
.or_insert_with(|| {
let name = generate_identifier(import.name(), imported_identifiers);
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 {
imports.push_str(&format!(
"const {} = require(String.raw`{}`).{};\n",
name,
module,
path,
import.name()
));
} else if import.name() == name {
imports
.push_str(&format!("import {{ {} }} from '{}';\n", name, module));
imports.push_str(&format!("import {{ {} }} from '{}';\n", name, path));
} else {
imports.push_str(&format!(
"import {{ {} as {} }} from '{}';\n",
import.name(),
name,
module
path
));
}
name
@ -2290,24 +2382,6 @@ impl<'a> Context<'a> {
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) {
assert!(self.config.anyref);
if !self.should_write_global("anyref_table") {
@ -2368,6 +2442,14 @@ impl<'a> Context<'a> {
impl<'a, 'b> SubContext<'a, 'b> {
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() {
self.generate_export(f).with_context(|_| {
format!(
@ -2752,16 +2834,44 @@ impl<'a, 'b> SubContext<'a, 'b> {
) -> Result<Import<'b>, Error> {
// First up, imports don't work at all in `--no-modules` mode as we're
// not sure how to import them.
if self.cx.config.no_modules {
if let Some(module) = &import.module {
let is_local_snippet = match 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!(
"import from `{}` module not allowed with `--no-modules`; \
use `--nodejs` or `--browser` instead",
use `--nodejs`, `--browser`, or no flag instead",
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
// apis, shouldn't be necessary for things like npm packages or other
// imported items.
@ -2769,7 +2879,15 @@ impl<'a, 'b> SubContext<'a, 'b> {
if let Some(vendor_prefixes) = vendor_prefixes {
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!(
"import of `{}` from `{}` has a polyfill of `{}` listed, but
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 field = if import.js_namespace.is_some() {
Some(item)
} else {
None
let (name, field) = match import.js_namespace {
Some(ns) => (ns, Some(item)),
None => (item, None),
};
Ok(match import.module {
Some(module) => Import::Module {
decode::ImportModule::Named(module) if is_local_snippet => Import::LocalModule {
module,
name,
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> {
fn module(&self) -> Option<&'a str> {
fn module(&self) -> ImportModule<'a> {
match self {
Import::Module { module, .. } => Some(module),
_ => None,
Import::Module { module, .. } | Import::LocalModule { module, .. } => {
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> {
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,
}
}
@ -2833,6 +2984,8 @@ impl<'a> Import<'a> {
fn name(&self) -> &'a str {
match self {
Import::Module { name, .. }
| Import::LocalModule { name, .. }
| Import::InlineJs { name, .. }
| Import::Global { name, .. }
| Import::VendorPrefixed { name, .. } => *name,
}

View File

@ -17,11 +17,7 @@ pub mod wasm2es6js;
pub struct Bindgen {
input: Input,
out_name: Option<String>,
nodejs: bool,
nodejs_experimental_modules: bool,
browser: bool,
no_modules: bool,
no_modules_global: Option<String>,
mode: OutputMode,
debug: bool,
typescript: bool,
demangle: bool,
@ -39,6 +35,13 @@ pub struct Bindgen {
encode_into: EncodeInto,
}
enum OutputMode {
Bundler,
Browser,
NoModules { global: String },
Node { experimental_modules: bool },
}
enum Input {
Path(PathBuf),
Module(Module, String),
@ -56,11 +59,7 @@ impl Bindgen {
Bindgen {
input: Input::None,
out_name: None,
nodejs: false,
nodejs_experimental_modules: false,
browser: false,
no_modules: false,
no_modules_global: None,
mode: OutputMode::Bundler,
debug: false,
typescript: false,
demangle: true,
@ -92,29 +91,66 @@ impl Bindgen {
return self;
}
pub fn nodejs(&mut self, node: bool) -> &mut Bindgen {
self.nodejs = node;
self
fn switch_mode(&mut self, mode: OutputMode, flag: &str) -> Result<(), Error> {
match self.mode {
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 {
self.nodejs_experimental_modules = node;
self
pub fn nodejs(&mut self, node: bool) -> Result<&mut Bindgen, Error> {
if node {
self.switch_mode(
OutputMode::Node {
experimental_modules: false,
},
"--nodejs",
)?;
}
Ok(self)
}
pub fn browser(&mut self, browser: bool) -> &mut Bindgen {
self.browser = browser;
self
pub fn nodejs_experimental_modules(&mut self, node: bool) -> Result<&mut Bindgen, Error> {
if node {
self.switch_mode(
OutputMode::Node {
experimental_modules: true,
},
"--nodejs-experimental-modules",
)?;
}
Ok(self)
}
pub fn no_modules(&mut self, no_modules: bool) -> &mut Bindgen {
self.no_modules = no_modules;
self
pub fn browser(&mut self, browser: bool) -> Result<&mut Bindgen, Error> {
if browser {
self.switch_mode(OutputMode::Browser, "--browser")?;
}
Ok(self)
}
pub fn no_modules_global(&mut self, name: &str) -> &mut Bindgen {
self.no_modules_global = Some(name.to_string());
self
pub fn no_modules(&mut self, no_modules: bool) -> Result<&mut Bindgen, Error> {
if no_modules {
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 {
@ -203,8 +239,10 @@ impl Bindgen {
// a module's start function, if any, because we assume start functions
// only show up when injected on behalf of wasm-bindgen's passes.
if module.start.is_some() {
bail!("wasm-bindgen is currently incompatible with modules that \
already have a start function");
bail!(
"wasm-bindgen is currently incompatible with modules that \
already have a start function"
);
}
let mut program_storage = Vec::new();
@ -263,8 +301,10 @@ impl Bindgen {
imported_functions: Default::default(),
imported_statics: Default::default(),
direct_imports: Default::default(),
local_modules: Default::default(),
start: None,
anyref: Default::default(),
snippet_offsets: Default::default(),
};
cx.anyref.enabled = self.anyref;
cx.anyref.prepare(cx.module)?;
@ -275,11 +315,37 @@ impl Bindgen {
vendor_prefixes: Default::default(),
}
.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)?
};
let extension = if self.nodejs_experimental_modules {
let extension = if self.mode.nodejs_experimental_modules() {
"mjs"
} else {
"js"
@ -296,7 +362,7 @@ impl Bindgen {
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 shim = self.generate_node_wasm_import(&module, &wasm_path);
fs::write(&js_path, shim)
@ -325,7 +391,7 @@ impl Bindgen {
let mut shim = String::new();
if self.nodejs_experimental_modules {
if self.mode.nodejs_experimental_modules() {
for (i, module) in imports.iter().enumerate() {
shim.push_str(&format!("import * as import{} from '{}';\n", i, module));
}
@ -357,7 +423,7 @@ impl Bindgen {
}
shim.push_str("let imports = {};\n");
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));
} else {
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() {
shim.push_str("export const ");
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,
}
}
}

View File

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

View File

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

View File

@ -5,7 +5,6 @@ use std::path::Path;
use failure::{format_err, Error, ResultExt};
use rouille::{Request, Response, Server};
use wasm_bindgen_cli_support::wasm2es6js::Config;
pub fn spawn(
addr: &SocketAddr,
@ -23,9 +22,9 @@ pub fn spawn(
__wbgtest_console_log,
__wbgtest_console_info,
__wbgtest_console_warn,
__wbgtest_console_error
__wbgtest_console_error,
default as init,
}} from './{0}';
import * as wasm from './{0}_bg';
// 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
@ -33,9 +32,7 @@ pub fn spawn(
document.getElementById('output').textContent = "Loading wasm module...";
async function main(test) {{
// this is a facet of using wasm2es6js, a hack until browsers have
// native ESM support for wasm modules.
await wasm.booted;
const wasm = await init('./{0}_bg.wasm');
const cx = new Context();
window.on_console_debug = __wbgtest_console_debug;
@ -65,25 +62,6 @@ pub fn spawn(
let js_path = tmpdir.join("run.js");
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!
let tmpdir = tmpdir.to_path_buf();
let srv = Server::new(addr, move |request| {

View File

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

View File

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

View File

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

View File

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

View File

@ -33,6 +33,7 @@ macro_rules! attrgen {
(static_method_of, StaticMethodOf(Span, Ident)),
(js_namespace, JsNamespace(Span, Ident)),
(module, Module(Span, String, Span)),
(inline_js, InlineJs(Span, String, Span)),
(getter, Getter(Span, Option<Ident>)),
(setter, Setter(Span, Option<Ident>)),
(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;
fn convert(
self,
(opts, module): (BindgenAttrs, &'a Option<String>),
(opts, module): (BindgenAttrs, &'a ast::ImportModule),
) -> Result<Self::Target, Diagnostic> {
let wasm = function_from_decl(
&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;
fn convert(
self,
(opts, module): (BindgenAttrs, &'a Option<String>),
(opts, module): (BindgenAttrs, &'a ast::ImportModule),
) -> Result<Self::Target, Diagnostic> {
if self.mutability.is_some() {
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() {
if let Err(e) = item.macro_parse(program, &opts) {
let module = match opts.module() {
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);
}
}
@ -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(
mut self,
program: &mut ast::Program,
opts: &'a BindgenAttrs,
module: ast::ImportModule,
) -> Result<(), Diagnostic> {
let item_opts = {
let attrs = match self {
@ -1110,11 +1130,7 @@ impl<'a> MacroParse<&'a BindgenAttrs> for syn::ForeignItem {
};
BindgenAttrs::find(attrs)?
};
let module = item_opts
.module()
.or(opts.module())
.map(|s| s.0.to_string());
let js_namespace = item_opts.js_namespace().or(opts.js_namespace()).cloned();
let js_namespace = item_opts.js_namespace().cloned();
let kind = match self {
syn::ForeignItem::Fn(f) => f.convert((item_opts, &module))?,
syn::ForeignItem::Type(t) => t.convert(item_opts)?,

View File

@ -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() {}

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,14 +21,13 @@ fn main() {
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!(
r#"
pub mod import_script {{
use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*;
#[wasm_bindgen(module = r"{}")]
#[wasm_bindgen(module = "/{}.js")]
extern "C" {{
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
));

View File

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

View File

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

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

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

View File

@ -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',
]

View File

@ -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

View File

@ -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>

View File

@ -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
}

View File

@ -7,7 +7,11 @@
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!

View File

@ -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

View File

@ -3,33 +3,29 @@
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
</head>
<body>
<!--
This is the JS generated by the `wasm-pack` build command
The script here will define a `wasm_bindgen` global where all
functionality can be accessed such as instantiation and the actual
functions (examples below).
You can customize the name of the file here with the `out-name` CLI flag
to `wasm-bindgen`. You can also customize the name of the global exported
here with the `no-modules-global` flag.
-->
<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;
<!-- Note the usage of `type=module` here as this is an ES6 module -->
<script type="module">
// Use ES module import syntax to import functionality from the module
// that we have compiled.
//
// Note that the `default` import is an initialization function which
// will "boot" the module and make it ready to use. Currently browsers
// don't support natively imported WebAssembly as an ES module, but
// eventually the manual initialization won't be required!
import { add, default as init } from './pkg/without_a_bundler.js';
async function run() {
// 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
// server, and then we wait on the returned promies to wait for the
// default export to inform it where the wasm file is located on the
// server, and then we wait on the returned promise to wait for the
// wasm to be loaded.
//
// 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.
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.
const result = add(1, 2);

View File

@ -27,6 +27,7 @@
- [web-sys: A TODO MVC App](./examples/todomvc.md)
- [Reference](./reference/index.md)
- [Deployment](./reference/deployment.md)
- [JS snippets](./reference/js-snippets.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)
- [`Promise`s and `Future`s](./reference/js-promises-and-rust-futures.md)

View File

@ -4,12 +4,15 @@
[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
required. For more information on deployment see the [dedicated
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!
```rust
@ -22,7 +25,33 @@ Otherwise the rest of the deployment magic happens in `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
means to deploy without a bundler.
And that's it! Be sure to read up on the [deployment options][deployment] to see
what it means to deploy without a bundler.
[deployment]: ../reference/deployment.html
## 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

View File

@ -29,26 +29,29 @@ necessary.
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
`--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:
* When using `wasm-pack` you'll pass `--target no-modules`, and when using
`wasm-bindgen` directly you'll pass `--no-modules`.
* When using `wasm-bindgen` directly you'll pass `--browser`.
* The output can natively be included on a web page, and doesn't require any
further postprocessing.
* The `--no-modules` mode is not able to use NPM dependencies nor local JS
snippets (both currently [proposed][rfc1] [features][rfc2])
further postprocessing. The output is included as an ES module.
* The `--browser` mode is not able to use NPM dependencies.
* You'll want to review the [browser requirements] for `wasm-bindgen` because
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
[rfc1]: https://github.com/rustwasm/rfcs/pull/6
[rfc2]: https://github.com/rustwasm/rfcs/pull/8
[browser requirements]: browser-support.html
Despite these limitations almost all code today is compatible with
`--no-modules`, but this area is actively being worked on to improve the
experience so the experience here may be tweaked over time!
The `wasm-bindgen` CLI also supports an output mode called `--no-modules` which
is similar to `--browser` in that it requires manual initialization of the wasm
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

View File

@ -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].

View File

@ -110,8 +110,11 @@ impl Slab {
}
fn internal_error(msg: &str) -> ! {
let msg = if cfg!(debug_assertions) { msg } else { "" };
super::throw_str(msg)
if cfg!(debug_assertions) {
super::throw_str(msg)
} else {
std::process::abort()
}
}
// 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
View File

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

View File

@ -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);
}

View File

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