Skip to content

Commit bac1334

Browse files
committed
Implement transitive support for NPM dependencies
This commit implements [RFC 8], which enables transitive and transparent dependencies on NPM. The `module` attribute, when seen and not part of a local JS snippet, triggers detection of a `package.json` next to `Cargo.toml`. If found it will cause the `wasm-bindgen` CLI tool to load and parse the `package.json` within each crate and then create a merged `package.json` at the end. [RFC 8]: rustwasm/rfcs#8
1 parent ccad626 commit bac1334

File tree

7 files changed

+116
-6
lines changed

7 files changed

+116
-6
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ matrix:
9494
install:
9595
- *INSTALL_NODE_VIA_NVM
9696
- *INSTALL_AWS
97-
- npm install
97+
- mv _package.json package.json && npm install && rm package.json
9898
- curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f
9999
script:
100100
- cargo build -p wasm-bindgen-cli
File renamed without changes.

crates/backend/src/encode.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use proc_macro2::{Ident, Span};
2-
use std::cell::RefCell;
2+
use std::cell::{RefCell, Cell};
33
use std::collections::{HashMap, HashSet};
44
use std::env;
55
use std::fs;
@@ -29,6 +29,7 @@ struct Interner {
2929
files: RefCell<HashMap<String, LocalFile>>,
3030
root: PathBuf,
3131
crate_name: String,
32+
has_package_json: Cell<bool>,
3233
}
3334

3435
struct LocalFile {
@@ -45,6 +46,7 @@ impl Interner {
4546
files: RefCell::new(HashMap::new()),
4647
root: env::var_os("CARGO_MANIFEST_DIR").unwrap().into(),
4748
crate_name: env::var("CARGO_PKG_NAME").unwrap(),
49+
has_package_json: Cell::new(false),
4850
}
4951
}
5052

@@ -83,6 +85,7 @@ impl Interner {
8385
let msg = "relative module paths aren't supported yet";
8486
return Err(Diagnostic::span_error(span, msg))
8587
} else {
88+
self.check_for_package_json();
8689
return Ok(self.intern_str(&id))
8790
};
8891

@@ -98,6 +101,16 @@ impl Interner {
98101
drop(files);
99102
self.resolve_import_module(id, span)
100103
}
104+
105+
fn check_for_package_json(&self) {
106+
if self.has_package_json.get() {
107+
return
108+
}
109+
let path = self.root.join("package.json");
110+
if path.exists() {
111+
self.has_package_json.set(true);
112+
}
113+
}
101114
}
102115

103116
fn shared_program<'a>(
@@ -149,6 +162,11 @@ fn shared_program<'a>(
149162
.iter()
150163
.map(|js| intern.intern_str(js))
151164
.collect(),
165+
package_json: if intern.has_package_json.get() {
166+
Some(intern.intern_str(intern.root.join("package.json").to_str().unwrap()))
167+
} else {
168+
None
169+
},
152170
})
153171
}
154172

crates/cli-support/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ base64 = "0.9"
1616
failure = "0.1.2"
1717
log = "0.4"
1818
rustc-demangle = "0.1.13"
19+
serde_json = "1.0"
1920
tempfile = "3.0"
2021
walrus = "0.4.0"
2122
wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.37' }

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

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::{Bindgen, EncodeInto, OutputMode};
44
use failure::{bail, Error, ResultExt};
55
use std::collections::{HashMap, HashSet};
66
use std::env;
7+
use std::fs;
78
use walrus::{MemoryId, Module};
89
use wasm_bindgen_wasm_interpreter::Interpreter;
910

@@ -62,6 +63,10 @@ pub struct Context<'a> {
6263
/// snippets. This is incremented each time a `Program` is processed.
6364
pub snippet_offset: usize,
6465

66+
/// All package.json dependencies we've learned about so far
67+
pub package_json_read: HashSet<&'a str>,
68+
pub npm_dependencies: HashMap<String, (&'a str, String)>,
69+
6570
pub anyref: wasm_bindgen_anyref_xform::Context,
6671
}
6772

@@ -1250,8 +1255,7 @@ impl<'a> Context<'a> {
12501255
passStringToWasm = function(arg) {{ {} }};
12511256
}}
12521257
",
1253-
use_encode_into,
1254-
use_encode,
1258+
use_encode_into, use_encode,
12551259
));
12561260
}
12571261
}
@@ -2409,6 +2413,10 @@ impl<'a, 'b> SubContext<'a, 'b> {
24092413
self.cx.typescript.push_str("\n\n");
24102414
}
24112415

2416+
if let Some(path) = self.program.package_json {
2417+
self.add_package_json(path)?;
2418+
}
2419+
24122420
Ok(())
24132421
}
24142422

@@ -2869,6 +2877,67 @@ impl<'a, 'b> SubContext<'a, 'b> {
28692877
let import = self.determine_import(import, item)?;
28702878
Ok(self.cx.import_identifier(import))
28712879
}
2880+
2881+
fn add_package_json(&mut self, path: &'b str) -> Result<(), Error> {
2882+
if !self.cx.package_json_read.insert(path) {
2883+
return Ok(());
2884+
}
2885+
if !self.cx.config.mode.nodejs() && !self.cx.config.mode.bundler() {
2886+
bail!("NPM dependencies have been specified in `{}` but \
2887+
this is only compatible with the default output of \
2888+
`wasm-bindgen` or the `--nodejs` flag");
2889+
}
2890+
let contents = fs::read_to_string(path).context(format!("failed to read `{}`", path))?;
2891+
let json: serde_json::Value = serde_json::from_str(&contents)?;
2892+
let object = match json.as_object() {
2893+
Some(s) => s,
2894+
None => bail!(
2895+
"expected `package.json` to have an JSON object in `{}`",
2896+
path
2897+
),
2898+
};
2899+
let mut iter = object.iter();
2900+
let (key, value) = match iter.next() {
2901+
Some(pair) => pair,
2902+
None => return Ok(()),
2903+
};
2904+
if key != "dependencies" || iter.next().is_some() {
2905+
bail!(
2906+
"NPM manifest found at `{}` can currently only have one key, \
2907+
`dependencies`, and no other fields",
2908+
path
2909+
);
2910+
}
2911+
let value = match value.as_object() {
2912+
Some(s) => s,
2913+
None => bail!("expected `dependencies` to be a JSON object in `{}`", path),
2914+
};
2915+
2916+
for (name, value) in value.iter() {
2917+
let value = match value.as_str() {
2918+
Some(s) => s,
2919+
None => bail!(
2920+
"keys in `dependencies` are expected to be strings in `{}`",
2921+
path
2922+
),
2923+
};
2924+
if let Some((prev, _prev_version)) = self.cx.npm_dependencies.get(name) {
2925+
bail!(
2926+
"dependency on NPM package `{}` specified in two `package.json` files, \
2927+
which at the time is not allowed:\n * {}\n * {}",
2928+
name,
2929+
path,
2930+
prev
2931+
)
2932+
}
2933+
2934+
self.cx
2935+
.npm_dependencies
2936+
.insert(name.to_string(), (path, value.to_string()));
2937+
}
2938+
2939+
Ok(())
2940+
}
28722941
}
28732942

28742943
#[derive(Hash, Eq, PartialEq)]

crates/cli-support/src/lib.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![doc(html_root_url = "https://docs.rs/wasm-bindgen-cli-support/0.2")]
22

33
use failure::{bail, Error, ResultExt};
4-
use std::collections::BTreeSet;
4+
use std::collections::{BTreeSet, BTreeMap};
55
use std::env;
66
use std::fs;
77
use std::mem;
@@ -305,6 +305,8 @@ impl Bindgen {
305305
start: None,
306306
anyref: Default::default(),
307307
snippet_offset: 0,
308+
npm_dependencies: Default::default(),
309+
package_json_read: Default::default(),
308310
};
309311
cx.anyref.enabled = self.anyref;
310312
cx.anyref.prepare(cx.module)?;
@@ -335,6 +337,16 @@ impl Bindgen {
335337
.with_context(|_| format!("failed to write `{}`", path.display()))?;
336338
}
337339

340+
if cx.npm_dependencies.len() > 0 {
341+
let map = cx
342+
.npm_dependencies
343+
.iter()
344+
.map(|(k, v)| (k, &v.1))
345+
.collect::<BTreeMap<_, _>>();
346+
let json = serde_json::to_string_pretty(&map)?;
347+
fs::write(out_dir.join("package.json"), json)?;
348+
}
349+
338350
cx.finalize(stem)?
339351
};
340352

@@ -630,7 +642,9 @@ fn demangle(module: &mut Module) {
630642
impl OutputMode {
631643
fn nodejs_experimental_modules(&self) -> bool {
632644
match self {
633-
OutputMode::Node { experimental_modules } => *experimental_modules,
645+
OutputMode::Node {
646+
experimental_modules,
647+
} => *experimental_modules,
634648
_ => false,
635649
}
636650
}
@@ -663,4 +677,11 @@ impl OutputMode {
663677
_ => false,
664678
}
665679
}
680+
681+
fn bundler(&self) -> bool {
682+
match self {
683+
OutputMode::Bundler => true,
684+
_ => false,
685+
}
686+
}
666687
}

crates/shared/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ macro_rules! shared_api {
1616
typescript_custom_sections: Vec<&'a str>,
1717
local_modules: Vec<LocalModule<'a>>,
1818
inline_js: Vec<&'a str>,
19+
package_json: Option<&'a str>,
1920
}
2021

2122
struct Import<'a> {

0 commit comments

Comments
 (0)