Skip to content

Commit 36db788

Browse files
authored
Merge pull request #1689 from alexcrichton/direct-imports
Support emitting direct imports in wasm files
2 parents 38b8232 + 2aac606 commit 36db788

File tree

2 files changed

+126
-23
lines changed

2 files changed

+126
-23
lines changed

crates/cli-support/src/js/mod.rs

Lines changed: 111 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::webidl::{AuxValue, Binding};
66
use crate::webidl::{JsImport, JsImportName, NonstandardWebidlSection, WasmBindgenAux};
77
use crate::{Bindgen, EncodeInto, OutputMode};
88
use failure::{bail, Error, ResultExt};
9-
use std::collections::{BTreeMap, HashMap, HashSet};
9+
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
1010
use std::fs;
1111
use std::path::{Path, PathBuf};
1212
use walrus::{ExportId, ImportId, MemoryId, Module};
@@ -248,7 +248,7 @@ impl<'a> Context<'a> {
248248
OutputMode::NoModules { global } => {
249249
js.push_str("const __exports = {};\n");
250250
js.push_str("let wasm;\n");
251-
init = self.gen_init(needs_manual_start);
251+
init = self.gen_init(needs_manual_start, None)?;
252252
footer.push_str(&format!(
253253
"self.{} = Object.assign(init, __exports);\n",
254254
global
@@ -309,7 +309,7 @@ impl<'a> Context<'a> {
309309
// as the default export of the module.
310310
OutputMode::Web => {
311311
self.imports_post.push_str("let wasm;\n");
312-
init = self.gen_init(needs_manual_start);
312+
init = self.gen_init(needs_manual_start, Some(&mut imports))?;
313313
footer.push_str("export default init;\n");
314314
}
315315
}
@@ -435,7 +435,11 @@ impl<'a> Context<'a> {
435435
)
436436
}
437437

438-
fn gen_init(&mut self, needs_manual_start: bool) -> (String, String) {
438+
fn gen_init(
439+
&mut self,
440+
needs_manual_start: bool,
441+
mut imports: Option<&mut String>,
442+
) -> Result<(String, String), Error> {
439443
let module_name = "wbg";
440444
let mem = self.module.memories.get(self.memory);
441445
let (init_memory1, init_memory2) = if let Some(id) = mem.import {
@@ -495,6 +499,30 @@ impl<'a> Context<'a> {
495499
imports_init.push_str(";\n");
496500
}
497501

502+
let extra_modules = self
503+
.module
504+
.imports
505+
.iter()
506+
.filter(|i| !self.wasm_import_definitions.contains_key(&i.id()))
507+
.filter(|i| {
508+
// Importing memory is handled specially in this area, so don't
509+
// consider this a candidate for importing from extra modules.
510+
match i.kind {
511+
walrus::ImportKind::Memory(_) => false,
512+
_ => true,
513+
}
514+
})
515+
.map(|i| &i.module)
516+
.collect::<BTreeSet<_>>();
517+
for (i, extra) in extra_modules.iter().enumerate() {
518+
let imports = match &mut imports {
519+
Some(list) => list,
520+
None => bail!("cannot import from modules (`{}`) with `--no-modules`", extra),
521+
};
522+
imports.push_str(&format!("import * as __wbg_star{} from '{}';\n", i, extra));
523+
imports_init.push_str(&format!("imports['{}'] = __wbg_star{};\n", extra, i));
524+
}
525+
498526
let js = format!(
499527
"\
500528
function init(module{init_memory_arg}) {{
@@ -553,7 +581,7 @@ impl<'a> Context<'a> {
553581
imports_init = imports_init,
554582
);
555583

556-
(js, ts)
584+
Ok((js, ts))
557585
}
558586

559587
fn write_classes(&mut self) -> Result<(), Error> {
@@ -1104,15 +1132,17 @@ impl<'a> Context<'a> {
11041132
//
11051133
// If `ptr` and `len` are both `0` then that means it's `None`, in that case we rely upon
11061134
// the fact that `getObject(0)` is guaranteed to be `undefined`.
1107-
self.global("
1135+
self.global(
1136+
"
11081137
function getCachedStringFromWasm(ptr, len) {
11091138
if (ptr === 0) {
11101139
return getObject(len);
11111140
} else {
11121141
return getStringFromWasm(ptr, len);
11131142
}
11141143
}
1115-
");
1144+
",
1145+
);
11161146
Ok(())
11171147
}
11181148

@@ -1730,7 +1760,8 @@ impl<'a> Context<'a> {
17301760

17311761
JsImportName::LocalModule { module, name } => {
17321762
let unique_name = generate_identifier(name, &mut self.defined_identifiers);
1733-
add_module_import(format!("./snippets/{}", module), name, &unique_name);
1763+
let module = self.config.local_module_name(module);
1764+
add_module_import(module, name, &unique_name);
17341765
unique_name
17351766
}
17361767

@@ -1739,11 +1770,10 @@ impl<'a> Context<'a> {
17391770
snippet_idx_in_crate,
17401771
name,
17411772
} => {
1773+
let module = self
1774+
.config
1775+
.inline_js_module_name(unique_crate_identifier, *snippet_idx_in_crate);
17421776
let unique_name = generate_identifier(name, &mut self.defined_identifiers);
1743-
let module = format!(
1744-
"./snippets/{}/inline{}.js",
1745-
unique_crate_identifier, snippet_idx_in_crate,
1746-
);
17471777
add_module_import(module, name, &unique_name);
17481778
unique_name
17491779
}
@@ -2014,16 +2044,11 @@ impl<'a> Context<'a> {
20142044
.types
20152045
.get::<ast::WebidlFunction>(binding.webidl_ty)
20162046
.unwrap();
2017-
let js = match import {
2047+
match import {
20182048
AuxImport::Value(AuxValue::Bare(js))
20192049
if !variadic && !catch && self.import_does_not_require_glue(binding, webidl) =>
20202050
{
2021-
self.expose_not_defined();
2022-
let name = self.import_name(js)?;
2023-
format!(
2024-
"typeof {name} == 'function' ? {name} : notDefined('{name}')",
2025-
name = name,
2026-
)
2051+
self.direct_import(id, js)
20272052
}
20282053
_ => {
20292054
if assert_no_shim {
@@ -2048,11 +2073,11 @@ impl<'a> Context<'a> {
20482073
cx.invoke_import(&binding, import, bindings, args, variadic, prelude)
20492074
},
20502075
)?;
2051-
format!("function{}", js)
2076+
self.wasm_import_definitions
2077+
.insert(id, format!("function{}", js));
2078+
Ok(())
20522079
}
2053-
};
2054-
self.wasm_import_definitions.insert(id, js);
2055-
Ok(())
2080+
}
20562081
}
20572082

20582083
fn import_does_not_require_glue(
@@ -2078,6 +2103,69 @@ impl<'a> Context<'a> {
20782103
)
20792104
}
20802105

2106+
/// Emit a direct import directive that hooks up the `js` value specified to
2107+
/// the wasm import `id`.
2108+
fn direct_import(&mut self, id: ImportId, js: &JsImport) -> Result<(), Error> {
2109+
// If there's no field projection happening here and this is a direct
2110+
// import from an ES-looking module, then we can actually just hook this
2111+
// up directly in the wasm file itself. Note that this is covered in the
2112+
// various output formats as well:
2113+
//
2114+
// * `bundler` - they think wasm is an ES module anyway
2115+
// * `web` - we're sure to emit more `import` directives during
2116+
// `gen_init` and we update the import object accordingly.
2117+
// * `nodejs` - the polyfill we have for requiring a wasm file as a node
2118+
// module will naturally emit `require` directives for the module
2119+
// listed on each wasm import.
2120+
// * `no-modules` - imports aren't allowed here anyway from other
2121+
// modules and an error is generated.
2122+
if js.fields.len() == 0 {
2123+
match &js.name {
2124+
JsImportName::Module { module, name } => {
2125+
let import = self.module.imports.get_mut(id);
2126+
import.module = module.clone();
2127+
import.name = name.clone();
2128+
return Ok(());
2129+
}
2130+
JsImportName::LocalModule { module, name } => {
2131+
let module = self.config.local_module_name(module);
2132+
let import = self.module.imports.get_mut(id);
2133+
import.module = module;
2134+
import.name = name.clone();
2135+
return Ok(());
2136+
}
2137+
JsImportName::InlineJs {
2138+
unique_crate_identifier,
2139+
snippet_idx_in_crate,
2140+
name,
2141+
} => {
2142+
let module = self
2143+
.config
2144+
.inline_js_module_name(unique_crate_identifier, *snippet_idx_in_crate);
2145+
let import = self.module.imports.get_mut(id);
2146+
import.module = module;
2147+
import.name = name.clone();
2148+
return Ok(());
2149+
}
2150+
2151+
// Fall through below to requiring a JS shim to create an item
2152+
// that we can import. These are plucked from the global
2153+
// environment so there's no way right now to describe these
2154+
// imports in an ES module-like fashion.
2155+
JsImportName::Global { .. } | JsImportName::VendorPrefixed { .. } => {}
2156+
}
2157+
}
2158+
2159+
self.expose_not_defined();
2160+
let name = self.import_name(js)?;
2161+
let js = format!(
2162+
"typeof {name} == 'function' ? {name} : notDefined('{name}')",
2163+
name = name,
2164+
);
2165+
self.wasm_import_definitions.insert(id, js);
2166+
Ok(())
2167+
}
2168+
20812169
/// Generates a JS snippet appropriate for invoking `import`.
20822170
///
20832171
/// This is generating code for `binding` where `bindings` has more type

crates/cli-support/src/lib.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,21 @@ impl Bindgen {
360360
typescript: self.typescript,
361361
})
362362
}
363+
364+
fn local_module_name(&self, module: &str) -> String {
365+
format!("./snippets/{}", module)
366+
}
367+
368+
fn inline_js_module_name(
369+
&self,
370+
unique_crate_identifier: &str,
371+
snippet_idx_in_crate: usize,
372+
) -> String {
373+
format!(
374+
"./snippets/{}/inline{}.js",
375+
unique_crate_identifier, snippet_idx_in_crate,
376+
)
377+
}
363378
}
364379

365380
fn reset_indentation(s: &str) -> String {

0 commit comments

Comments
 (0)