From 79f96af262185f4326cd49c8fe72343a6b757be8 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Mon, 1 Jun 2020 15:05:44 +0200 Subject: [PATCH 1/5] add deno target --- crates/cli-support/src/js/mod.rs | 51 ++++++++++++++++++++++++++++-- crates/cli-support/src/lib.rs | 19 ++++++++++- crates/cli/src/bin/wasm-bindgen.rs | 3 +- 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 4444d1ef6d7..a26a489d815 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -143,7 +143,8 @@ impl<'a> Context<'a> { | OutputMode::Node { experimental_modules: true, } - | OutputMode::Web => { + | OutputMode::Web + | OutputMode::Deno => { if contents.starts_with("function") { let body = &contents[8..]; if export_name == definition_name { @@ -269,6 +270,40 @@ impl<'a> Context<'a> { reset_indentation(&shim) } + // generates somthing like + // ```js + // const imports = { + // __wbindgen_placeholder__: { + // __wbindgen_throw: function(..) { .. }, + // .. + // } + // } + // ``` + fn generate_deno_imports(&self) -> String { + let mut imports = "const imports = {\n".to_string(); + imports.push_str(&format!(" {}: {{\n", crate::PLACEHOLDER_MODULE)); + + for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) { + let import = self.module.imports.get(*id); + imports.push_str(&format!("{}: {},\n", &import.name, js.trim())); + } + + imports.push_str("\t}\n};\n\n"); + + imports + } + + fn generate_deno_wasm_loading(&self, module_name: &str) -> String { + format!( + "const file = new URL(import.meta.url).pathname; + const wasmFile = file.substring(0, file.lastIndexOf(Deno.build.os === 'windows' ? '\\\\' : '/') + 1) + '{}_bg.wasm'; + const wasmModule = new WebAssembly.Module(Deno.readFileSync(wasmFile)); + const wasmInstance = new WebAssembly.Instance(wasmModule, imports); + const wasm = wasmInstance.exports;", + module_name + ) + } + /// Performs the task of actually generating the final JS module, be it /// `--target no-modules`, `--target web`, or for bundlers. This is the very /// last step performed in `finalize`. @@ -331,6 +366,15 @@ impl<'a> Context<'a> { } } + OutputMode::Deno => { + footer.push_str(&self.generate_deno_imports()); + footer.push_str(&self.generate_deno_wasm_loading(module_name)); + + if needs_manual_start { + footer.push_str("wasm.__wbindgen_start();\n"); + } + } + // With Bundlers and modern ES6 support in Node we can simply import // the wasm file as if it were an ES module and let the // bundler/runtime take care of it. @@ -443,7 +487,8 @@ impl<'a> Context<'a> { | OutputMode::Node { experimental_modules: true, } - | OutputMode::Web => { + | OutputMode::Web + | OutputMode::Deno => { for (module, items) in crate::sorted_iter(&self.js_imports) { imports.push_str("import { "); for (i, (item, rename)) in items.iter().enumerate() { @@ -1247,7 +1292,7 @@ impl<'a> Context<'a> { fields: Vec::new(), })?; self.global(&format!("let cached{} = new {}{};", s, name, args)); - } else if !self.config.mode.always_run_in_browser() { + } else if !self.config.mode.always_run_in_browser() && !self.config.mode.deno() { self.global(&format!( " const l{0} = typeof {0} === 'undefined' ? \ diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index dd2979f16b7..ffbed9c13ec 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -74,6 +74,7 @@ enum OutputMode { Web, NoModules { global: String }, Node { experimental_modules: bool }, + Deno, } enum Input { @@ -210,6 +211,14 @@ impl Bindgen { Ok(self) } + pub fn deno(&mut self, deno: bool) -> Result<&mut Bindgen, Error> { + if deno { + self.switch_mode(OutputMode::Deno, "--target deno")?; + self.encode_into(EncodeInto::Always); + } + Ok(self) + } + pub fn no_modules_global(&mut self, name: &str) -> Result<&mut Bindgen, Error> { match &mut self.mode { OutputMode::NoModules { global } => *global = name.to_string(), @@ -526,7 +535,8 @@ impl OutputMode { | OutputMode::Web | OutputMode::Node { experimental_modules: true, - } => true, + } + | OutputMode::Deno => true, _ => false, } } @@ -570,6 +580,13 @@ impl OutputMode { } } + fn deno(&self) -> bool { + match self { + OutputMode::Deno { .. } => true, + _ => false, + } + } + fn esm_integration(&self) -> bool { match self { OutputMode::Bundler { .. } diff --git a/crates/cli/src/bin/wasm-bindgen.rs b/crates/cli/src/bin/wasm-bindgen.rs index 9e9969a63a3..6a633c0b5a3 100644 --- a/crates/cli/src/bin/wasm-bindgen.rs +++ b/crates/cli/src/bin/wasm-bindgen.rs @@ -22,7 +22,7 @@ Options: --out-dir DIR Output directory --out-name VAR Set a custom output filename (Without extension. Defaults to crate name) --target TARGET What type of output to generate, valid - values are [web, bundler, nodejs, no-modules], + values are [web, bundler, nodejs, no-modules, deno], and the default is [bundler] --no-modules-global VAR Name of the global variable to initialize --browser Hint that JS should only be compatible with a browser @@ -98,6 +98,7 @@ fn rmain(args: &Args) -> Result<(), Error> { "web" => b.web(true)?, "no-modules" => b.no_modules(true)?, "nodejs" => b.nodejs(true)?, + "deno" => b.deno(true)?, s => bail!("invalid encode-into mode: `{}`", s), }; } From 84c7cf01ce5941e03e59cbcec97609f4327b6743 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Tue, 2 Jun 2020 10:53:21 +0200 Subject: [PATCH 2/5] address pr comments --- Cargo.toml | 1 + azure-pipelines.yml | 12 +++++++- ci/azure-install-deno.yml | 3 ++ crates/cli-support/src/js/mod.rs | 47 +++++++++++++++++++------------ crates/cli-support/src/lib.rs | 16 ----------- examples/deno/Cargo.toml | 11 ++++++++ examples/deno/README.md | 16 +++++++++++ examples/deno/build.sh | 7 +++++ examples/deno/crate/.gitignore | 1 + examples/deno/defined-in-js.js | 17 +++++++++++ examples/deno/src/lib.rs | 37 ++++++++++++++++++++++++ examples/deno/test.ts | 3 ++ guide/src/reference/deployment.md | 16 ++++++++++- 13 files changed, 151 insertions(+), 36 deletions(-) create mode 100644 ci/azure-install-deno.yml create mode 100644 examples/deno/Cargo.toml create mode 100644 examples/deno/README.md create mode 100755 examples/deno/build.sh create mode 100644 examples/deno/crate/.gitignore create mode 100644 examples/deno/defined-in-js.js create mode 100644 examples/deno/src/lib.rs create mode 100644 examples/deno/test.ts diff --git a/Cargo.toml b/Cargo.toml index 05d3fe82c22..505c98b24ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ members = [ "examples/char", "examples/closures", "examples/console_log", + "examples/deno", "examples/dom", "examples/duck-typed-interfaces", "examples/fetch", diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 968a12fb010..10d29b26ecf 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -167,7 +167,7 @@ jobs: - script: mv _package.json package.json && npm install && rm package.json displayName: "run npm install" - script: | - for dir in `ls examples | grep -v README | grep -v asm.js | grep -v raytrace | grep -v without-a-bundler | grep -v websockets | grep -v webxr`; do + for dir in `ls examples | grep -v README | grep -v asm.js | grep -v raytrace | grep -v without-a-bundler | grep -v websockets | grep -v webxr | grep -v deno`; do (cd examples/$dir && ln -fs ../../node_modules . && npm run build -- --output-path $BUILD_ARTIFACTSTAGINGDIRECTORY/exbuild/$dir) || exit 1; @@ -178,6 +178,16 @@ jobs: artifactName: examples1 targetPath: '$(Build.ArtifactStagingDirectory)' + - job: test_deno + displayName: "Build and test the deno example" + steps: + - template: ci/azure-install-rust.yml + - script: rustup target add wasm32-unknown-unknown + displayName: "install wasm target" + - template: ci/azure-install-deno.yml + - script: cd examples/deno && ./build.sh && $HOME/.deno/bin/deno run --allow-read test.ts + displayName: "build and run deno example" + - job: build_raytrace displayName: "Build raytrace examples" steps: diff --git a/ci/azure-install-deno.yml b/ci/azure-install-deno.yml new file mode 100644 index 00000000000..b995c7c8bbf --- /dev/null +++ b/ci/azure-install-deno.yml @@ -0,0 +1,3 @@ +steps: + - script: curl -fsSL https://deno.land/x/install/install.sh | sh + displayName: "install deno" diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index a26a489d815..60a6b2a5b8d 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -294,6 +294,8 @@ impl<'a> Context<'a> { } fn generate_deno_wasm_loading(&self, module_name: &str) -> String { + // Deno removed support for .wasm imports in https://github.com/denoland/deno/pull/5135 + // the issue for bringing it back is https://github.com/denoland/deno/issues/5609. format!( "const file = new URL(import.meta.url).pathname; const wasmFile = file.substring(0, file.lastIndexOf(Deno.build.os === 'windows' ? '\\\\' : '/') + 1) + '{}_bg.wasm'; @@ -1283,27 +1285,36 @@ impl<'a> Context<'a> { } fn expose_text_processor(&mut self, s: &str, args: &str) -> Result<(), Error> { - if self.config.mode.nodejs() { - let name = self.import_name(&JsImport { - name: JsImportName::Module { - module: "util".to_string(), - name: s.to_string(), - }, - fields: Vec::new(), - })?; - self.global(&format!("let cached{} = new {}{};", s, name, args)); - } else if !self.config.mode.always_run_in_browser() && !self.config.mode.deno() { - self.global(&format!( - " + match &self.config.mode { + OutputMode::Node { .. } => { + let name = self.import_name(&JsImport { + name: JsImportName::Module { + module: "util".to_string(), + name: s.to_string(), + }, + fields: Vec::new(), + })?; + self.global(&format!("let cached{} = new {}{};", s, name, args)); + } + OutputMode::Bundler { + browser_only: false, + } => { + self.global(&format!( + " const l{0} = typeof {0} === 'undefined' ? \ (0, module.require)('util').{0} : {0};\ ", - s - )); - self.global(&format!("let cached{0} = new l{0}{1};", s, args)); - } else { - self.global(&format!("let cached{0} = new {0}{1};", s, args)); - } + s + )); + self.global(&format!("let cached{0} = new l{0}{1};", s, args)); + } + OutputMode::Deno + | OutputMode::Web + | OutputMode::NoModules { .. } + | OutputMode::Bundler { browser_only: true } => { + self.global(&format!("let cached{0} = new {0}{1};", s, args)) + } + }; Ok(()) } diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index ffbed9c13ec..1e73d53450c 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -564,15 +564,6 @@ impl OutputMode { } } - fn always_run_in_browser(&self) -> bool { - match self { - OutputMode::Web => true, - OutputMode::NoModules { .. } => true, - OutputMode::Bundler { browser_only } => *browser_only, - _ => false, - } - } - fn web(&self) -> bool { match self { OutputMode::Web => true, @@ -580,13 +571,6 @@ impl OutputMode { } } - fn deno(&self) -> bool { - match self { - OutputMode::Deno { .. } => true, - _ => false, - } - } - fn esm_integration(&self) -> bool { match self { OutputMode::Bundler { .. } diff --git a/examples/deno/Cargo.toml b/examples/deno/Cargo.toml new file mode 100644 index 00000000000..b296e909003 --- /dev/null +++ b/examples/deno/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "deno" +version = "0.1.0" +authors = ["The wasm-bindgen Developers"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wasm-bindgen = "0.2.63" diff --git a/examples/deno/README.md b/examples/deno/README.md new file mode 100644 index 00000000000..3df58384fa8 --- /dev/null +++ b/examples/deno/README.md @@ -0,0 +1,16 @@ +# Using deno + +You can build the example with + +```sh +$ ./build.sh +``` + +and test it with + +```sh +$ deno run --allow-read test.ts +``` + +The `--allow-read` flag is needed because the wasm file is read during runtime. +This will be fixed when https://github.com/denoland/deno/issues/5609 is resolved. diff --git a/examples/deno/build.sh b/examples/deno/build.sh new file mode 100755 index 00000000000..722cb11fd8f --- /dev/null +++ b/examples/deno/build.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +set -eux + +cargo build --target wasm32-unknown-unknown --release +cargo run --package wasm-bindgen-cli --bin wasm-bindgen -- \ + --out-dir pkg --target deno ${CARGO_TARGET_DIR:-../../target}/wasm32-unknown-unknown/release/deno.wasm diff --git a/examples/deno/crate/.gitignore b/examples/deno/crate/.gitignore new file mode 100644 index 00000000000..09c2139a9cf --- /dev/null +++ b/examples/deno/crate/.gitignore @@ -0,0 +1 @@ +/deno.wasm diff --git a/examples/deno/defined-in-js.js b/examples/deno/defined-in-js.js new file mode 100644 index 00000000000..51490b86918 --- /dev/null +++ b/examples/deno/defined-in-js.js @@ -0,0 +1,17 @@ +export class MyClass { + constructor() { + this._number = 42; + } + + get number() { + return this._number; + } + + set number(n) { + return (this._number = n); + } + + render() { + return `My number is: ${this.number}`; + } +} diff --git a/examples/deno/src/lib.rs b/examples/deno/src/lib.rs new file mode 100644 index 00000000000..bee2ec64d6e --- /dev/null +++ b/examples/deno/src/lib.rs @@ -0,0 +1,37 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(module = "/defined-in-js.js")] +extern "C" { + type MyClass; + + #[wasm_bindgen(constructor)] + fn new() -> MyClass; + + #[wasm_bindgen(method, getter)] + fn number(this: &MyClass) -> u32; + #[wasm_bindgen(method, setter)] + fn set_number(this: &MyClass, number: u32) -> MyClass; + #[wasm_bindgen(method)] + fn render(this: &MyClass) -> String; +} + +// lifted from the `console_log` example +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); +} + +#[wasm_bindgen(inline_js = "export function add(a, b) { return a + b; }")] +extern "C" { + fn add(a: u32, b: u32) -> u32; +} + +#[wasm_bindgen] +pub fn greet(name: String) { + log(&format!("Hello from {}!", name)); // should output "Hello from Rust!" + + let x = MyClass::new(); + assert_eq!(x.number(), add(40, 2)); + log(&x.render()); +} diff --git a/examples/deno/test.ts b/examples/deno/test.ts new file mode 100644 index 00000000000..e61a31336fc --- /dev/null +++ b/examples/deno/test.ts @@ -0,0 +1,3 @@ +import { greet } from "./pkg/deno.js"; + +greet("Deno"); diff --git a/guide/src/reference/deployment.md b/guide/src/reference/deployment.md index c0c8323f97d..7ff38610ba0 100644 --- a/guide/src/reference/deployment.md +++ b/guide/src/reference/deployment.md @@ -14,12 +14,14 @@ should behave the same way in this respect. The values possible here are: | [`bundler`] | Suitable for loading in bundlers like Webpack | | [`web`] | Directly loadable in a web browser | | [`nodejs`] | Loadable via `require` as a Node.js module | +| [`deno`] | Loadable using imports from Deno modules | | [`no-modules`] | Like `web`, but older and doesn't use ES modules | [`bundler`]: #bundlers [`web`]: #without-a-bundler [`no-modules`]: #without-a-bundler [`nodejs`]: #nodejs +[`deno`]: #Deno ## Bundlers @@ -69,7 +71,7 @@ documentation, but the highlights of this output are: The CLI also supports an output mode called `--target no-modules` which is similar to the `web` target in that it requires manual initialization of the wasm and is intended to be included in web pages without any further -postprocessing. See the [without a bundler example][nomex] for some more +postprocessing. See the [without a bundler example][nomex] for some more information about `--target no-modules`. ## Node.js @@ -88,6 +90,18 @@ as it has a JS shim generated as well). Note that this method requires a version of Node.js with WebAssembly support, which is currently Node 8 and above. +## Deno + +**`--target deno`** + +To deploy WebAssembly to Deno, use the `--target deno` flag. +To then import your module inside deno, use + +```ts +// @deno-types="./out/crate_name.d.ts" +import { yourFunction } from "./out/crate_name.js"; +``` + ## NPM If you'd like to deploy compiled WebAssembly to NPM, then the tool for the job From addb0824d19ec942fa483ef43ecc08a10171aa17 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Wed, 3 Jun 2020 22:30:27 +0200 Subject: [PATCH 3/5] fix deno import logic to include non-placeholder-module imports --- crates/cli-support/src/js/mod.rs | 40 +++++++++++++++++++++++++------- examples/deno/src/lib.rs | 5 ++++ 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 60a6b2a5b8d..159b65ffc39 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -272,25 +272,46 @@ impl<'a> Context<'a> { // generates somthing like // ```js + // import * as import0 from './snippets/.../inline1.js'; + // ```, + // + // ```js // const imports = { // __wbindgen_placeholder__: { // __wbindgen_throw: function(..) { .. }, // .. - // } + // }, + // './snippets/deno-65e2634a84cc3c14/inline1.js': import0, // } // ``` - fn generate_deno_imports(&self) -> String { - let mut imports = "const imports = {\n".to_string(); - imports.push_str(&format!(" {}: {{\n", crate::PLACEHOLDER_MODULE)); + fn generate_deno_imports(&self) -> (String, String) { + let mut imports = String::new(); + let mut wasm_import_object = "const imports = {\n".to_string(); + + wasm_import_object.push_str(&format!(" {}: {{\n", crate::PLACEHOLDER_MODULE)); for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) { let import = self.module.imports.get(*id); - imports.push_str(&format!("{}: {},\n", &import.name, js.trim())); + wasm_import_object.push_str(&format!("{}: {},\n", &import.name, js.trim())); + } + + wasm_import_object.push_str("\t},\n"); + + // e.g. snippets without parameters + let import_modules = self + .module + .imports + .iter() + .map(|import| &import.module) + .filter(|module| module.as_str() != PLACEHOLDER_MODULE); + for (i, module) in import_modules.enumerate() { + imports.push_str(&format!("import * as import{} from '{}'\n", i, module)); + wasm_import_object.push_str(&format!(" '{}': import{},", module, i)) } - imports.push_str("\t}\n};\n\n"); + wasm_import_object.push_str("\n};\n\n"); - imports + (imports, wasm_import_object) } fn generate_deno_wasm_loading(&self, module_name: &str) -> String { @@ -369,7 +390,10 @@ impl<'a> Context<'a> { } OutputMode::Deno => { - footer.push_str(&self.generate_deno_imports()); + let (js_imports, wasm_import_object) = self.generate_deno_imports(); + imports.push_str(&js_imports); + footer.push_str(&wasm_import_object); + footer.push_str(&self.generate_deno_wasm_loading(module_name)); if needs_manual_start { diff --git a/examples/deno/src/lib.rs b/examples/deno/src/lib.rs index bee2ec64d6e..31a035de5c3 100644 --- a/examples/deno/src/lib.rs +++ b/examples/deno/src/lib.rs @@ -26,6 +26,10 @@ extern "C" { extern "C" { fn add(a: u32, b: u32) -> u32; } +#[wasm_bindgen(inline_js = "export function test() {}")] +extern "C" { + fn test(); +} #[wasm_bindgen] pub fn greet(name: String) { @@ -33,5 +37,6 @@ pub fn greet(name: String) { let x = MyClass::new(); assert_eq!(x.number(), add(40, 2)); + test(); log(&x.render()); } From 77bf0e9e6b124dffdeefab35ba790502fd7bc049 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Wed, 3 Jun 2020 16:54:39 +0200 Subject: [PATCH 4/5] make wasm-bindgen-test-runner easier to expand --- .../src/bin/wasm-bindgen-test-runner/main.rs | 103 ++++++++++-------- 1 file changed, 59 insertions(+), 44 deletions(-) diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs index a32121bdc91..45e06a1c298 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs @@ -27,6 +27,12 @@ mod node; mod server; mod shell; +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +enum TestMode { + Node, + Browser, +} + fn main() -> anyhow::Result<()> { env_logger::init(); let mut args = env::args_os().skip(1); @@ -81,14 +87,20 @@ fn main() -> anyhow::Result<()> { // That's done on a per-test-binary basis with the // `wasm_bindgen_test_configure` macro, which emits a custom section for us // to read later on. - let mut node = true; - if let Some(section) = wasm.customs.remove_raw("__wasm_bindgen_test_unstable") { - node = !section.data.contains(&0x01); - } + + let custom_section = wasm.customs.remove_raw("__wasm_bindgen_test_unstable"); + let test_mode = match custom_section { + Some(section) if section.data.contains(&0x01) => TestMode::Browser, + Some(_) => bail!("invalid __wasm_bingen_test_unstable value"), + None => TestMode::Node, + }; + let headless = env::var("NO_HEADLESS").is_err(); let debug = env::var("WASM_BINDGEN_NO_DEBUG").is_err(); // Gracefully handle requests to execute only node or only web tests. + let node = test_mode == TestMode::Node; + if env::var_os("WASM_BINDGEN_TEST_ONLY_NODE").is_some() { if !node { println!( @@ -131,9 +143,12 @@ integration test.\ // Make the generated bindings available for the tests to execute against. shell.status("Executing bindgen..."); let mut b = Bindgen::new(); + match test_mode { + TestMode::Node => b.nodejs(true)?, + TestMode::Browser => b.web(true)?, + }; + b.debug(debug) - .nodejs(node)? - .web(!node)? .input_module(module, wasm) .keep_debug(false) .emit_start(false) @@ -141,44 +156,44 @@ integration test.\ .context("executing `wasm-bindgen` over the wasm file")?; shell.clear(); - // If we're executing in node.js, that module will take it from here. - if node { - return node::execute(&module, &tmpdir, &args.collect::>(), &tests); - } - - // Otherwise we're executing in a browser. Spawn a server which serves up - // the local generated files over an HTTP server. - let srv = server::spawn( - &if headless { - "127.0.0.1:0".parse().unwrap() - } else { - "127.0.0.1:8000".parse().unwrap() - }, - headless, - &module, - &tmpdir, - &args.collect::>(), - &tests, - ) - .context("failed to spawn server")?; - let addr = srv.server_addr(); - - // TODO: eventually we should provide the ability to exit at some point - // (gracefully) here, but for now this just runs forever. - if !headless { - println!( - "Interactive browsers tests are now available at http://{}", - addr - ); - println!(""); - println!("Note that interactive mode is enabled because `NO_HEADLESS`"); - println!("is specified in the environment of this process. Once you're"); - println!("done with testing you'll need to kill this server with"); - println!("Ctrl-C."); - return Ok(srv.run()); + let args: Vec<_> = args.collect(); + + match test_mode { + TestMode::Node => node::execute(&module, &tmpdir, &args, &tests)?, + TestMode::Browser => { + let srv = server::spawn( + &if headless { + "127.0.0.1:0".parse().unwrap() + } else { + "127.0.0.1:8000".parse().unwrap() + }, + headless, + &module, + &tmpdir, + &args, + &tests, + ) + .context("failed to spawn server")?; + let addr = srv.server_addr(); + + // TODO: eventually we should provide the ability to exit at some point + // (gracefully) here, but for now this just runs forever. + if !headless { + println!( + "Interactive browsers tests are now available at http://{}", + addr + ); + println!(""); + println!("Note that interactive mode is enabled because `NO_HEADLESS`"); + println!("is specified in the environment of this process. Once you're"); + println!("done with testing you'll need to kill this server with"); + println!("Ctrl-C."); + return Ok(srv.run()); + } + + thread::spawn(|| srv.run()); + headless::run(&addr, &shell, timeout)?; + } } - - thread::spawn(|| srv.run()); - headless::run(&addr, &shell, timeout)?; Ok(()) } From 665785e690b501ce578128cde5d039263f856167 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Wed, 3 Jun 2020 19:18:08 +0200 Subject: [PATCH 5/5] add deno test mode, enabled by WASM_BINDGEN_USE_DENO=1 --- .../src/bin/wasm-bindgen-test-runner/deno.rs | 73 +++++++++++++++++++ .../src/bin/wasm-bindgen-test-runner/main.rs | 5 ++ .../src/bin/wasm-bindgen-test-runner/node.rs | 71 +++++++++--------- 3 files changed, 116 insertions(+), 33 deletions(-) create mode 100644 crates/cli/src/bin/wasm-bindgen-test-runner/deno.rs diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/deno.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/deno.rs new file mode 100644 index 00000000000..a0b05cdbd5b --- /dev/null +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/deno.rs @@ -0,0 +1,73 @@ +use std::ffi::OsString; +use std::fs; +use std::path::Path; +use std::process::Command; + +use anyhow::{Context, Error}; + +use crate::node::{exec, SHARED_SETUP}; + +pub fn execute( + module: &str, + tmpdir: &Path, + args: &[OsString], + tests: &[String], +) -> Result<(), Error> { + let mut js_to_execute = format!( + r#"import * as wasm from "./{0}.js"; + + {console_override} + + // global.__wbg_test_invoke = f => f(); + + // Forward runtime arguments. These arguments are also arguments to the + // `wasm-bindgen-test-runner` which forwards them to deno which we + // forward to the test harness. this is basically only used for test + // filters for now. + cx.args(Deno.args.slice(1)); + + const ok = await cx.run(tests.map(n => wasm.__wasm[n])); + if (!ok) Deno.exit(1); + + const tests = []; + "#, + module, + console_override = SHARED_SETUP, + ); + + for test in tests { + js_to_execute.push_str(&format!("tests.push('{}')\n", test)); + } + + let js_path = tmpdir.join("run.js"); + fs::write(&js_path, js_to_execute).context("failed to write JS file")?; + + /* + // Augment `NODE_PATH` so things like `require("tests/my-custom.js")` work + // and Rust code can import from custom JS shims. This is a bit of a hack + // and should probably be removed at some point. + let path = env::var("NODE_PATH").unwrap_or_default(); + let mut path = env::split_paths(&path).collect::>(); + path.push(env::current_dir().unwrap()); + path.push(tmpdir.to_path_buf()); + let extra_node_args = env::var("NODE_ARGS") + .unwrap_or_default() + .split(",") + .map(|s| s.to_string()) + .filter(|s| !s.is_empty()) + .collect::>(); + exec( + Command::new("node") + .env("NODE_PATH", env::join_paths(&path).unwrap()) + .args(&extra_node_args) + .arg(&js_path) + .args(args), + )*/ + exec( + Command::new("deno") + .arg("run") + .arg("--allow-read") + .arg(&js_path) + .args(args), + ) +} diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs index 45e06a1c298..95a82601702 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs @@ -22,6 +22,7 @@ use wasm_bindgen_cli_support::Bindgen; #[global_allocator] static ALLOC: std::alloc::System = std::alloc::System; +mod deno; mod headless; mod node; mod server; @@ -30,6 +31,7 @@ mod shell; #[derive(Debug, Copy, Clone, Eq, PartialEq)] enum TestMode { Node, + Deno, Browser, } @@ -92,6 +94,7 @@ fn main() -> anyhow::Result<()> { let test_mode = match custom_section { Some(section) if section.data.contains(&0x01) => TestMode::Browser, Some(_) => bail!("invalid __wasm_bingen_test_unstable value"), + None if std::env::var("WASM_BINDGEN_USE_DENO").is_ok() => TestMode::Deno, None => TestMode::Node, }; @@ -145,6 +148,7 @@ integration test.\ let mut b = Bindgen::new(); match test_mode { TestMode::Node => b.nodejs(true)?, + TestMode::Deno => b.deno(true)?, TestMode::Browser => b.web(true)?, }; @@ -160,6 +164,7 @@ integration test.\ match test_mode { TestMode::Node => node::execute(&module, &tmpdir, &args, &tests)?, + TestMode::Deno => deno::execute(&module, &tmpdir, &args, &tests)?, TestMode::Browser => { let srv = server::spawn( &if headless { diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs index 221aa00ee9d..0b389682aa7 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs @@ -6,6 +6,38 @@ use std::process::Command; use anyhow::{Context, Error}; +// depends on the variable 'wasm' and initializes te WasmBindgenTestContext cx +pub const SHARED_SETUP: &str = r#" +const handlers = {}; + +const wrap = method => { + const og = console[method]; + const on_method = `on_console_${method}`; + console[method] = function (...args) { + og.apply(this, args); + if (handlers[on_method]) { + handlers[on_method](args); + } + }; +}; + +// override `console.log` and `console.error` etc... before we import tests to +// ensure they're bound correctly in wasm. This'll allow us to intercept +// all these calls and capture the output of tests +wrap("debug"); +wrap("log"); +wrap("info"); +wrap("warn"); +wrap("error"); + +cx = new wasm.WasmBindgenTestContext(); +handlers.on_console_debug = wasm.__wbgtest_console_debug; +handlers.on_console_log = wasm.__wbgtest_console_log; +handlers.on_console_info = wasm.__wbgtest_console_info; +handlers.on_console_warn = wasm.__wbgtest_console_warn; +handlers.on_console_error = wasm.__wbgtest_console_error; +"#; + pub fn execute( module: &str, tmpdir: &Path, @@ -15,41 +47,13 @@ pub fn execute( let mut js_to_execute = format!( r#" const {{ exit }} = require('process'); + const wasm = require("./{0}"); - const handlers = {{}}; - - const wrap = method => {{ - const og = console[method]; - const on_method = `on_console_${{method}}`; - console[method] = function (...args) {{ - og.apply(this, args); - if (handlers[on_method]) {{ - handlers[on_method](args); - }} - }}; - }}; - - // override `console.log` and `console.error` etc... before we import tests to - // ensure they're bound correctly in wasm. This'll allow us to intercept - // all these calls and capture the output of tests - wrap("debug"); - wrap("log"); - wrap("info"); - wrap("warn"); - wrap("error"); + {console_override} global.__wbg_test_invoke = f => f(); async function main(tests) {{ - const wasm = require("./{0}"); - - cx = new wasm.WasmBindgenTestContext(); - handlers.on_console_debug = wasm.__wbgtest_console_debug; - handlers.on_console_log = wasm.__wbgtest_console_log; - handlers.on_console_info = wasm.__wbgtest_console_info; - handlers.on_console_warn = wasm.__wbgtest_console_warn; - handlers.on_console_error = wasm.__wbgtest_console_error; - // Forward runtime arguments. These arguments are also arguments to the // `wasm-bindgen-test-runner` which forwards them to node which we // forward to the test harness. this is basically only used for test @@ -63,7 +67,8 @@ pub fn execute( const tests = []; "#, - module + module, + console_override = SHARED_SETUP, ); // Note that we're collecting *JS objects* that represent the functions to @@ -109,7 +114,7 @@ pub fn execute( } #[cfg(unix)] -fn exec(cmd: &mut Command) -> Result<(), Error> { +pub fn exec(cmd: &mut Command) -> Result<(), Error> { use std::os::unix::prelude::*; Err(Error::from(cmd.exec()) .context("failed to execute `node`") @@ -117,7 +122,7 @@ fn exec(cmd: &mut Command) -> Result<(), Error> { } #[cfg(windows)] -fn exec(cmd: &mut Command) -> Result<(), Error> { +pub fn exec(cmd: &mut Command) -> Result<(), Error> { use std::process; let status = cmd.status()?; process::exit(status.code().unwrap_or(3));