Skip to content

Commit e62289d

Browse files
committed
Add support for version specifications
This commit adds a `#[wasm_bindgen(version = "...")]` attribute support. This information is eventually written into a `__wasm_pack_unstable` section. Currently this is a strawman for the proposal in ashleygwilliams/wasm-pack#101
1 parent d9a71b4 commit e62289d

File tree

7 files changed

+180
-0
lines changed

7 files changed

+180
-0
lines changed

DESIGN.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,34 @@ controlling precisely how imports are imported and what they map to in JS. This
886886
section is intended to hopefully be an exhaustive reference of the
887887
possibilities!
888888

889+
* `module` and `version` - we've seen `module` so far indicating where we can
890+
import items from but `version` is also allowed:
891+
892+
```rust
893+
#[wasm_bindgen(module = "moment", version = "^2.0.0")]
894+
extern {
895+
type Moment;
896+
fn moment() -> Moment;
897+
#[wasm_bindgen(method)]
898+
fn format(this: &Moment) -> String;
899+
}
900+
```
901+
902+
The `module` key is used to configure the module that each item is imported
903+
from. The `version` key does not affect the generated wasm itself but rather
904+
it's an informative directive for tools like [wasm-pack]. Tools like wasm-pack
905+
will generate a `package.json` for you and the `version` listed here, when
906+
`module` is also an NPM package, will correspond to what to write down in
907+
`package.json`.
908+
909+
In other words the usage of `module` as the name of an NPM package and
910+
`version` as the version requirement allows you to, inline in Rust, depend on
911+
the NPM ecosystem and import functionality from those packages. When bundled
912+
with a tool like [wasm-pack] everything will automatically get wired up with
913+
bundlers and you should be good to go!
914+
915+
[wasm-pack]: https://github.com/ashleygwilliams/wasm-pack
916+
889917
* `catch` - as we saw before the `catch` attribute allows catching a JS
890918
exception. This can be attached to any imported function and the function must
891919
return a `Result` where the `Err` payload is a `JsValue`, like so:

crates/backend/src/ast.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub struct Export {
2020

2121
pub struct Import {
2222
pub module: Option<String>,
23+
pub version: Option<String>,
2324
pub js_namespace: Option<syn::Ident>,
2425
pub kind: ImportKind,
2526
}
@@ -294,6 +295,7 @@ impl Program {
294295
BindgenAttrs::find(attrs)
295296
};
296297
let module = item_opts.module().or(opts.module()).map(|s| s.to_string());
298+
let version = item_opts.version().or(opts.version()).map(|s| s.to_string());
297299
let js_namespace = item_opts.js_namespace().or(opts.js_namespace());
298300
let mut kind = match item {
299301
syn::ForeignItem::Fn(f) => self.push_foreign_fn(f, item_opts),
@@ -304,6 +306,7 @@ impl Program {
304306

305307
self.imports.push(Import {
306308
module,
309+
version,
307310
js_namespace,
308311
kind,
309312
});
@@ -584,8 +587,29 @@ impl Variant {
584587

585588
impl Import {
586589
fn shared(&self) -> shared::Import {
590+
match (&self.module, &self.version) {
591+
(&Some(ref m), None) if m.starts_with("./") => {}
592+
(&Some(ref m), &Some(_)) if m.starts_with("./") => {
593+
panic!("when a module path starts with `./` that indicates \
594+
that a local file is being imported so the `version` \
595+
key cannot also be specified");
596+
}
597+
(&Some(_), &Some(_)) => {}
598+
(&Some(_), &None) => {
599+
panic!("when the `module` directive doesn't start with `./` \
600+
then it's interpreted as an NPM package which requires \
601+
a `version` to be specified as well, try using \
602+
#[wasm_bindgen(module = \"...\", version = \"...\")]")
603+
}
604+
(&None, &Some(_)) => {
605+
panic!("the #[wasm_bindgen(version = \"...\")] attribute can only \
606+
be used when `module = \"...\"` is also specified");
607+
}
608+
(&None, &None) => {}
609+
}
587610
shared::Import {
588611
module: self.module.clone(),
612+
version: self.version.clone(),
589613
js_namespace: self.js_namespace.map(|s| s.as_ref().to_string()),
590614
kind: self.kind.shared(),
591615
}
@@ -746,6 +770,16 @@ impl BindgenAttrs {
746770
.next()
747771
}
748772

773+
fn version(&self) -> Option<&str> {
774+
self.attrs
775+
.iter()
776+
.filter_map(|a| match *a {
777+
BindgenAttr::Version(ref s) => Some(&s[..]),
778+
_ => None,
779+
})
780+
.next()
781+
}
782+
749783
pub fn catch(&self) -> bool {
750784
self.attrs.iter().any(|a| match *a {
751785
BindgenAttr::Catch => true,
@@ -844,6 +878,7 @@ enum BindgenAttr {
844878
Method,
845879
JsNamespace(syn::Ident),
846880
Module(String),
881+
Version(String),
847882
Getter(Option<syn::Ident>),
848883
Setter(Option<syn::Ident>),
849884
Structural,
@@ -897,6 +932,13 @@ impl syn::synom::Synom for BindgenAttr {
897932
(s.value())
898933
)=> { BindgenAttr::Module }
899934
|
935+
do_parse!(
936+
call!(term, "version") >>
937+
punct!(=) >>
938+
s: syn!(syn::LitStr) >>
939+
(s.value())
940+
)=> { BindgenAttr::Version }
941+
|
900942
do_parse!(
901943
call!(term, "js_name") >>
902944
punct!(=) >>

crates/cli-support/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Shared support for the wasm-bindgen-cli package, an internal dependency
1414
base64 = "0.9"
1515
failure = "0.1"
1616
parity-wasm = "0.27"
17+
serde = "1.0"
18+
serde_derive = "1.0"
1719
serde_json = "1.0"
1820
wasm-bindgen-shared = { path = "../shared", version = '=0.2.5' }
1921
wasm-gc-api = "0.1"

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

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::mem;
55
use failure::{Error, ResultExt};
66
use parity_wasm::elements::*;
77
use parity_wasm;
8+
use serde_json;
89
use shared;
910
use wasm_gc;
1011

@@ -29,6 +30,7 @@ pub struct Context<'a> {
2930
pub exported_classes: HashMap<String, ExportedClass>,
3031
pub function_table_needed: bool,
3132
pub run_descriptor: &'a Fn(&str) -> Vec<u32>,
33+
pub module_versions: Vec<(String, String)>,
3234
}
3335

3436
#[derive(Default)]
@@ -341,6 +343,7 @@ impl<'a> Context<'a> {
341343

342344
self.export_table();
343345
self.gc()?;
346+
self.add_wasm_pack_section();
344347

345348
while js.contains("\n\n\n") {
346349
js = js.replace("\n\n\n", "\n\n");
@@ -1333,6 +1336,28 @@ impl<'a> Context<'a> {
13331336
self.globals.push_str(s);
13341337
self.globals.push_str("\n");
13351338
}
1339+
1340+
fn add_wasm_pack_section(&mut self) {
1341+
if self.module_versions.len() == 0 {
1342+
return
1343+
}
1344+
1345+
#[derive(Serialize)]
1346+
struct WasmPackSchema<'a> {
1347+
version: &'a str,
1348+
modules: &'a [(String, String)],
1349+
}
1350+
1351+
let contents = serde_json::to_string(&WasmPackSchema {
1352+
version: "0.0.1",
1353+
modules: &self.module_versions,
1354+
}).unwrap();
1355+
1356+
let mut section = CustomSection::default();
1357+
*section.name_mut() = "__wasm_pack_unstable".to_string();
1358+
*section.payload_mut() = contents.into_bytes();
1359+
self.module.sections_mut().push(Section::Custom(section));
1360+
}
13361361
}
13371362

13381363
impl<'a, 'b> SubContext<'a, 'b> {
@@ -1423,6 +1448,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
14231448
}
14241449

14251450
fn generate_import(&mut self, import: &shared::Import) -> Result<(), Error> {
1451+
self.validate_import_module(import)?;
14261452
match import.kind {
14271453
shared::ImportKind::Function(ref f) => {
14281454
self.generate_import_function(import, f)
@@ -1443,6 +1469,40 @@ impl<'a, 'b> SubContext<'a, 'b> {
14431469
Ok(())
14441470
}
14451471

1472+
fn validate_import_module(&mut self, import: &shared::Import)
1473+
-> Result<(), Error>
1474+
{
1475+
let version = match import.version {
1476+
Some(ref s) => s,
1477+
None => return Ok(()),
1478+
};
1479+
let module = match import.module {
1480+
Some(ref s) => s,
1481+
None => return Ok(()),
1482+
};
1483+
if module.starts_with("./") {
1484+
return Ok(())
1485+
}
1486+
let pkg = if module.starts_with("@") {
1487+
// Translate `@foo/bar/baz` to `@foo/bar` and `@foo/bar` to itself
1488+
let first_slash = match module.find('/') {
1489+
Some(i) => i,
1490+
None => {
1491+
bail!("packages starting with `@` must be of the form \
1492+
`@foo/bar`, but found: `{}`", module)
1493+
}
1494+
};
1495+
match module[first_slash + 1..].find('/') {
1496+
Some(i) => &module[..i],
1497+
None => module,
1498+
}
1499+
} else {
1500+
module.split('/').next().unwrap()
1501+
};
1502+
self.cx.module_versions.push((pkg.to_string(), version.clone()));
1503+
Ok(())
1504+
}
1505+
14461506
fn generate_import_static(
14471507
&mut self,
14481508
info: &shared::Import,

crates/cli-support/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
extern crate parity_wasm;
22
extern crate wasm_bindgen_shared as shared;
3+
#[macro_use]
4+
extern crate serde_derive;
35
extern crate serde_json;
46
extern crate wasm_gc;
57
extern crate wasmi;
@@ -131,6 +133,7 @@ impl Bindgen {
131133
config: &self,
132134
module: &mut module,
133135
function_table_needed: false,
136+
module_versions: Default::default(),
134137
run_descriptor: &|name| {
135138
let mut v = MyExternals(Vec::new());
136139
let ret = instance

crates/shared/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub struct Program {
2222
#[derive(Deserialize, Serialize)]
2323
pub struct Import {
2424
pub module: Option<String>,
25+
pub version: Option<String>,
2526
pub js_namespace: Option<String>,
2627
pub kind: ImportKind,
2728
}

tests/all/imports.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,3 +351,47 @@ fn rename() {
351351
"#)
352352
.test();
353353
}
354+
355+
#[test]
356+
fn versions() {
357+
project()
358+
.file("src/lib.rs", r#"
359+
#![feature(proc_macro, wasm_custom_section, wasm_import_module)]
360+
361+
extern crate wasm_bindgen;
362+
363+
use wasm_bindgen::prelude::*;
364+
365+
#[wasm_bindgen(module = "webpack", version = "^0.2.0")]
366+
extern {
367+
fn foo();
368+
}
369+
370+
#[wasm_bindgen]
371+
pub fn run() {
372+
foo();
373+
}
374+
"#)
375+
.file("test.js", r#"
376+
const fs = require("fs");
377+
const assert = require("assert");
378+
379+
exports.test = function() {
380+
const bytes = fs.readFileSync('out_bg.wasm');
381+
const m = new WebAssembly.Module(bytes);
382+
const name = '__wasm_pack_unstable';
383+
const sections = WebAssembly.Module.customSections(m, name);
384+
assert.strictEqual(sections.length, 1);
385+
const b = new Uint8Array(sections[0]);
386+
const buf = new Buffer(b);
387+
const map = JSON.parse(buf.toString());
388+
assert.deepStrictEqual(map, {
389+
version: '0.0.1',
390+
modules: [
391+
['webpack', '^0.2.0']
392+
]
393+
});
394+
};
395+
"#)
396+
.test();
397+
}

0 commit comments

Comments
 (0)