├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── package.json ├── passes ├── wasmopt.js └── wasmsnip.js └── presets ├── debug.js ├── production.js └── profiling.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | package-lock.json 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sven Sauleau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-plugin 2 | 3 | > Rust plugin for Webpack 4 | 5 | ## Installation 6 | 7 | ```sh 8 | yarn add rust-plugin 9 | ``` 10 | 11 | ## Options 12 | 13 | | name | description | 14 | |--------|-------------| 15 | | `debug` | Enable debug mode | 16 | | `profiling` | Enable profiling mode | 17 | | `wasmopt.level` | configure `wasm-opt` optimization level (default `z`) | 18 | | `wasmopt.required` | throw if the binary is not available (default `false`: warn) | 19 | | `wasmsnip.snipRustPanickingCode` | Snip Rust's `std::panicking` and `core::panicking` code | 20 | | `wasmsnip.snipRustFmtCode` | Snip Rust's `std::fmt` and `core::fmt` code | 21 | | `wasmsnip.functions` | Snip any function that matches the given regular expression or a string | 22 | 23 | ## Example 24 | 25 | ```js 26 | const RustPlugin = require("rust-plugin"); 27 | 28 | module.exports = { 29 | entry: "./index.js", 30 | plugins: [ 31 | new RustPlugin({ 32 | debug: true, 33 | profiling: false, 34 | wasmopt: { 35 | level: '0' 36 | } 37 | }) 38 | ] 39 | }; 40 | 41 | ``` 42 | 43 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const {extname} = require("path"); 2 | const tempy = require('tempy'); 3 | const {writeFileSync, readFileSync, unlinkSync} = require('fs'); 4 | 5 | const getPreset = n => require(`./presets/${n}.js`); 6 | 7 | const isWasm = n => extname(n) === ".wasm"; 8 | 9 | const defaultOpts = { 10 | debug: false, 11 | 12 | wasmopt: {}, 13 | wasmsnip: false 14 | }; 15 | 16 | function createRunner(compilation, options, bin /*: Buffer */) { 17 | const filename = tempy.file({extension: 'wasm'}); 18 | 19 | writeFileSync(filename, new Buffer(bin)); 20 | 21 | return { 22 | filename, 23 | options, 24 | 25 | debug(...msg /*: Array */) { 26 | if (options.debug === true) { 27 | console.log("debug", ...msg); 28 | } 29 | }, 30 | 31 | warn(msg /*: string */) { 32 | // warnings are not showned by Webpack is production, so we log directly instead 33 | if (options.env === "production") { 34 | console.warn(msg); 35 | } else { 36 | compilation.warnings.push(new Error(msg)); 37 | } 38 | }, 39 | 40 | runPasses(passFn /*: Array */)/*: Promise */ { 41 | const promises = passFn.reduce((acc, fn) => { 42 | acc.push(fn(this)); 43 | return acc; 44 | }, []); 45 | 46 | return Promise.all(promises); 47 | }, 48 | 49 | get() /*: Buffer */ { 50 | const buff = readFileSync(filename, null); 51 | unlinkSync(filename); 52 | 53 | return buff; 54 | } 55 | } 56 | } 57 | 58 | module.exports = class { 59 | constructor(options = {}) { 60 | this._options = Object.assign({}, defaultOpts, options); 61 | } 62 | 63 | _configure(webpackMode, warn) { 64 | let env = "production"; 65 | 66 | if (webpackMode === "development") { 67 | env = "debug"; 68 | } 69 | 70 | if (env === "production" && this._options.profiling === true) { 71 | warn("You are profiling a production build, this might not be intended."); 72 | } 73 | 74 | if (this._options.profiling === true) { 75 | env = "profiling"; 76 | } 77 | 78 | const {configureOptions, configurePasses} = getPreset(env); 79 | 80 | this._options.env = env; 81 | 82 | this._options = configureOptions(this._options); 83 | this._passes = configurePasses(this._options); 84 | } 85 | 86 | apply(compiler) { 87 | compiler.plugin("emit", (compilation, ok) => { 88 | 89 | this._configure( 90 | compiler.options.mode, 91 | msg => compilation.warnings.push(new Error(msg)) 92 | ); 93 | 94 | const processes = []; 95 | 96 | for (const name in compilation.assets) { 97 | if (isWasm(name) === false) { 98 | continue; 99 | } 100 | 101 | const cachedSource = compilation.assets[name]; 102 | 103 | const runner = createRunner( 104 | compilation, 105 | this._options, 106 | cachedSource.source() 107 | ); 108 | 109 | const p = runner.runPasses(this._passes) 110 | .then(runner.get) 111 | .then(newBin => { 112 | 113 | // Emit the new binary 114 | compilation.assets[name] = { 115 | source: () => newBin, 116 | size: () => newBin.byteLength 117 | }; 118 | }) 119 | .catch(err => { 120 | compilation.errors.push(err); 121 | }); 122 | 123 | processes.push(p); 124 | } 125 | 126 | Promise.all(processes).then(() => ok()).catch(() => ok()); 127 | }); 128 | } 129 | }; 130 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rust-plugin", 3 | "version": "0.0.5", 4 | "description": "Rust plugin for Webpack", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/wasm-tool/rust-plugin.git" 12 | }, 13 | "author": "Sven Sauleau", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/wasm-tool/rust-plugin/issues" 17 | }, 18 | "homepage": "https://github.com/wasm-tool/rust-plugin#readme", 19 | "dependencies": { 20 | "tempy": "^0.2.1", 21 | "which": "^1.3.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /passes/wasmopt.js: -------------------------------------------------------------------------------- 1 | const {exec} = require("child_process"); 2 | const which = require("which"); 3 | 4 | const cwd = process.cwd(); 5 | 6 | const binaryNotFound = ( 7 | "Could not find a suitable `wasm-opt` on $PATH. Install" 8 | + " `wasm-opt` from the `binaryen` suite to produce smaller" 9 | + " and faster `.wasm` binaries!" 10 | + "\n\n" 11 | + "See https://github.com/WebAssembly/binaryen" 12 | ); 13 | 14 | function wasmopt({options, filename, debug, warn}) { 15 | const { 16 | required = false, 17 | level = "z" 18 | } = options.wasmopt; 19 | 20 | return new Promise((resolve, reject) => { 21 | 22 | const bin = which.sync("wasm-opt", {nothrow: true}); 23 | 24 | if (bin === null) { 25 | if (required === true) { 26 | return reject(binaryNotFound); 27 | } else { 28 | return warn(binaryNotFound); 29 | } 30 | } 31 | 32 | const command = [ 33 | bin, 34 | "-O" + level, 35 | "-o", filename, 36 | filename 37 | ].join(" "); 38 | 39 | debug("command", command); 40 | 41 | exec(command, (error, stdout, stderr) => { 42 | if (error) { 43 | return reject(error); 44 | } 45 | 46 | debug(stdout); 47 | debug(stderr); 48 | 49 | resolve(); 50 | }); 51 | }); 52 | } 53 | 54 | module.exports = { wasmopt }; 55 | -------------------------------------------------------------------------------- /passes/wasmsnip.js: -------------------------------------------------------------------------------- 1 | const {exec} = require("child_process"); 2 | const which = require("which"); 3 | 4 | const cwd = process.cwd(); 5 | 6 | const binaryNotFound = ( 7 | "Could not find a suitable `wasm-snip` on $PATH. Install" 8 | + " `wasm-snip` from https://github.com/rustwasm/wasm-snip#executable." 9 | ); 10 | 11 | function wasmsnip({options, filename, debug, warn}) { 12 | 13 | // Plugin is not configured, ignore 14 | if (options.wasmsnip === false) { 15 | debug("wasmsnip is not configured; skipping"); 16 | 17 | return Promise.resolve(); 18 | } 19 | 20 | const { 21 | snipRustPanickingCode = false, 22 | snipRustFmtCode = false, 23 | functions = [] 24 | } = options.wasmsnip; 25 | 26 | return new Promise((resolve, reject) => { 27 | const bin = which.sync("wasm-snip", {nothrow: true}); 28 | 29 | if (bin === null) { 30 | return reject(binaryNotFound); 31 | } 32 | 33 | const splitedCommand = [ 34 | bin, 35 | filename, 36 | "-o", filename 37 | ]; 38 | 39 | if (snipRustPanickingCode === true) { 40 | splitedCommand.push("--snip-rust-panicking-code"); 41 | } 42 | 43 | if (snipRustFmtCode === true) { 44 | splitedCommand.push("--snip-rust-fmt-code"); 45 | } 46 | 47 | functions.forEach(fn => { 48 | if (fn instanceof RegExp === true) { 49 | let pattern = fn.toString(); 50 | 51 | // remove fist / and end / 52 | pattern = pattern.slice(1).slice(0, -1); 53 | 54 | splitedCommand.push(`--pattern "${pattern}"`); 55 | } 56 | }); 57 | 58 | functions.forEach(fn => { 59 | if (typeof fn === "string") { 60 | splitedCommand.push("--"); 61 | splitedCommand.push('"' + fn + '"'); 62 | } 63 | }); 64 | 65 | const command = splitedCommand.join(" ") 66 | 67 | debug("command", command); 68 | 69 | exec(command, (error, stdout, stderr) => { 70 | if (error) { 71 | return reject(error); 72 | } 73 | 74 | debug(stdout); 75 | debug(stderr); 76 | 77 | resolve(); 78 | }); 79 | }); 80 | } 81 | 82 | module.exports = { wasmsnip }; 83 | -------------------------------------------------------------------------------- /presets/debug.js: -------------------------------------------------------------------------------- 1 | const {wasmopt} = require("../passes/wasmopt.js"); 2 | const {wasmsnip} = require("../passes/wasmsnip.js"); 3 | 4 | function configureOptions(options = {}) { 5 | return options; 6 | } 7 | 8 | function configurePasses(options = {}) { 9 | return [ 10 | wasmsnip 11 | ]; 12 | } 13 | 14 | module.exports = { configureOptions, configurePasses }; 15 | -------------------------------------------------------------------------------- /presets/production.js: -------------------------------------------------------------------------------- 1 | const {wasmopt} = require("../passes/wasmopt.js"); 2 | const {wasmsnip} = require("../passes/wasmsnip.js"); 3 | 4 | function configureOptions(options = {}) { 5 | options.wasmopt.required = true; 6 | 7 | return options; 8 | } 9 | 10 | function configurePasses(options = {}) { 11 | return [ 12 | wasmopt, 13 | wasmsnip 14 | ]; 15 | } 16 | 17 | module.exports = { configureOptions, configurePasses }; 18 | -------------------------------------------------------------------------------- /presets/profiling.js: -------------------------------------------------------------------------------- 1 | function configureOptions(options = {}) { 2 | return options; 3 | } 4 | 5 | function configurePasses(options = {}) { 6 | return []; 7 | } 8 | 9 | module.exports = { configureOptions, configurePasses }; 10 | --------------------------------------------------------------------------------