diff --git a/.travis.yml b/.travis.yml index 7780687c811..f5ab4bc5e4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -66,6 +66,8 @@ matrix: (cd examples/closures && sed -i 's/: "webpack-dev-server"/: "webpack"/' package.json && ./build.sh) - | (cd examples/comments && sed -i 's/: "webpack-dev-server"/: "webpack"/' package.json && ./build.sh) + - | + (cd examples/node_and_browser && sed -i 's/: "webpack-dev-server"/: "webpack"/' package.json && ./build.sh && npm start) # Build the guide. - rust: stable diff --git a/Cargo.toml b/Cargo.toml index 40ff56ac927..4a28a6a58c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,8 @@ members = [ "examples/asm.js", "examples/char", "examples/import_js", - "examples/comments" + "examples/comments", + "examples/node_and_browser" ] [profile.release] diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index fde0e40ae6e..241d8d3d1d8 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -55,12 +55,11 @@ pub struct SubContext<'a, 'b: 'a> { impl<'a> Context<'a> { fn export(&mut self, name: &str, contents: &str, comments: Option) { - let contents = contents; let contents = contents.trim(); if let Some(ref c) = comments { self.globals.push_str(c); } - let global = if self.config.nodejs { + let global = if self.config.nodejs || self.config.both { if contents.starts_with("class") { format!("{1}\nmodule.exports.{0} = {0};\n", name, contents) } else { @@ -360,7 +359,7 @@ impl<'a> Context<'a> { .unwrap_or("wasm_bindgen"), ) } else { - let import_wasm = if self.config.nodejs { + let import_wasm = if self.config.nodejs || self.config.both { self.footer.push_str(&format!("wasm = require('./{}_bg');", module_name)); format!("var wasm;") @@ -870,7 +869,7 @@ impl<'a> Context<'a> { self.global(&format!(" const TextEncoder = require('util').TextEncoder; ")); - } else if !(self.config.browser || self.config.no_modules) { + } else if !(self.config.browser || self.config.no_modules) || self.config.both { self.global(&format!(" const TextEncoder = typeof self === 'object' && self.TextEncoder ? self.TextEncoder @@ -890,7 +889,7 @@ impl<'a> Context<'a> { self.global(&format!(" const TextDecoder = require('util').TextDecoder; ")); - } else if !(self.config.browser || self.config.no_modules) { + } else if !(self.config.browser || self.config.no_modules) || self.config.both { self.global(&format!(" const TextDecoder = typeof self === 'object' && self.TextDecoder ? self.TextDecoder @@ -1743,7 +1742,7 @@ 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()) { - if self.cx.config.nodejs { + if self.cx.config.nodejs || self.cx.config.both { self.cx.imports.push_str(&format!("\ const {} = require('{}').{};\n\ ", name, module, name)); diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index da74b3d2d18..96b2be39f27 100644 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -26,6 +26,7 @@ pub struct Bindgen { nodejs: bool, browser: bool, no_modules: bool, + both: bool, no_modules_global: Option, debug: bool, typescript: bool, @@ -40,6 +41,7 @@ impl Bindgen { browser: false, no_modules: false, no_modules_global: None, + both: false, debug: false, typescript: false, demangle: true, @@ -66,6 +68,11 @@ impl Bindgen { self } + pub fn both(&mut self, both: bool) -> &mut Bindgen { + self.both = both; + self + } + pub fn no_modules_global(&mut self, name: &str) -> &mut Bindgen { self.no_modules_global = Some(name.to_string()); self @@ -178,13 +185,7 @@ 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) - .and_then(|mut f| f.write_all(shim.as_bytes())) - .with_context(|_| format!("failed to write `{}`", js_path.display()))?; - } + self.write_wasm_import(&module, &wasm_path)?; let wasm_bytes = parity_wasm::serialize(module)?; File::create(&wasm_path) @@ -193,7 +194,56 @@ impl Bindgen { Ok(()) } + fn write_wasm_import(&self, m: &Module, wasm_path: &PathBuf) -> Result<(), Error> { + let shim = if self.nodejs { + self.generate_node_wasm_import(m, &wasm_path) + } else if self.both { + self.generate_both_wasm_import(m, &wasm_path) + } else { + return Ok(()) + }; + let js_path = wasm_path.with_extension("js"); + File::create(&js_path) + .and_then(|mut f| f.write_all(shim.as_bytes())) + .with_context(|_| format!("failed to write `{}`", js_path.display()))?; + Ok(()) + } + fn generate_node_wasm_import(&self, m: &Module, path: &Path) -> String { + let mut shim = self.generate_import_shim(m); + + shim.push_str(&format!(" + const join = require('path').join; + const bytes = require('fs').readFileSync(join(__dirname, '{}')); + const wasmModule = new WebAssembly.Module(bytes); + const wasmInstance = new WebAssembly.Instance(wasmModule, imports); + module.exports = wasmInstance.exports; + ", path.file_name().unwrap().to_str().unwrap() + )); + + reset_indentation(&shim) + } + + fn generate_both_wasm_import(&self, m: &Module, path: &Path) -> String { + reset_indentation(&format!(" + const bytesScript = `const join = require('path').join; + require('fs').readFileSync(join(__dirname, '{path}'));`; + module.exports = resolve(); + function resolve() {{ + if (typeof self === 'object') {{ + return require('./{path}'); + }} else {{ + {import} + const bytes = eval(bytesScript); + const wasmModule = new WebAssembly.Module(bytes); + return new WebAssembly.Instance(wasmModule, imports).exports; + }} + }} + ",path = path.file_name().unwrap().to_str().unwrap(), + import = &self.generate_import_shim(m))) + } + + fn generate_import_shim(&self, m: &Module) -> String { let mut imports = BTreeSet::new(); if let Some(i) = m.import_section() { for i in i.entries() { @@ -206,16 +256,7 @@ impl Bindgen { for module in imports { shim.push_str(&format!("imports['{0}'] = require('{0}');\n", module)); } - - shim.push_str(&format!(" - const join = require('path').join; - const bytes = require('fs').readFileSync(join(__dirname, '{}')); - const wasmModule = new WebAssembly.Module(bytes); - const wasmInstance = new WebAssembly.Instance(wasmModule, imports); - module.exports = wasmInstance.exports; - ", path.file_name().unwrap().to_str().unwrap())); - - reset_indentation(&shim) + shim } } diff --git a/crates/cli/src/bin/wasm-bindgen.rs b/crates/cli/src/bin/wasm-bindgen.rs index 3dd4bfd4857..3727d6615cc 100644 --- a/crates/cli/src/bin/wasm-bindgen.rs +++ b/crates/cli/src/bin/wasm-bindgen.rs @@ -28,6 +28,7 @@ Options: --browser Generate output that only works in a browser --no-modules Generate output that only works in a browser (without modules) --no-modules-global VAR Name of the global variable to initialize + --both Generate output that will work with both node.js and webpack --typescript Output a TypeScript definition file (on by default) --no-typescript Don't emit a *.d.ts file --debug Include otherwise-extraneous debug checks in output @@ -40,6 +41,7 @@ struct Args { flag_nodejs: bool, flag_browser: bool, flag_no_modules: bool, + flag_both: bool, flag_typescript: bool, flag_no_typescript: bool, flag_out_dir: Option, @@ -83,6 +85,7 @@ fn rmain(args: &Args) -> Result<(), Error> { .nodejs(args.flag_nodejs) .browser(args.flag_browser) .no_modules(args.flag_no_modules) + .both(args.flag_both) .debug(args.flag_debug) .demangle(!args.flag_no_demangle) .typescript(typescript); diff --git a/examples/node_and_browser/Cargo.toml b/examples/node_and_browser/Cargo.toml new file mode 100644 index 00000000000..b83421c5813 --- /dev/null +++ b/examples/node_and_browser/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "node-and-browser" +version = "0.1.0" +authors = ["Robert Masen "] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +# Here we're using a path dependency to use what's already in this repository, +# but you'd use the commented out version below if you're copying this into your +# project. +wasm-bindgen = { path = "../.." } +#wasm-bindgen = "0.2" +[dependencies.pulldown-cmark] +version = "0.1.2" +default-features = false \ No newline at end of file diff --git a/examples/node_and_browser/README.md b/examples/node_and_browser/README.md new file mode 100644 index 00000000000..65836407462 --- /dev/null +++ b/examples/node_and_browser/README.md @@ -0,0 +1,23 @@ +# Node and Browser + + +This directory is an example of using the `#[wasm_bindgen]` macro to create an +entry point that's callable from both the browser and Node.js. + +You can build the example locally with: + +``` +$ ./build.sh +``` +This will build the project and spin up `webpack-dev-server` which makes it viewable at http://localhost:8080 +(or a higher port if 8080 is already bound). + +To see this project used in Node.js you can exit the instance of `webpack-dev-server` and run + +``` +$ npm start +``` + +This will read in the `wasm-bindgen` (README.md)[../../README.md] file and convert it to HTML +you can find the results in `./out.html` once it is finished. + diff --git a/examples/node_and_browser/build.sh b/examples/node_and_browser/build.sh new file mode 100755 index 00000000000..db7944b9e3d --- /dev/null +++ b/examples/node_and_browser/build.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +set -ex + +# Build the `hello_world.wasm` file using Cargo/rustc +cargo +nightly build --target wasm32-unknown-unknown + +# Run the `wasm-bindgen` CLI tool to postprocess the wasm file emitted by the +# Rust compiler to emit the JS support glue that's necessary +# +# Here we're using the version of the CLI in this repository, but for external +# usage you'd use the commented out version below +cargo +nightly run --manifest-path ../../crates/cli/Cargo.toml \ + --bin wasm-bindgen -- \ + ../../target/wasm32-unknown-unknown/debug/node_and_browser.wasm --out-dir . --both +# wasm-bindgen ../../target/wasm32-unknown-unknown/hello_world.wasm --out-dir . + +# Finally, package everything up using Webpack and start a server so we can +# browse the result +npm install +npm run serve diff --git a/examples/node_and_browser/index.html b/examples/node_and_browser/index.html new file mode 100644 index 00000000000..f9dbf4e2f5f --- /dev/null +++ b/examples/node_and_browser/index.html @@ -0,0 +1,54 @@ + + + + + + +
+
+ +
+
+
+
+
+ + + \ No newline at end of file diff --git a/examples/node_and_browser/index.js b/examples/node_and_browser/index.js new file mode 100644 index 00000000000..edb62cd1c8a --- /dev/null +++ b/examples/node_and_browser/index.js @@ -0,0 +1,48 @@ +let wasm; +import('./node_and_browser.js').then(w => { + console.log('wasm_cmark_parse resolved'); + wasm = w; + ready(); +}); + +function parseMD(md) { + console.log('parseMD'); + return wasm.parse_markdown(md); +} + +let debounce; +function ready() { + console.log('ready'); + renderMD(); + let input = getInput(); + if (!input) throw new Error('Unable to find MD input'); + input.addEventListener('keyup', () => { + if (!debounce) { + debounce = setTimeout(renderMD, 2000); + } else { + clearTimeout(debounce); + debounce = setTimeout(renderMD, 2000); + } + }); +} +function renderMD() { + console.log('renderMD'); + debounce = null; + let input = getInput(); + if (!input) throw new Error('Unable to find MD input'); + let md = input.value; + let html = parseMD(md); + let target = getHTML(); + if (!target) throw new Error('Unable to find div to render HTML'); + target.innerHTML = html; +} + +function getInput() { + console.log('getInput'); + return document.getElementById('md-input-box'); +} + +function getHTML() { + console.log('getHTML'); + return document.getElementById('rendered'); +} \ No newline at end of file diff --git a/examples/node_and_browser/main.js b/examples/node_and_browser/main.js new file mode 100644 index 00000000000..0e9c307a587 --- /dev/null +++ b/examples/node_and_browser/main.js @@ -0,0 +1,49 @@ +const wasm = require('./node_and_browser.js'); +const fs = require('fs'); +async function main() { + if (process.argv.length != 4) { + return printHelp(); + } + let md = await readMarkdown(process.argv[2]); + let html = parseMarkdown(md); + await writeHTML(html, process.argv[3]); +} + +function printHelp() { + console.log(`\ +USAGE: +node ./main.js + +ARGUMENTS: +inpath this is the path to your + input markdown file +outpath this is the path where you + would like the HTML file to be written`); +} + +function readMarkdown(path) { + return new Promise((resolve, reject) => { + fs.readFile(path, (err, content) => { + if (err) return reject(err); + if (typeof content !== 'string') { + content = content.toString(); + } + resolve(content); + }) + }); +} + +function parseMarkdown(md) { + return wasm.parse_markdown(md); +} + +function writeHTML(html, path) { + return new Promise((resolve, reject) => { + fs.writeFile(path, html, err => { + if (err) return reject(err); + resolve(); + }); + }); +} + +main(); \ No newline at end of file diff --git a/examples/node_and_browser/package.json b/examples/node_and_browser/package.json new file mode 100644 index 00000000000..84d383d20e7 --- /dev/null +++ b/examples/node_and_browser/package.json @@ -0,0 +1,11 @@ +{ + "scripts": { + "serve": "webpack-dev-server", + "start": "node ./main.js ../../README.md ./out.html" + }, + "devDependencies": { + "webpack": "^4.11.1", + "webpack-cli": "^2.0.10", + "webpack-dev-server": "^3.1.0" + } +} diff --git a/examples/node_and_browser/src/lib.rs b/examples/node_and_browser/src/lib.rs new file mode 100644 index 00000000000..1906fddc200 --- /dev/null +++ b/examples/node_and_browser/src/lib.rs @@ -0,0 +1,18 @@ +#![feature(proc_macro, wasm_custom_section, wasm_import_module)] +extern crate pulldown_cmark; +extern crate wasm_bindgen; +use pulldown_cmark::{Parser, Options, html::push_html}; + +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +/// This function will take a string formatted +/// using the CommonMark syntax and returns a +/// HTML string to match the input +pub fn parse_markdown(md: &str) -> String { + let opts = Options::all(); + let p = Parser::new_ext(md, opts); + let mut ret = String::new(); + push_html(&mut ret, p); + ret +} \ No newline at end of file diff --git a/examples/node_and_browser/webpack.config.js b/examples/node_and_browser/webpack.config.js new file mode 100644 index 00000000000..eda09d47689 --- /dev/null +++ b/examples/node_and_browser/webpack.config.js @@ -0,0 +1,14 @@ +const path = require('path'); + +module.exports = { + entry: './index.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'index.js', + }, + resolve: { + extensions: ['.js', '.wasm'], + }, + mode: 'development', + devtool: 'source-map' +}; \ No newline at end of file