diff --git a/crates/test-support/src/lib.rs b/crates/test-support/src/lib.rs index ec6a0b6f..2e6fdb69 100644 --- a/crates/test-support/src/lib.rs +++ b/crates/test-support/src/lib.rs @@ -15,7 +15,7 @@ pub struct Project { files: Vec<(String, String)>, debug: bool, js: bool, - detect_node: bool, + node: bool, } pub fn project() -> Project { @@ -29,7 +29,7 @@ pub fn project() -> Project { Project { debug: true, js: false, - detect_node: false, + node: false, files: vec![ ("Cargo.toml".to_string(), format!(r#" [package] @@ -134,8 +134,8 @@ impl Project { self } - pub fn detect_node(&mut self, detect_node: bool) -> &mut Project { - self.detect_node = detect_node; + pub fn node(&mut self, node: bool) -> &mut Project { + self.node = node; self } @@ -194,6 +194,7 @@ impl Project { cli::Bindgen::new() .input_path(&as_a_module) .typescript(true) + .nodejs(self.node) .debug(self.debug) .generate(&root) .expect("failed to run bindgen"); @@ -216,21 +217,28 @@ impl Project { let cwd = env::current_dir().unwrap(); symlink_dir(&cwd.join("node_modules"), &root.join("node_modules")).unwrap(); - let mut cmd = if cfg!(windows) { - let mut c = Command::new("cmd"); - c.arg("/c"); - c.arg("yarn"); - c + if self.node { + let mut cmd = Command::new("node"); + cmd.arg(root.join("out.js")) + .current_dir(&root); + run(&mut cmd, "node"); } else { - Command::new("yarn") - }; - cmd.arg("webpack").current_dir(&root); - run(&mut cmd, "node"); + let mut cmd = if cfg!(windows) { + let mut c = Command::new("cmd"); + c.arg("/c"); + c.arg("yarn"); + c + } else { + Command::new("yarn") + }; + cmd.arg("webpack").current_dir(&root); + run(&mut cmd, "node"); - let mut cmd = Command::new("node"); - cmd.arg(root.join("bundle.js")) - .current_dir(&root); - run(&mut cmd, "node"); + let mut cmd = Command::new("node"); + cmd.arg(root.join("bundle.js")) + .current_dir(&root); + run(&mut cmd, "node"); + } } } diff --git a/crates/wasm-bindgen-cli-support/src/js.rs b/crates/wasm-bindgen-cli-support/src/js.rs index 12894da0..bb68974d 100644 --- a/crates/wasm-bindgen-cli-support/src/js.rs +++ b/crates/wasm-bindgen-cli-support/src/js.rs @@ -11,6 +11,7 @@ use super::Bindgen; pub struct Context<'a> { pub globals: String, pub imports: String, + pub footer: String, pub typescript: String, pub exposed_globals: HashSet<&'static str>, pub required_internal_exports: HashSet<&'static str>, @@ -52,10 +53,14 @@ impl<'a> Context<'a> { } let contents = f(self); let contents = contents.trim(); - let global = if contents.starts_with("function") { - format!("export function {} {}\n", name, &contents[8..]) + let global = if self.config.nodejs { + format!("module.exports.{} = {};\n", name, contents) } else { - format!("export const {} = {};\n", name, contents) + if contents.starts_with("function") { + format!("export function {} {}\n", name, &contents[8..]) + } else { + format!("export const {} = {};\n", name, contents) + } }; self.globals.push_str(&global); }; @@ -212,16 +217,26 @@ impl<'a> Context<'a> { self.rewrite_imports(module_name); + let import_wasm = if self.config.nodejs { + self.footer.push_str(&format!("wasm = require('./{}_bg');", + module_name)); + format!("var wasm;") + } else { + format!("import * as wasm from './{}_bg';", module_name) + }; + let js = format!(" /* tslint:disable */ - import * as wasm from './{module_name}_bg'; // imports from wasm file + {import_wasm} {imports} {globals} + {footer} ", - module_name = module_name, + import_wasm = import_wasm, globals = self.globals, imports = self.imports, + footer = self.footer, ); self.unexport_unused_internal_exports(); @@ -233,7 +248,12 @@ impl<'a> Context<'a> { let classes = mem::replace(&mut self.exported_classes, Default::default()); for (class, exports) in classes { let mut dst = String::new(); - dst.push_str(&format!("export class {} {{", class)); + let global_export = if self.config.nodejs { + format!("module.exports.{} = class {} {{\n", class, class) + } else { + format!("export class {} {{", class) + }; + dst.push_str(&global_export); let mut ts_dst = dst.clone(); ts_dst.push_str(" public ptr: number; @@ -1019,8 +1039,17 @@ impl<'a, 'b> SubContext<'a, 'b> { &export.function.name, false, &export.function); - self.cx.globals.push_str("export "); + if self.cx.config.nodejs { + self.cx.globals.push_str("module.exports."); + self.cx.globals.push_str(&export.function.name); + self.cx.globals.push_str(" = "); + } else { + self.cx.globals.push_str("export "); + } self.cx.globals.push_str(&js); + if self.cx.config.nodejs { + self.cx.globals.push_str(";"); + } self.cx.globals.push_str("\n"); self.cx.typescript.push_str("export "); self.cx.typescript.push_str(&ts); @@ -1517,9 +1546,17 @@ impl<'a, 'b> SubContext<'a, 'b> { dst.push_str(&extra); dst.push_str(&format!("{}\n}}", invoc)); - self.cx.globals.push_str("export "); - self.cx.globals.push_str(&dst); - self.cx.globals.push_str("\n"); + if self.cx.config.nodejs { + self.cx.globals.push_str("module.exports."); + self.cx.globals.push_str(&import.shim); + self.cx.globals.push_str(" = "); + self.cx.globals.push_str(&dst); + self.cx.globals.push_str(";\n"); + } else { + self.cx.globals.push_str("export "); + self.cx.globals.push_str(&dst); + self.cx.globals.push_str("\n"); + } } pub fn generate_enum(&mut self, enum_: &shared::Enum) { @@ -1546,9 +1583,15 @@ impl<'a, 'b> SubContext<'a, 'b> { let name = import.js_namespace.as_ref().map(|s| &**s).unwrap_or(item); if self.cx.imported_names.insert(name.to_string()) { - self.cx.imports.push_str(&format!(" - import {{ {} }} from '{}'; - ", name, module)); + if self.cx.config.nodejs { + self.cx.imports.push_str(&format!(" + const {} = require('{}').{}; + ", name, module, name)); + } else { + self.cx.imports.push_str(&format!(" + import {{ {} }} from '{}'; + ", name, module)); + } } } match import.js_namespace { diff --git a/crates/wasm-bindgen-cli-support/src/lib.rs b/crates/wasm-bindgen-cli-support/src/lib.rs index 4c7296cc..c10b0054 100644 --- a/crates/wasm-bindgen-cli-support/src/lib.rs +++ b/crates/wasm-bindgen-cli-support/src/lib.rs @@ -4,6 +4,7 @@ extern crate serde_json; extern crate wasm_gc; use std::char; +use std::collections::BTreeSet; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; @@ -86,6 +87,7 @@ impl Bindgen { let mut cx = js::Context { globals: String::new(), imports: String::new(), + footer: String::new(), typescript: format!("/* tslint:disable */\n"), exposed_globals: Default::default(), required_internal_exports: Default::default(), @@ -118,6 +120,13 @@ impl Bindgen { } let wasm_path = out_dir.join(format!("{}_bg", stem)).with_extension("wasm"); + + if self.nodejs { + let js_path = wasm_path.with_extension("js"); + let shim = self.generate_node_wasm_import(&module, &wasm_path); + File::create(&js_path)?.write_all(shim.as_bytes())?; + } + let wasm_bytes = parity_wasm::serialize(module).map_err(|e| { Error(format!("{:?}", e)) })?; @@ -127,6 +136,30 @@ impl Bindgen { File::create(&wasm_path)?.write_all(&bytes)?; Ok(()) } + + fn generate_node_wasm_import(&self, m: &Module, path: &Path) -> String { + let mut imports = BTreeSet::new(); + if let Some(i) = m.import_section() { + for i in i.entries() { + imports.insert(i.module()); + } + } + + let mut shim = String::new(); + shim.push_str("let imports = {};\n"); + for module in imports { + shim.push_str(&format!("imports['{0}'] = require('{0}');\n", module)); + } + + shim.push_str(&format!(" + const bytes = require('fs').readFileSync('{}'); + const wasmModule = new WebAssembly.Module(bytes); + const wasmInstance = new WebAssembly.Instance(wasmModule, imports); + module.exports = wasmInstance.exports; + ", path.file_name().unwrap().to_str().unwrap())); + + shim + } } fn extract_programs(module: &mut Module) -> Vec { diff --git a/tests/api.rs b/tests/api.rs index 26509564..60be9c45 100644 --- a/tests/api.rs +++ b/tests/api.rs @@ -3,7 +3,6 @@ extern crate test_support; #[test] fn works() { test_support::project() - .detect_node(true) .file("src/lib.rs", r#" #![feature(proc_macro, wasm_custom_section)] diff --git a/tests/node.rs b/tests/node.rs new file mode 100644 index 00000000..d0118e6b --- /dev/null +++ b/tests/node.rs @@ -0,0 +1,75 @@ +extern crate test_support; + +#[test] +fn works() { + test_support::project() + .node(true) + .file("src/lib.rs", r#" + #![feature(proc_macro, wasm_custom_section, wasm_import_module)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(module = "./test")] + extern { + fn hit(); + } + + #[wasm_bindgen] + pub fn run() { + hit(); + } + + #[wasm_bindgen] + pub struct Foo { + contents: u32, + } + + #[wasm_bindgen] + impl Foo { + pub fn new() -> Foo { + Foo::with_contents(0) + } + pub fn with_contents(a: u32) -> Foo { + Foo { contents: a } + } + pub fn add(&mut self, amt: u32) -> u32 { + self.contents += amt; + self.contents + } + } + "#) + .file("test.js", r#" + const assert = require('assert'); + const run = require('./out'); + + var called = false; + + module.exports.hit = function() { + called = true; + }; + + module.exports.test = function() { + run(); + assert.strictEqual(called, true); + var Foo = run.Foo; + + var r = Foo.new(); + assert.strictEqual(r.contents, 0); + assert.strictEqual(r.add(0), 0); + assert.strictEqual(r.add(1), 1); + assert.strictEqual(r.add(2), 2); + r.free(); + + var r2 = Foo.with_contents(10); + assert.strictEqual(r2.contents, 10); + assert.strictEqual(r2.add(0), 0); + assert.strictEqual(r2.add(1), 1); + assert.strictEqual(r2.add(2), 2); + r2.free(); + }; + + "#) + .test(); +} diff --git a/tests/structural.rs b/tests/structural.rs index 7840d43d..2d601704 100644 --- a/tests/structural.rs +++ b/tests/structural.rs @@ -3,7 +3,6 @@ extern crate test_support; #[test] fn works() { test_support::project() - .detect_node(true) .file("src/lib.rs", r#" #![feature(proc_macro, wasm_custom_section, wasm_import_module)]