daubaris 6 years ago
commit e918fddb71
  1. 3
      .appveyor.yml
  2. 4
      Cargo.lock
  3. 2
      Cargo.toml
  4. 23
      binary-install/Cargo.toml
  5. 2
      binary-install/README.md
  6. 346
      binary-install/src/lib.rs
  7. 142
      binary-install/tests/all/cache.rs
  8. 125
      binary-install/tests/all/download.rs
  9. 7
      binary-install/tests/all/main.rs
  10. 79
      binary-install/tests/all/utils/mod.rs
  11. 10
      docs/src/SUMMARY.md
  12. 12
      docs/src/commands/build.md
  13. 4
      docs/src/prerequisites/index.md
  14. 24
      docs/src/tutorials/npm-browser-packages/building-your-package.md
  15. 23
      docs/src/tutorials/npm-browser-packages/building-your-project.md
  16. 2
      docs/src/tutorials/npm-browser-packages/getting-started.md
  17. 6
      docs/src/tutorials/npm-browser-packages/getting-started/manual-setup.md
  18. 10
      docs/src/tutorials/npm-browser-packages/project-setup/index.md
  19. 21
      docs/src/tutorials/npm-browser-packages/project-setup/using-a-template.md
  20. 1
      docs/src/tutorials/npm-browser-packages/template-deep-dive.md
  21. 44
      docs/src/tutorials/npm-browser-packages/template-deep-dive/cargo-toml.md
  22. 10
      docs/src/tutorials/npm-browser-packages/template-deep-dive/index.md
  23. 14
      docs/src/tutorials/npm-browser-packages/template-deep-dive/src-lib-rs.md
  24. 39
      docs/src/tutorials/npm-browser-packages/template-deep-dive/src-utils-rs.md
  25. 74
      docs/src/tutorials/npm-browser-packages/template-deep-dive/tests-web-rs.md
  26. 40
      docs/src/tutorials/npm-browser-packages/template-deep-dive/wee_alloc.md
  27. 45
      docs/src/tutorials/npm-browser-packages/testing-your-project.md
  28. 49
      src/command/build.rs
  29. 26
      tests/all/build.rs

@ -17,9 +17,6 @@ build: false
test_script:
- cargo test --release --tests --locked
- cargo test --release --doc
- cd binary-install
- cargo test
- cd ..
before_deploy:
- ps: |

4
Cargo.lock generated

@ -89,6 +89,7 @@ dependencies = [
[[package]]
name = "binary-install"
version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"curl 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)",
"dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1293,7 +1294,7 @@ version = "0.7.0"
dependencies = [
"assert_cmd 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"binary-install 0.0.2",
"binary-install 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"cargo_metadata 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"console 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1416,6 +1417,7 @@ dependencies = [
"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799"
"checksum backtrace 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "cd5a90e2b463010cd0e0ce9a11d4a9d5d58d9f41d4a6ba3dcaf9e68b466e88b4"
"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6"
"checksum binary-install 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7b5bc5f8c50dd6a80d0b303ddab79f42ddcb52fd43d68107ecf622c551fd4cd4"
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
"checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400"
"checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39"

@ -32,7 +32,7 @@ siphasher = "0.2.3"
structopt = "0.2"
toml = "0.4"
which = "2.0.0"
binary-install = { version = "0.0.2", path = "./binary-install" }
binary-install = "0.0.2"
walkdir = "2"
chrono = "0.4.6"

@ -1,23 +0,0 @@
[package]
name = "binary-install"
description = "install a binary from a path to a global cache"
authors = ["The wasm-pack team"]
repository = "https://github.com/rustwasm/wasm-pack/tree/master/binary-install"
license = "MIT/Apache-2.0"
version = "0.0.2"
documentation = "https://docs.rs/binary-install"
readme = "./README.md"
[dependencies]
curl = "0.4.13"
dirs = "1.0.4"
failure = "0.1.2"
flate2 = "1.0.2"
hex = "0.3"
is_executable = "0.1.2"
siphasher = "0.2.3"
tar = "0.4.16"
zip = "0.5.0"
[dev-dependencies]
tempfile = "3.0.5"

@ -1,2 +0,0 @@
# `binary-install`
> install a binary from a path to a global cache

@ -1,346 +0,0 @@
//! Utilities for finding and installing binaries that we depend on.
extern crate curl;
#[macro_use]
extern crate failure;
extern crate dirs;
extern crate flate2;
extern crate hex;
extern crate is_executable;
extern crate siphasher;
extern crate tar;
extern crate zip;
use failure::{Error, ResultExt};
use siphasher::sip::SipHasher13;
use std::collections::HashSet;
use std::env;
use std::ffi;
use std::fs;
use std::hash::{Hash, Hasher};
use std::io;
use std::path::{Path, PathBuf};
/// Global cache for wasm-pack, currently containing binaries downloaded from
/// urls like wasm-bindgen and such.
#[derive(Debug)]
pub struct Cache {
destination: PathBuf,
}
/// Representation of a downloaded tarball/zip
#[derive(Debug)]
pub struct Download {
root: PathBuf,
}
impl Cache {
/// Returns the global cache directory, as inferred from env vars and such.
///
/// This function may return an error if a cache directory cannot be
/// determined.
pub fn new(name: &str) -> Result<Cache, Error> {
let cache_name = format!(".{}", name);
let destination = dirs::cache_dir()
.map(|p| p.join(&cache_name))
.or_else(|| {
let home = dirs::home_dir()?;
Some(home.join(&cache_name))
})
.ok_or_else(|| format_err!("couldn't find your home directory, is $HOME not set?"))?;
Ok(Cache::at(&destination))
}
/// Creates a new cache specifically at a particular directory, useful in
/// testing and such.
pub fn at(path: &Path) -> Cache {
Cache {
destination: path.to_path_buf(),
}
}
/// Joins a path to the destination of this cache, returning the result
pub fn join(&self, path: &Path) -> PathBuf {
self.destination.join(path)
}
/// Downloads a tarball or zip file from the specified url, extracting it
/// locally and returning the directory that the contents were extracted
/// into.
///
/// Note that this function requries that the contents of `url` never change
/// as the contents of the url are globally cached on the system and never
/// invalidated.
///
/// The `name` is a human-readable name used to go into the folder name of
/// the destination, and `binaries` is a list of binaries expected to be at
/// the url. If the URL's extraction doesn't contain all the binaries this
/// function will return an error.
pub fn download(
&self,
install_permitted: bool,
name: &str,
binaries: &[&str],
url: &str,
) -> Result<Option<Download>, Error> {
let dirname = hashed_dirname(url, name);
let destination = self.destination.join(&dirname);
if destination.exists() {
return Ok(Some(Download { root: destination }));
}
if !install_permitted {
return Ok(None);
}
let data = curl(&url).with_context(|_| format!("failed to download from {}", url))?;
// Extract everything in a temporary directory in case we're ctrl-c'd.
// Don't want to leave around corrupted data!
let temp = self.destination.join(&format!(".{}", dirname));
drop(fs::remove_dir_all(&temp));
fs::create_dir_all(&temp)?;
if url.ends_with(".tar.gz") {
self.extract_tarball(&data, &temp, binaries)
.with_context(|_| format!("failed to extract tarball from {}", url))?;
} else if url.ends_with(".zip") {
self.extract_zip(&data, &temp, binaries)
.with_context(|_| format!("failed to extract zip from {}", url))?;
} else {
// panic instead of runtime error as it's a static violation to
// download a different kind of url, all urls should be encoded into
// the binary anyway
panic!("don't know how to extract {}", url)
}
// Now that everything is ready move this over to our destination and
// we're good to go.
fs::rename(&temp, &destination)?;
Ok(Some(Download { root: destination }))
}
fn extract_tarball(&self, tarball: &[u8], dst: &Path, binaries: &[&str]) -> Result<(), Error> {
let mut binaries: HashSet<_> = binaries.into_iter().map(ffi::OsStr::new).collect();
let mut archive = tar::Archive::new(flate2::read::GzDecoder::new(tarball));
for entry in archive.entries()? {
let mut entry = entry?;
let dest = match entry.path()?.file_stem() {
Some(f) if binaries.contains(f) => {
binaries.remove(f);
dst.join(entry.path()?.file_name().unwrap())
}
_ => continue,
};
entry.unpack(dest)?;
}
if !binaries.is_empty() {
bail!(
"the tarball was missing expected executables: {}",
binaries
.into_iter()
.map(|s| s.to_string_lossy())
.collect::<Vec<_>>()
.join(", "),
)
}
Ok(())
}
fn extract_zip(&self, zip: &[u8], dst: &Path, binaries: &[&str]) -> Result<(), Error> {
let mut binaries: HashSet<_> = binaries.into_iter().map(ffi::OsStr::new).collect();
let data = io::Cursor::new(zip);
let mut zip = zip::ZipArchive::new(data)?;
for i in 0..zip.len() {
let mut entry = zip.by_index(i).unwrap();
let entry_path = entry.sanitized_name();
match entry_path.file_stem() {
Some(f) if binaries.contains(f) => {
binaries.remove(f);
let mut dest = bin_open_options()
.write(true)
.create_new(true)
.open(dst.join(entry_path.file_name().unwrap()))?;
io::copy(&mut entry, &mut dest)?;
}
_ => continue,
};
}
if !binaries.is_empty() {
bail!(
"the zip was missing expected executables: {}",
binaries
.into_iter()
.map(|s| s.to_string_lossy())
.collect::<Vec<_>>()
.join(", "),
)
}
return Ok(());
#[cfg(unix)]
fn bin_open_options() -> fs::OpenOptions {
use std::os::unix::fs::OpenOptionsExt;
let mut opts = fs::OpenOptions::new();
opts.mode(0o755);
opts
}
#[cfg(not(unix))]
fn bin_open_options() -> fs::OpenOptions {
fs::OpenOptions::new()
}
}
}
impl Download {
/// Manually constructs a download at the specified path
pub fn at(path: &Path) -> Download {
Download {
root: path.to_path_buf(),
}
}
/// Returns the path to the binary `name` within this download
pub fn binary(&self, name: &str) -> Result<PathBuf, Error> {
use is_executable::IsExecutable;
let ret = self
.root
.join(name)
.with_extension(env::consts::EXE_EXTENSION);
if !ret.is_file() {
bail!("{} binary does not exist", ret.display());
}
if !ret.is_executable() {
bail!("{} is not executable", ret.display());
}
Ok(ret)
}
}
fn curl(url: &str) -> Result<Vec<u8>, Error> {
let mut data = Vec::new();
let mut easy = curl::easy::Easy::new();
easy.follow_location(true)?;
easy.url(url)?;
easy.get(true)?;
{
let mut transfer = easy.transfer();
transfer.write_function(|part| {
data.extend_from_slice(part);
Ok(part.len())
})?;
transfer.perform()?;
}
let status_code = easy.response_code()?;
if 200 <= status_code && status_code < 300 {
Ok(data)
} else {
bail!(
"received a bad HTTP status code ({}) when requesting {}",
status_code,
url
)
}
}
fn hashed_dirname(url: &str, name: &str) -> String {
let mut hasher = SipHasher13::new();
url.hash(&mut hasher);
let result = hasher.finish();
let hex = hex::encode(&[
(result >> 0) as u8,
(result >> 8) as u8,
(result >> 16) as u8,
(result >> 24) as u8,
(result >> 32) as u8,
(result >> 40) as u8,
(result >> 48) as u8,
(result >> 56) as u8,
]);
format!("{}-{}", name, hex)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_returns_same_hash_for_same_name_and_url() {
let name = "wasm-pack";
let url = "http://localhost:7878/wasm-pack-v0.6.0.tar.gz";
let first = hashed_dirname(url, name);
let second = hashed_dirname(url, name);
assert!(!first.is_empty());
assert!(!second.is_empty());
assert_eq!(first, second);
}
#[test]
fn it_returns_different_hashes_for_different_urls() {
let name = "wasm-pack";
let url = "http://localhost:7878/wasm-pack-v0.5.1.tar.gz";
let second_url = "http://localhost:7878/wasm-pack-v0.6.0.tar.gz";
let first = hashed_dirname(url, name);
let second = hashed_dirname(second_url, name);
assert_ne!(first, second);
}
#[test]
fn it_returns_cache_dir() {
let name = "wasm-pack";
let cache = Cache::new(name);
let expected = dirs::cache_dir()
.unwrap()
.join(PathBuf::from(".".to_owned() + name));
assert!(cache.is_ok());
assert_eq!(cache.unwrap().destination, expected);
}
#[test]
fn it_returns_destination_if_binary_already_exists() {
use std::fs;
let binary_name = "wasm-pack";
let binaries = vec![binary_name];
let dir = tempfile::TempDir::new().unwrap();
let cache = Cache::at(dir.path());
let url = &format!("{}/{}.tar.gz", "http://localhost:7878", binary_name);
let dirname = hashed_dirname(&url, &binary_name);
let full_path = dir.path().join(dirname);
// Create temporary directory and binary to simulate that
// a cached binary already exists.
fs::create_dir_all(full_path).unwrap();
let dl = cache.download(true, binary_name, &binaries, url);
assert!(dl.is_ok());
assert!(dl.unwrap().is_some())
}
}

@ -1,142 +0,0 @@
use binary_install::Cache;
use std::path::Path;
use utils;
#[test]
fn it_returns_none_if_install_is_not_permitted() {
let binary_name = "wasm-pack";
let binaries = vec![binary_name];
let dir = tempfile::TempDir::new().unwrap();
let cache = Cache::at(dir.path());
let dl = cache.download(
false,
binary_name,
&binaries,
&format!("{}/{}.tar.gz", "", binary_name),
);
assert!(dl.is_ok());
assert!(dl.unwrap().is_none())
}
#[test]
fn it_downloads_tarball() {
let binary_name = "wasm-pack";
let binaries = vec![binary_name];
// Create a temporary tarball.
let tarball = utils::create_tarball(binary_name).ok();
// Spin up a local TcpListener.
let server_port = utils::start_server(tarball, None).recv().unwrap();
let url = format!("http://{}:{}", utils::TEST_SERVER_HOST, server_port);
let dir = tempfile::TempDir::new().unwrap();
let cache = Cache::at(dir.path());
let dl = cache.download(
true,
binary_name,
&binaries,
&format!("{}/{}.tar.gz", &url, binary_name),
);
assert!(dl.is_ok());
assert!(dl.unwrap().is_some())
}
#[test]
fn it_returns_error_when_it_failed_to_download() {
let server_port = 7881;
let url = format!("http://{}:{}", utils::TEST_SERVER_HOST, server_port);
let binary_name = "wasm-pack";
let binaries = vec![binary_name];
let dir = tempfile::TempDir::new().unwrap();
let cache = Cache::at(dir.path());
let full_url = &format!("{}/{}.tar.gz", &url, binary_name);
let dl = cache.download(true, binary_name, &binaries, full_url);
assert!(dl.is_err());
assert_eq!(
&format!("failed to download from {}", full_url),
&format!("{}", dl.unwrap_err())
);
}
#[test]
fn it_returns_error_when_it_failed_to_extract_tarball() {
let binary_name = "wasm-pack";
let binaries = vec![binary_name];
let dir = tempfile::TempDir::new().unwrap();
let cache = Cache::at(dir.path());
// Spin up a local TcpListener.
let server_port = utils::start_server(None, None).recv().unwrap();
let url = format!("http://{}:{}", utils::TEST_SERVER_HOST, server_port);
let full_url = &format!("{}/{}.tar.gz", &url, binary_name);
let dl = cache.download(true, binary_name, &binaries, full_url);
assert!(dl.is_err());
assert_eq!(
&format!("failed to extract tarball from {}", full_url),
&format!("{}", dl.unwrap_err())
);
}
#[test]
fn it_returns_error_when_it_failed_to_extract_zip() {
let binary_name = "wasm-pack";
let binaries = vec![binary_name];
let dir = tempfile::TempDir::new().unwrap();
let cache = Cache::at(dir.path());
// Spin up a local TcpListener.
let server_port = utils::start_server(None, None).recv().unwrap();
let url = format!("http://{}:{}", utils::TEST_SERVER_HOST, server_port);
let full_url = &format!("{}/{}.zip", &url, binary_name);
let dl = cache.download(true, binary_name, &binaries, full_url);
assert!(dl.is_err());
assert_eq!(
&format!("failed to extract zip from {}", full_url),
&format!("{}", dl.unwrap_err())
);
}
#[test]
#[should_panic(expected = "don't know how to extract http://localhost:7884/wasm-pack.bin")]
fn it_panics_if_not_tarball_or_zip() {
let server_port = 7884;
let binary_name = "wasm-pack";
let binaries = vec![binary_name];
let dir = tempfile::TempDir::new().unwrap();
let cache = Cache::at(dir.path());
// Spin up a local TcpListener.
utils::start_server(None, Some(server_port)).recv().unwrap();
let url = format!("http://{}:{}", utils::TEST_SERVER_HOST, server_port);
let full_url = &format!("{}/{}.bin", &url, binary_name);
let _ = cache.download(true, binary_name, &binaries, full_url);
}
#[test]
fn it_joins_path_with_destination() {
let dir = tempfile::TempDir::new().unwrap();
let cache = Cache::at(dir.path());
assert_eq!(dir.path().join("hello"), cache.join(Path::new("hello")));
}

@ -1,125 +0,0 @@
use binary_install::Download;
use std::fs::OpenOptions;
#[test]
#[cfg(unix)]
fn it_returns_binary_name_for_unix() {
use std::os::unix::fs::OpenOptionsExt;
let binary_name = "wasm-pack";
let dir = tempfile::TempDir::new().unwrap();
let download = Download::at(dir.path());
let full_path = dir.path().join(binary_name);
let mut options = OpenOptions::new();
options.create(true);
options.write(true);
// Make the "binary" an executable.
options.mode(0o755);
options.open(&full_path).unwrap();
let binary = download.binary(binary_name);
assert!(binary.is_ok());
assert_eq!(full_path, binary.unwrap());
}
#[test]
#[cfg(not(windows))]
fn it_bails_if_not_file_for_unix() {
let binary_name = "wasm-pack";
let dir = tempfile::TempDir::new().unwrap();
let download = Download::at(dir.path());
let full_path = dir.path().join(binary_name);
let mut options = OpenOptions::new();
options.create(true);
options.write(true);
let binary = download.binary(binary_name);
assert!(binary.is_err());
assert_eq!(
format!("{} binary does not exist", full_path.to_str().unwrap()),
binary.unwrap_err().to_string()
);
}
#[test]
#[cfg(windows)]
fn it_bails_if_not_file_for_windows() {
let binary_name = "wasm-pack.exe";
let dir = tempfile::TempDir::new().unwrap();
let download = Download::at(dir.path());
let full_path = dir.path().join(binary_name);
let mut options = OpenOptions::new();
options.create(true);
options.write(true);
let binary = download.binary(binary_name);
assert!(binary.is_err());
assert_eq!(
format!("{} binary does not exist", full_path.to_str().unwrap()),
binary.unwrap_err().to_string()
);
}
#[test]
#[cfg(not(windows))]
fn it_bails_if_not_executable_for_unix() {
let binary_name = "wasm-pack";
let dir = tempfile::TempDir::new().unwrap();
let download = Download::at(dir.path());
let full_path = dir.path().join(binary_name);
let mut options = OpenOptions::new();
options.create(true);
options.write(true);
options.open(&full_path).unwrap();
let binary = download.binary(binary_name);
assert!(binary.is_err());
assert_eq!(
format!("{} is not executable", full_path.to_str().unwrap()),
binary.unwrap_err().to_string()
);
}
#[test]
#[cfg(windows)]
fn it_bails_if_not_executable_for_windows() {
let binary_name = "wasm-pack.exe";
let dir = tempfile::TempDir::new().unwrap();
let download = Download::at(dir.path());
let full_path = dir.path().join(binary_name);
let mut options = OpenOptions::new();
options.create(true);
options.write(true);
options.open(&full_path).unwrap();
let binary = download.binary(binary_name);
assert!(binary.is_err());
assert_eq!(
format!("{} is not executable", full_path.to_str().unwrap()),
binary.unwrap_err().to_string()
);
}

@ -1,7 +0,0 @@
extern crate binary_install;
extern crate flate2;
extern crate tar;
mod cache;
mod download;
mod utils;

@ -1,79 +0,0 @@
use flate2::write::GzEncoder;
use flate2::Compression;
use std::fs::{File, OpenOptions};
use std::io::{self, Read, Write};
use std::net::TcpListener;
use std::sync::mpsc::{channel, Receiver};
use std::thread;
pub const TEST_SERVER_HOST: &'static str = "localhost";
pub fn start_server(tarball: Option<Vec<u8>>, server_port: Option<u16>) -> Receiver<u16> {
let (sender, receiver) = channel();
thread::spawn(move || {
TcpListener::bind(format!(
"{}:{}",
TEST_SERVER_HOST,
server_port.unwrap_or_else(|| 0)
))
.map(|listener| {
sender.send(listener.local_addr().unwrap().port()).unwrap();
for stream in listener.incoming() {
let mut stream = stream.unwrap();
let mut buffer = [0; 512];
stream.read(&mut buffer).unwrap();
let response = "HTTP/1.1 200 OK\r\n\r\n";
stream.write(response.as_bytes()).unwrap();
match tarball.to_owned() {
Some(tar) => {
stream.write(tar.as_ref()).unwrap();
}
None => {}
}
stream.flush().unwrap();
}
})
.unwrap();
});
receiver
}
pub fn create_tarball(binary_name: &str) -> Result<Vec<u8>, io::Error> {
let temp_dir = tempfile::TempDir::new().unwrap();
let full_path = temp_dir.path().join(binary_name.to_owned() + ".tar.gz");
let tar = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.open(&full_path)?;
let mut file = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.open(temp_dir.path().join(binary_name))?;
let mut encoder = GzEncoder::new(tar, Compression::default());
{
let mut archive = tar::Builder::new(&mut encoder);
archive.append_file(binary_name, &mut file)?;
}
let mut contents = vec![];
encoder.finish()?;
File::open(temp_dir.path().join(&full_path))?.read_to_end(&mut contents)?;
Ok(contents)
}

@ -3,25 +3,25 @@
- [Prerequisites](./prerequisites/index.md)
- [npm (optional)](./prerequisites/npm.md)
- [Commands](./commands/index.md)
- [`init` (DEPRECATED)](./commands/init.md)
- [`build`](./commands/build.md)
- [`test`](./commands/test.md)
- [`pack` and `publish`](./commands/pack-and-publish.md)
- [`init` (DEPRECATED)](./commands/init.md)
- [Tutorials](./tutorials/index.md)
- [Hybrid applications with Webpack](./tutorials/hybrid-applications-with-webpack/index.md)
- [Getting started](./tutorials/hybrid-applications-with-webpack/getting-started.md)
- [Using your library](./tutorials/hybrid-applications-with-webpack/using-your-library.md)
- [npm browser packages](./tutorials/npm-browser-packages/index.md)
- [Getting started](./tutorials/npm-browser-packages/getting-started.md)
- [Project setup](./tutorials/npm-browser-packages/project-setup/index.md)
- [Using a Template](./tutorials/npm-browser-packages/project-setup/using-a-template.md)
- [Manual Setup](./tutorials/npm-browser-packages/project-setup/manual-setup.md)
- [Manual Setup](./tutorials/npm-browser-packages/getting-started/manual-setup.md)
- [Template deep dive](./tutorials/npm-browser-packages/template-deep-dive/index.md)
- [`Cargo.toml`](./tutorials/npm-browser-packages/template-deep-dive/cargo-toml.md)
- [`src/lib.rs`](./tutorials/npm-browser-packages/template-deep-dive/src-lib-rs.md)
- [`src/utils.rs`](./tutorials/npm-browser-packages/template-deep-dive/src-utils-rs.md)
- [`wee_alloc`](./tutorials/npm-browser-packages/template-deep-dive/wee_alloc.md)
- [Building your project](./tutorials/npm-browser-packages/template-deep-dive/building-your-project.md)
- [`tests/web.rs`](./tutorials/npm-browser-packages/template-deep-dive/tests-web-rs.md)
- [Building your project](./tutorials/npm-browser-packages/building-your-project.md)
- [Testing your project](./tutorials/npm-browser-packages/testing-your-project.md)
- [Packaging and publishing](./tutorials/npm-browser-packages/packaging-and-publishing.md)
- [Using your library](./tutorials/npm-browser-packages/using-your-library.md)
- [`Cargo.toml` Configuration](./cargo-toml-configuration.md)

@ -20,6 +20,18 @@ wasm-pack build examples/js-hello-world
This path should point to a directory that contains a `Cargo.toml` file. If no
path is given, the `build` command will run in the current directory.
## Output Directory
By default, `wasm-pack` will generate a directory for it's build output called `pkg`.
If you'd like to customize this you can use the `--out-dir` flag.
```
wasm-pack build --out-dir out
```
The above command will put your build artifacts in a directory called `out`, instead
of the default `pkg`.
## Profile
The `build` command accepts an optional profile argument: one of `--dev`,

@ -10,10 +10,10 @@ Next, since `wasm-pack` is a build tool, you'll want to make sure you have
[rust]: https://www.rust-lang.org/tools/install
Finally, if you're using `wasm-pack` to install to publish to NPM, you'll want
Finally, if you're using `wasm-pack` to publish to NPM, you'll want
to [install and configure `npm`][npm]. In the future, we intend to rewrite the
npm registry client bits so that the need for a Node runtime is eliminated. If
you're excited about that work- you should reach out to the maintainers and get
involved!
[npm]: npm.html
[npm]: prerequisites/npm.html

@ -1,24 +0,0 @@
# Building your package
We've written our code so now we need to package it all up.
We are writing a package that should be used in the browser, so we run this in our terminal:
```bash
$ wasm-pack build --scope MYSCOPE
```
If you were writing a package that should be used in Node.js (with CommonJS modules, e.g. `require`),
you would run this in your terminal:
```bash
$ wasm-pack build --scope MYSCOPE --target nodejs
```
where `MYSCOPE` is your npm username. Normally you could just type `wasm-pack init` but since
other people are doing this tutorial as well we don't want conflicts with the `wasm-add` package
name! This command when run does a few things:
1. It'll compile your code to wasm if you haven't already
2. It'll generate a pkg folder with the wasm file, a JS wrapper file around the wasm, your README,
and a `package.json` file.

@ -0,0 +1,23 @@
# Building your project
We've written our code so now we need to build it.
We are writing a crate that should be used in the browser, so we run this in
our terminal:
```bash
$ wasm-pack build
```
If you were writing a package that should be used in Node.js (with CommonJS
modules, e.g. `require`), you would run this in your terminal:
```bash
$ wasm-pack build --target nodejs
```
This command when run does a few things:
1. It'll compile your code to wasm if you haven't already
2. It'll generate a `pkg` folder with the wasm file, a JS wrapper file around
the wasm, your README, and a `package.json` file.

@ -25,4 +25,4 @@ further in this guide.
If you'd rather not use a template, or are having trouble with the template, you can
do a manual setup by following [these instructions].
[these instructions]: ../project-setup/manual-setup.html
[these instructions]: ./getting-started/manual-setup.html

@ -1,9 +1,9 @@
# Manual Setup
This is not the recommended way to start a `wasm-pack` project! If you ended up
here by mistake, go check out our recommended project start, [Using A Template].
here by mistake, go check out our [recommended project start][template].
[Using A Template]: using-a-template.md
[template]: ../getting-started.html
### Step 1: Create a New Rust Library Project
@ -25,7 +25,7 @@ section. `wasm-bindgen` is a tool that facilitates interoperability between
wasm modules and JavaScript.
If you are coming from JavaScript, you might note that when we add the dependency
there is no `^` or `~` symbol- it looks like we're locking to the `0.2` version.
there is no `^` or `~` symbol- it looks like we're locking to the `0.2` version.
However, that's not the case! In Rust, the `^` is implied.
#### Add `crate-type`

@ -1,10 +0,0 @@
# Project Setup
In this section, how to setup a `wasm-pack` project.
There are a few things you need to do to setup a project for `wasm-pack`.
We strongly recommending [using a template], but you can also set the project
up [manually].
[using a template]: ./using-a-template.html
[manually]: ./manual-setup.html

@ -1,21 +0,0 @@
# Using a Template
You can create a new Rust-WebAssembly project by using the [rustwasm wasm-pack-template].
To so do, you'll need the `cargo-generate` tool. To install `cargo-generate`:
```
cargo install cargo-generate
```
Then run:
```
cargo generate --git https://github.com/rustwasm/wasm-pack-template
```
You will be prompted to give your project a name. Once you do, you will have a directory
with a new project, ready to go. We'll talk about what's been included in this template
further in this guide.
[rustwasm wasm-pack-template]: https://github.com/rustwasm/wasm-pack-template

@ -23,20 +23,21 @@ crate-type = ["cdylib", "rlib"]
A Rust-`wasm` crate is a bit different from a normal crate, and as a result, we need to note
this in our `Cargo.toml`.
When `cargo` is told to build a project, or compilation is otherwise done on a Rust project,
the Rust compiler will need to link crates together, using a particular method, either
staticly or dynamically. The two types of crate that you are likely most familiar with are
`#[crate_type = "bin"]` and `#[crate_type = "lib"]`, which are the crate types that largely
represent the difference between Rust application projects and Rust libraries.
`#[crate_type = "cdylib"]` signifies that you'd like the compiler to create a dynamic system
library. This type of library is suited for situations where you'd like to compile Rust code
as a dynamic library to be loaded from another language. In our case, we'll be compiling to a
`.wasm` file, but this output type will create `*.so` files on Linux, `*.dylib` files on
macOS, and `*.dll` files on Windows in non-`wasm` circumstances.
`#[crate_type = "rlib"]` signifies that an intermediate "Rust library" file will be produced.
This allows tests to use the main crate.
This `[lib]` annotation is typically not needed in Cargo projects, and if you're
familiar with other Rust crates you'll remember that the most common crate types
are `rlib` (the default) or `bin` for binaries (which don't need a `crate-type`
annotation).
Here though `crate-type = ["cdylib"]` typically signifies that you'd like the
compiler to create a dynamic system library, but for WebAssembly target it
simply means "create a `*.wasm` file without a `start` function". On other
platforms this output type will create `*.so` file on Linux, `*.dylib` on
macOS, and `*.dll` Windows.
We also specify `crate-type = ["rlib"]` to ensure that our library can be unit
tested with `wasm-pack test` (which we'll see later). Without this we wouldn't
be able to test our library because the `cdylib` crate type is incompatible with
`wasm-pack`'s style of unit tests.
You can read more about linking and crate types, [here](https://doc.rust-lang.org/reference/linkage.html).
@ -56,7 +57,7 @@ wasm-bindgen = "0.2"
We'll see more about how to use this library when we discuss what has been generated in `lib.rs`.
If you are coming from JavaScript, you might note that when we add the dependency
there is no `^` or `~` symbol- it looks like we're locking to the `0.2` version.
there is no `^` or `~` symbol- it looks like we're locking to the `0.2` version.
However, that's not the case! In Rust, the `^` is implied. You can read more about this in the
[cargo documentation on specifying dependencies].
@ -64,13 +65,14 @@ However, that's not the case! In Rust, the `^` is implied. You can read more abo
## 3. `[features]` and [`wee_alloc`], [`console_error_panic_hook`] dependencies
[`wee_alloc`]: https://github.com/rustwasm/wee_alloc
[`console_error_panic_hook`]: https://github.com/rustwasm/console_error_panic_hook
[`wee_alloc`]: https://crates.io/crates/wee_alloc
[`console_error_panic_hook`]: https://crates.io/crates/console_error_panic_hook
[`cfg-if`]: https://crates.io/crates/cfg-if
As part of our effort to design a template that helps people discover useful crates
for their particular use case, this template includes two dependencies that can be
very useful for folks developing Rust-`wasm` crates: `console-error-panic-hook` and
`wee-alloc`.
very useful for folks developing Rust-`wasm` crates:[ `console_error_panic_hook`] and
[`wee_alloc`].
Because these dependencies are useful primarily in a specific portion of the Rust-`wasm`
crate development workflow, we've also set up a bit of glue code that allows us to include
@ -99,13 +101,13 @@ wee_alloc = { version = "0.4.2", optional = true }
```
[`cfg-if`] allows us to check if certain features are enabled on a Rust crate. We'll
use this crate later to optionally enable `console_error_panic_hook` or
use this crate later to optionally enable [`console_error_panic_hook` or
`wee_alloc`.
By default, only `console_error_panic_hook` is enabled. To disable either
feature, we can remove its name from the `default` vector.
To learn more about these features, we discuss them in-depth in the [`src/lib.rs`] and
To learn more about these features, we discuss them in-depth in the [`src/lib.rs`] and
[`src/utils.rs`] sections.
[`src/lib.rs`]: src-lib-rs.html

@ -9,8 +9,10 @@ may look slightly different than what is described here.
### What the Template Gave Us
Let's start by taking a look at what the template generated for us.
Let's start by taking a look at what the template generated for us.
- [`Cargo.toml`](./cargo-toml.html)
- [`src/lib.rs`](./src-lib-rs.html)
- [`src/utils.rs`](./src-utils-rs.html)
- [`Cargo.toml` - the Cargo manifest](./cargo-toml.html)
- [`src/lib.rs` - main library module](./src-lib-rs.html)
- [`src/utils.rs` - a utility module](./src-utils-rs.html)
- [`wee_alloc` - a tiny memory allocator](./wee_alloc.html)
- [`tests/web.rs` - running headless browser tests](./tests-web-rs.html)

@ -46,7 +46,7 @@ This is all you need to know to interface with JavaScript, at least to start! Yo
If you are curious about the rest, read on.
## 2. Crate imports
## 2. Crate Organization
```rust
mod utils;
@ -74,7 +74,6 @@ With this in mind, this `use` allows us to call the macro `cfg_if!` inside the c
```rust
cfg_if! {
if #[cfg(feature = "wee_alloc")] {
extern crate wee_alloc;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
}
@ -83,7 +82,11 @@ cfg_if! {
We immediately notice that `cfg_if!` is a macro because it ends in `!`, similarly to other Rust macros such as `println!` and `vec!`. A macro is directly replaced by other code during compile time.
During compile time, `cfg_if!` evaluates the `if` statement. This tests whether the feature `wee_alloc` is present in the `[features]` section of `Cargo.toml` (among other possible ways to set it).
At compile time this will test if the `wee_alloc` feature is enabled for this
compilation. If it's enabled we'll configure a global allocator (according to
[`wee_alloc`'s docs][wee-alloc-docs]), otherwise it'll compile to nothing.
[wee-alloc-docs]: https://docs.rs/wee_alloc/0.4.3/wee_alloc/
As we saw earlier, the `default` vector in `[features]` only contains `"console_error_panic_hook"` and not `"wee_alloc"`. So, in this case, the `cfg_if!` block will be replaced by no code at all, and hence the default memory allocator will be used instead of `wee_alloc`.
@ -91,7 +94,10 @@ As we saw earlier, the `default` vector in `[features]` only contains `"console_
use wasm_bindgen::prelude::*;
```
Many modules contain a prelude, a list of things that should be automatically imported. This allows common features of the module to be conveniently accessed without a lengthy prefix. For example, in this file we can use `#[wasm_bindgen]` only because it is brought into scope by the prelude.
Many crates contain a prelude, a list of things that are convenient to import
all at once. This allows common features of the module to be conveniently
accessed without a lengthy prefix. For example, in this file we can use
`#[wasm_bindgen]` only because it is brought into scope by the prelude.
The asterisk at the end of this `use` indicates that everything inside the module `wasm_bindgen::prelude` (i.e. the module `prelude` inside the crate `wasm_bindgen`) can be referred to without prefixing it with `wasm_bindgen::prelude`.

@ -22,7 +22,6 @@ This allows us to write `cfg_if!` instead of `cfg_if::cfg_if!`, identically to t
```rust
cfg_if! {
if #[cfg(feature = "console_error_panic_hook")] {
extern crate console_error_panic_hook;
pub use self::console_error_panic_hook::set_once as set_panic_hook;
} else {
#[inline]
@ -31,36 +30,52 @@ cfg_if! {
}
```
As described in the preceding section, the macro `cfg_if!` evaluates the `if` statement during compile time. This is possible because it is essentially testing whether `"console_error_panic_hook"` is defined in the `[features]` section of `Cargo.toml`, which is available during compile time.
The entire macro block will either be replaced with the statements in the `if` block or with those in the `else` block. These two cases are now described in turn:
As described in the preceding section, this invocation of the `cfg_if!`
tests whether the `console_error_panic_hook` feature is enabled at compile time,
replacing with the statements in the `if` block or with those in the `else`
block. These two cases are:
```rust
extern crate console_error_panic_hook;
pub use self::console_error_panic_hook::set_once as set_panic_hook;
```
Due to the `use` statement, the function `self::console_error_panic_hook::set_once` can now be accessed more conveniently as `set_panic_hook`. Due to `pub`, this function will be publicly accessible outside of the `utils` module as `utils::set_panic_hook`.
This `use` statement means the function
`self::console_error_panic_hook::set_once` can now be accessed more conveniently
as `set_panic_hook`. With `pub`, this function will be accessible
outside of the `utils` module as `utils::set_panic_hook`.
```rust
#[inline]
pub fn set_panic_hook() {}
```
An inline function replaces the function call with the contents of the function during compile time. Here, `set_panic_hook` is defined to be an empty inline function. This allows the use of `set_panic_hook` without any run-time or code-size performance penalty if the feature is not enabled.
Here, `set_panic_hook` is defined to be an empty inline function. The inline
annotation here means that whenever the function is called the function call is
replaced with the body of the function, which is for `set_panic_hook` nothing!
This allows the use of `set_panic_hook` without any run-time or code-size
performance penalty if the feature is not enabled.
## 2. What is `console_error_panic_hook`?
The crate `console_error_panic_hook` enhances error messages in the web browser. This allows you to easily debug WebAssembly code.
The [crate `console_error_panic_hook`][ceph] allows debugging Rust panic
messages in a web browser, making it much easier to debug WebAssembly code.
Let's compare error messages before and after enabling the feature:
Let's compare what happens when Rust code panics before and after enabling the
feature:
**Before:** `"RuntimeError: Unreachable executed"`
**After:** `"panicked at 'index out of bounds: the len is 3 but the index is 4', libcore/slice/mod.rs:2046:10"`
To do this, a panic hook for WebAssembly is provided that logs panics to the developer console via the JavaScript `console.error` function.
To do this, a [panic hook] is configured that logs panics to the
developer console via the JavaScript `console.error` function.
Note though that `console_error_panic_hook` is not entirely automatic, so you'll
need to make sure that `utils::set_panic_hook()` is called before any of our
code runs (and it's safe to run `set_panic_hook` many times).
Note that although the template sets up the function, your error messages will not automatically be enhanced. To enable the enhanced errors, call the function `utils::set_panic_hook()` in `lib.rs` when your code first runs. The function may be called multiple times if needed.
For more details, see the [`console_error_panic_hook`
repository](https://github.com/rustwasm/console_error_panic_hook).
For more details, see the [`console_error_panic_hook` repository](https://github.com/rustwasm/console_error_panic_hook).
[ceph]: https://crates.io/crates/console_error_panic_hook
[panic hook]: https://doc.rust-lang.org/std/panic/fn.set_hook.html

@ -0,0 +1,74 @@
# tests/web.rs
`web.rs` is an integration test [defined with Cargo][cargo-tests] that is
intended to be run in a headless web browser via the `wasm-pack test` command.
[cargo-tests]: https://doc.rust-lang.org/cargo/guide/tests.html
It contains three key parts:
1. [`#[wasm_bindgen_test] functions`](#a1-wasm_bindgen_test-functions)
2. [Crate Configuration](#a2-crate-configuration)
3. [`#![cfg]` directives](#a3-cfg-directives)
---
## 1. `#[wasm_bindgen_test]` functions
The `#[wasm_bindgen_test]` is like the [normal Rust `#[test]`
attribute][rust-test], except it defines a test accessible to WebAssembly and
headless web browser testing.
> **Note**: Eventually `#[test]` will work with WebAssembly as well! Currently
> though [custom test frameworks][ctf] are not stable.
[rust-test]: https://doc.rust-lang.org/book/ch11-01-writing-tests.html
[ctf]: https://github.com/rust-lang/rust/issues/50297
```rust
#[wasm_bindgen_test]
fn pass() {
assert_eq!(1 + 1, 2);
}
```
Here the `pass` function is a unit test which asserts that arithmetic works in
WebAssembly like we'd expect everywhere else. If the test panics (such as the
`assert_eq!` being false) then the test will fail, otherwise the test will
succeed.
The [reference documentation for `#[wasm_bindgen_test]`][wbg-test] should have
more information about defining these tests.
[wbg-test]: https://rustwasm.github.io/docs/wasm-bindgen/wasm-bindgen-test/index.html
## 2. Crate Configuration
Other than the test in this module, we'll also see:
```rust
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
```
Like we saw earlier in `src/lib.rs` the `*` import pulls in everything from
`wasm_bindgen_test`, notably the `wasm_bindgen_test_configure` macro and the
`wasm_bindgen_test` attribute.
The `wasm_bindgen_test_configure` macro (denoted by ending in `!`) is used to
indicate that the test is intended to execute in a web browser as opposed to
Node.js, which is the default.
## 3. `#![cfg]` directives
The last part we'll notice about this crate is this statement at the top:
```rust
#![cfg(target_arch = "wasm32")]
```
This statement means that the test is only intended for the `wasm32`
architecture, or the `wasm32-unknown-unknown` target. This enables `cargo test`
to work in your project if the library is also being developed for other
platforms by ensuring that these tests only execute in a web browser.

@ -6,17 +6,22 @@
## What is `wee_alloc`?
Reducing the size of compiled WebAssembly code is important, since it is often transmitted over the Internet or placed on embedded devices.
*Want to learn more about code sizein the rustwasm toolchain? Check out this [documentation](https://rustwasm.github.io/docs/book/reference/code-size.html).
WebAssembly code is frequently transmitted over the wire to users, so compiled
code size is often important to ensure an application loads quickly and is
responsive.
> `wee_alloc` is a tiny allocator designed for WebAssembly that has a (pre-compression) code-size footprint of only a single kilobyte.
[An analysis](http://fitzgeraldnick.com/2018/02/09/wee-alloc.html) suggests that over half of the bare minimum WebAssembly memory footprint is required by Rust's default memory allocator. Yet, WebAssembly code often does not require a sophisticated allocator, since it often just requests a couple of large initial allocations.
`wee_alloc` trades off size for speed. Although it has a tiny code-size footprint, it is relatively slow if additional allocations are needed.
`wee_alloc` trades off size for speed. It has a tiny code-size
footprint, but it is is not competitive in terms of performance with the
default global allocator, for example.
For even more details, see the [`wee_alloc` repository](https://github.com/rustwasm/wee_alloc).
For even more details, see the [`wee_alloc`
repository](https://github.com/rustwasm/wee_alloc), or
[general documentation](https://rustwasm.github.io/docs/book/reference/code-size.html) about
shrinking code size of WebAssembly binaries.
## Enabling `wee_alloc`
@ -25,32 +30,23 @@ In `lib.rs`, we have the configuration for `wee_alloc` inside a `cfg_if!` macro:
```rust
cfg_if! {
if #[cfg(feature = "wee_alloc")] {
extern crate wee_alloc;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
}
}
```
This code block is intended to initialize `wee_alloc` as the global memory allocator, but only if the `wee_alloc` feature is enabled in `Cargo.toml`.
To do so we need to append `"wee_alloc"` to the `default` vector in `Cargo.toml`. Then, the `cfg_if!` block is replaced with the contents of the `if` block, shown above.
This code block is intended to initialize `wee_alloc` as the global memory
allocator, but only if the `wee_alloc` feature is enabled at compile time. The
feature can be enabled by passing extra options while building:
```toml
[features]
default = ["console_error_panic_hook", "wee_alloc"]
```
## Rust nightly
`wee_alloc` currently relies on features only available in Rust nightly. As such it requires you to use the nightly toolchain for compilation. If you have [Rustup](https://rustup.rs/) set up, you can install the nightly toolchain as follows:
```
rustup toolchain add nightly
$ wasm-pack build -- --features wee_alloc
```
To use `wasm-pack` with Rust nightly run:
or alternatively you could turn it on by default in `Cargo.toml`:
```
rustup run nightly wasm-pack build
```toml
[features]
default = ["console_error_panic_hook", "wee_alloc"]
```

@ -0,0 +1,45 @@
# Testing your project
Now after writing and building code, let's actually execute it! You can execute
tests with:
```bash
$ wasm-pack test --firefox
[INFO]: Checking for the Wasm target...
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running target/wasm32-unknown-unknown/debug/deps/web-9e7d380f8600b08e.wasm
Interactive browsers tests are now available at http://127.0.0.1:8000
Note that interactive mode is enabled because `NO_HEADLESS`
is specified in the environment of this process. Once you're
done with testing you'll need to kill this server with
Ctrl-C.
```
The console won't finish just yet, but as indicated you can visit
http://127.0.0.1:8000 in your web browser to see the test output:
```
running 1 test
test web::pass ... ok
test result: ok. 1 passed; 0 failed; 0 ignored
```
and we've now executed our first tests in a web browser!
If you'd like to execute tests in a headless web browser (you don't need to
manually visit a page) you can do:
```bash
$ wasm-pack test --headless --firefox
```
and similarly if you're developing a project for Node.js you can also execute
`wasm-pack test --nodejs` to run tests in Node.
Be sure to see the [testing reference documentation][testing-reference] for
other supported features as well!
[testing-reference]: https://rustwasm.github.io/docs/wasm-bindgen/wasm-bindgen-test/index.html

@ -265,38 +265,27 @@ impl Build {
};
($($name:ident,)*) => (steps![$($name),*])
}
let mut steps = Vec::new();
match &mode {
BuildMode::Normal => steps![
step_check_rustc_version,
step_check_crate_config,
step_check_for_wasm_target,
step_build_wasm,
step_create_dir,
step_copy_readme,
step_copy_license,
step_install_wasm_bindgen,
step_run_wasm_bindgen,
step_create_json,
],
BuildMode::Noinstall => steps![
step_check_rustc_version,
step_check_crate_config,
step_build_wasm,
step_create_dir,
step_copy_readme,
step_copy_license,
step_run_wasm_bindgen,
step_create_json,
],
BuildMode::Force => steps![
step_build_wasm,
step_create_dir,
step_copy_readme,
step_copy_license,
step_run_wasm_bindgen,
step_create_json,
],
BuildMode::Force => {}
_ => {
steps.extend(steps![
step_check_rustc_version,
step_check_crate_config,
step_check_for_wasm_target,
]);
}
}
steps.extend(steps![
step_build_wasm,
step_create_dir,
step_copy_readme,
step_copy_license,
step_install_wasm_bindgen,
step_run_wasm_bindgen,
step_create_json,
]);
steps
}
fn step_check_rustc_version(&mut self) -> Result<(), Error> {

@ -212,3 +212,29 @@ fn build_with_arbitrary_cargo_options() {
.assert()
.success();
}
#[test]
fn build_no_install() {
let fixture = utils::fixture::js_hello_world();
fixture.install_local_wasm_bindgen();
fixture
.wasm_pack()
.arg("build")
.arg("--mode")
.arg("no-install")
.assert()
.success();
}
#[test]
fn build_force() {
let fixture = utils::fixture::js_hello_world();
fixture.install_local_wasm_bindgen();
fixture
.wasm_pack()
.arg("build")
.arg("--mode")
.arg("force")
.assert()
.success();
}

Loading…
Cancel
Save