mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-16 02:00:51 +00:00
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]: https://github.com/rustwasm/rfcs/pull/8
This commit is contained in:
parent
6522968fb6
commit
6edb40a807
@ -1,5 +1,5 @@
|
||||
use proc_macro2::{Ident, Span};
|
||||
use std::cell::RefCell;
|
||||
use std::cell::{RefCell, Cell};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
@ -28,6 +28,7 @@ struct Interner {
|
||||
files: RefCell<HashMap<String, LocalFile>>,
|
||||
root: PathBuf,
|
||||
crate_name: String,
|
||||
has_package_json: Cell<bool>,
|
||||
}
|
||||
|
||||
struct LocalFile {
|
||||
@ -43,6 +44,7 @@ impl Interner {
|
||||
files: RefCell::new(HashMap::new()),
|
||||
root: env::var_os("CARGO_MANIFEST_DIR").unwrap().into(),
|
||||
crate_name: env::var("CARGO_PKG_NAME").unwrap(),
|
||||
has_package_json: Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,6 +69,7 @@ impl Interner {
|
||||
if let Some(file) = files.get(id) {
|
||||
return Ok(self.intern_str(&file.new_identifier))
|
||||
}
|
||||
self.check_for_package_json();
|
||||
let path = if id.starts_with("/") {
|
||||
self.root.join(&id[1..])
|
||||
} else if id.starts_with("./") || id.starts_with("../") {
|
||||
@ -92,6 +95,16 @@ impl Interner {
|
||||
fn unique_crate_identifier(&self) -> String {
|
||||
format!("{}-{}", self.crate_name, ShortHash(0))
|
||||
}
|
||||
|
||||
fn check_for_package_json(&self) {
|
||||
if self.has_package_json.get() {
|
||||
return
|
||||
}
|
||||
let path = self.root.join("package.json");
|
||||
if path.exists() {
|
||||
self.has_package_json.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn shared_program<'a>(
|
||||
@ -144,6 +157,11 @@ fn shared_program<'a>(
|
||||
.map(|js| intern.intern_str(js))
|
||||
.collect(),
|
||||
unique_crate_identifier: intern.intern_str(&intern.unique_crate_identifier()),
|
||||
package_json: if intern.has_package_json.get() {
|
||||
Some(intern.intern_str(intern.root.join("package.json").to_str().unwrap()))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ base64 = "0.9"
|
||||
failure = "0.1.2"
|
||||
log = "0.4"
|
||||
rustc-demangle = "0.1.13"
|
||||
serde_json = "1.0"
|
||||
tempfile = "3.0"
|
||||
walrus = "0.5.0"
|
||||
wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.40' }
|
||||
|
@ -4,6 +4,7 @@ use crate::{Bindgen, EncodeInto, OutputMode};
|
||||
use failure::{bail, Error, ResultExt};
|
||||
use std::collections::{HashMap, HashSet, BTreeMap};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use walrus::{MemoryId, Module};
|
||||
use wasm_bindgen_wasm_interpreter::Interpreter;
|
||||
|
||||
@ -64,6 +65,10 @@ pub struct Context<'a> {
|
||||
/// the same `Program`.
|
||||
pub snippet_offsets: HashMap<&'a str, usize>,
|
||||
|
||||
/// All package.json dependencies we've learned about so far
|
||||
pub package_json_read: HashSet<&'a str>,
|
||||
pub npm_dependencies: HashMap<String, (&'a str, String)>,
|
||||
|
||||
pub anyref: wasm_bindgen_anyref_xform::Context,
|
||||
}
|
||||
|
||||
@ -2480,6 +2485,10 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
self.cx.typescript.push_str("\n\n");
|
||||
}
|
||||
|
||||
if let Some(path) = self.program.package_json {
|
||||
self.add_package_json(path)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -2951,6 +2960,67 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
let import = self.determine_import(import, item)?;
|
||||
Ok(self.cx.import_identifier(import))
|
||||
}
|
||||
|
||||
fn add_package_json(&mut self, path: &'b str) -> Result<(), Error> {
|
||||
if !self.cx.package_json_read.insert(path) {
|
||||
return Ok(());
|
||||
}
|
||||
if !self.cx.config.mode.nodejs() && !self.cx.config.mode.bundler() {
|
||||
bail!("NPM dependencies have been specified in `{}` but \
|
||||
this is only compatible with the default output of \
|
||||
`wasm-bindgen` or the `--nodejs` flag");
|
||||
}
|
||||
let contents = fs::read_to_string(path).context(format!("failed to read `{}`", path))?;
|
||||
let json: serde_json::Value = serde_json::from_str(&contents)?;
|
||||
let object = match json.as_object() {
|
||||
Some(s) => s,
|
||||
None => bail!(
|
||||
"expected `package.json` to have an JSON object in `{}`",
|
||||
path
|
||||
),
|
||||
};
|
||||
let mut iter = object.iter();
|
||||
let (key, value) = match iter.next() {
|
||||
Some(pair) => pair,
|
||||
None => return Ok(()),
|
||||
};
|
||||
if key != "dependencies" || iter.next().is_some() {
|
||||
bail!(
|
||||
"NPM manifest found at `{}` can currently only have one key, \
|
||||
`dependencies`, and no other fields",
|
||||
path
|
||||
);
|
||||
}
|
||||
let value = match value.as_object() {
|
||||
Some(s) => s,
|
||||
None => bail!("expected `dependencies` to be a JSON object in `{}`", path),
|
||||
};
|
||||
|
||||
for (name, value) in value.iter() {
|
||||
let value = match value.as_str() {
|
||||
Some(s) => s,
|
||||
None => bail!(
|
||||
"keys in `dependencies` are expected to be strings in `{}`",
|
||||
path
|
||||
),
|
||||
};
|
||||
if let Some((prev, _prev_version)) = self.cx.npm_dependencies.get(name) {
|
||||
bail!(
|
||||
"dependency on NPM package `{}` specified in two `package.json` files, \
|
||||
which at the time is not allowed:\n * {}\n * {}",
|
||||
name,
|
||||
path,
|
||||
prev
|
||||
)
|
||||
}
|
||||
|
||||
self.cx
|
||||
.npm_dependencies
|
||||
.insert(name.to_string(), (path, value.to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Hash, Eq, PartialEq)]
|
||||
|
@ -1,7 +1,7 @@
|
||||
#![doc(html_root_url = "https://docs.rs/wasm-bindgen-cli-support/0.2")]
|
||||
|
||||
use failure::{bail, Error, ResultExt};
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeSet, BTreeMap};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::mem;
|
||||
@ -329,6 +329,8 @@ impl Bindgen {
|
||||
start: None,
|
||||
anyref: Default::default(),
|
||||
snippet_offsets: Default::default(),
|
||||
npm_dependencies: Default::default(),
|
||||
package_json_read: Default::default(),
|
||||
};
|
||||
cx.anyref.enabled = self.anyref;
|
||||
cx.anyref.prepare(cx.module)?;
|
||||
@ -366,6 +368,16 @@ impl Bindgen {
|
||||
.with_context(|_| format!("failed to write `{}`", path.display()))?;
|
||||
}
|
||||
|
||||
if cx.npm_dependencies.len() > 0 {
|
||||
let map = cx
|
||||
.npm_dependencies
|
||||
.iter()
|
||||
.map(|(k, v)| (k, &v.1))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
let json = serde_json::to_string_pretty(&map)?;
|
||||
fs::write(out_dir.join("package.json"), json)?;
|
||||
}
|
||||
|
||||
cx.finalize(stem)?
|
||||
};
|
||||
|
||||
@ -701,4 +713,11 @@ impl OutputMode {
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn bundler(&self) -> bool {
|
||||
match self {
|
||||
OutputMode::Bundler => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ macro_rules! shared_api {
|
||||
local_modules: Vec<LocalModule<'a>>,
|
||||
inline_js: Vec<&'a str>,
|
||||
unique_crate_identifier: &'a str,
|
||||
package_json: Option<&'a str>,
|
||||
}
|
||||
|
||||
struct Import<'a> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user