mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-16 18:20:51 +00:00
Merge pull request #1295 from alexcrichton/js-snippets
Implement the local JS snippets RFC
This commit is contained in:
commit
20c25ca922
@ -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']
|
||||
|
@ -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"
|
||||
|
@ -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' )
|
||||
|
@ -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"
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)?,
|
||||
})
|
||||
|
@ -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;
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))?;
|
||||
|
@ -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)
|
||||
|
@ -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| {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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"]
|
||||
|
@ -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;
|
||||
|
@ -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)?,
|
||||
|
15
crates/macro/ui-tests/import-local.rs
Normal file
15
crates/macro/ui-tests/import-local.rs
Normal 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() {}
|
14
crates/macro/ui-tests/import-local.stderr
Normal file
14
crates/macro/ui-tests/import-local.stderr
Normal 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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
));
|
||||
|
||||
|
@ -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),
|
||||
});
|
||||
|
@ -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
2
examples/raytrace-parallel/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
raytrace_parallel.js
|
||||
raytrace_parallel_bg.wasm
|
21
examples/without-a-bundler-no-modules/Cargo.toml
Normal file
21
examples/without-a-bundler-no-modules/Cargo.toml
Normal 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',
|
||||
]
|
21
examples/without-a-bundler-no-modules/README.md
Normal file
21
examples/without-a-bundler-no-modules/README.md
Normal 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
|
30
examples/without-a-bundler-no-modules/index.html
Normal file
30
examples/without-a-bundler-no-modules/index.html
Normal 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>
|
24
examples/without-a-bundler-no-modules/src/lib.rs
Normal file
24
examples/without-a-bundler-no-modules/src/lib.rs
Normal 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
|
||||
}
|
@ -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!
|
||||
|
15
examples/without-a-bundler/build.sh
Executable file
15
examples/without-a-bundler/build.sh
Executable 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
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
78
guide/src/reference/js-snippets.md
Normal file
78
guide/src/reference/js-snippets.md
Normal 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].
|
@ -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
2
tests/headless.rs → tests/headless/main.rs
Executable file → Normal file
@ -37,3 +37,5 @@ extern "C" {
|
||||
fn can_log_html_strings() {
|
||||
log("<script>alert('lol')</script>");
|
||||
}
|
||||
|
||||
pub mod snippets;
|
58
tests/headless/snippets.rs
Normal file
58
tests/headless/snippets.rs
Normal 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);
|
||||
}
|
9
tests/headless/snippets1.js
Normal file
9
tests/headless/snippets1.js
Normal file
@ -0,0 +1,9 @@
|
||||
export function get_two() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
let a = 0;
|
||||
export function get_stateful() {
|
||||
a += 1;
|
||||
return a;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user