diff --git a/Cargo.toml b/Cargo.toml index c3396f8c61..01616c3c1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,3 +90,18 @@ path = "crates/math" [workspace.dependencies.rain_orderbook_js_api] path = "crates/js_api" + +# release profile for wasm build optimized for size reduction +[profile.release-wasm] +inherits = "release" +opt-level = "z" +lto = true +strip = true +panic = "abort" +codegen-units = 1 +rpath = false +debug = false +incremental = false +overflow-checks = false +debug-assertions = false +split-debuginfo = "packed" diff --git a/flake.nix b/flake.nix index ef1fa50678..79cff473b4 100644 --- a/flake.nix +++ b/flake.nix @@ -242,7 +242,7 @@ body = '' set -euxo pipefail - cargo build -r --target wasm32-unknown-unknown --lib --workspace --exclude rain_orderbook_cli --exclude rain_orderbook_integration_tests + cargo build --profile release-wasm --target wasm32-unknown-unknown --lib -p rain_orderbook_js_api ''; }; diff --git a/packages/orderbook/package.json b/packages/orderbook/package.json index b013d9f359..82aafa504f 100644 --- a/packages/orderbook/package.json +++ b/packages/orderbook/package.json @@ -31,9 +31,8 @@ ], "scripts": { "prepublish": "node ./scripts/setup", - "build": "npm run rm-dist && npm run rm-temp && node ./scripts/build", - "build-tauri": "npm run rm-dist && npm run rm-temp && node ./scripts/build tauri", - "build-wasm": "cargo build --target wasm32-unknown-unknown --lib -r --workspace --exclude rain_orderbook_cli --exclude rain_orderbook_integration_tests", + "build": "npm run rm-dist && npm run rm-temp && npm run build-wasm && node ./scripts/build && npm run check", + "build-wasm": "cargo build --target wasm32-unknown-unknown --lib --profile release-wasm -p rain_orderbook_js_api", "rm-dist": "rimraf ./dist", "rm-temp": "rimraf ./temp", "test": "npm run check && vitest run --dir test", diff --git a/packages/orderbook/scripts/build.js b/packages/orderbook/scripts/build.js index b980583a6d..1b3a252d67 100644 --- a/packages/orderbook/scripts/build.js +++ b/packages/orderbook/scripts/build.js @@ -1,34 +1,30 @@ const fs = require("fs"); +const { sizeCheck } = require("./sizeCheck"); const { execSync } = require("child_process"); - -const [isTauriBuild = false] = process.argv.slice(2); +const { buildCjs, buildEsm } = require("./buildPackage"); // create root esm.js and cjs.js files with their .d.ts fs.writeFileSync( "./cjs.js", '"use strict";\n\nmodule.exports = require("./dist/cjs/index");\n' ); -fs.writeFileSync("./cjs.d.ts", 'export * from "./dist/types/index";\n'); +fs.writeFileSync("./cjs.d.ts", 'export * from "./dist/cjs/index";\n'); fs.writeFileSync("./esm.js", 'export * from "./dist/esm/index";\n'); -fs.writeFileSync("./esm.d.ts", 'export * from "./dist/types/index";\n'); - -// create dist dir -fs.mkdirSync("./dist/cjs", { recursive: true }); -fs.mkdirSync("./dist/esm", { recursive: true }); - -// build for wasm32 target -execSync("npm run build-wasm"); +fs.writeFileSync("./esm.d.ts", 'export * from "./dist/esm/index";\n'); // build specified packages and include them in final index file // list of packages to build can be extended by adding new package // names to the list below -const packages = ["js_api"]; -for (const package of packages) { - execSync(`node ./scripts/buildPackage ${package} ${isTauriBuild ? 'true' : ''}`); +const pkgs = ["js_api"]; + +for (const pkg of pkgs) { + // build for cjs and esm + buildCjs(pkg); + buildEsm(pkg); + + // check wasm size + sizeCheck(pkg); } // rm temp folder execSync("npm run rm-temp"); - -// check bindings for possible errors -execSync("npm run check"); diff --git a/packages/orderbook/scripts/buildPackage.js b/packages/orderbook/scripts/buildPackage.js index 74a539128e..030c3f1f5a 100644 --- a/packages/orderbook/scripts/buildPackage.js +++ b/packages/orderbook/scripts/buildPackage.js @@ -2,88 +2,125 @@ const fs = require('fs'); const { execSync } = require('child_process'); const packagePrefix = 'rain_orderbook_'; -const [package, isTauriBuild = false] = process.argv.slice(2); -// generate node/web bindgens -execSync( - `wasm-bindgen --target nodejs ../../target/wasm32-unknown-unknown/release/${ - packagePrefix + package - }.wasm --out-dir ./temp/node/${package} --out-name ${package}` -); -execSync( - `wasm-bindgen --target web ../../target/wasm32-unknown-unknown/release/${ - packagePrefix + package - }.wasm --out-dir ./temp/web/${package} --out-name ${package}` -); +// after using opt-level on wasm build, WasmEncodedResult and WasmEncodedError +// are duplicated in the dts so we need to dedupe them +const dups = [ + "\nexport type WasmEncodedResult = { value: T; error: undefined } | { value: undefined; error: WasmEncodedError };\n", + `\nexport interface WasmEncodedError { + msg: string; + readableMsg: string; +}\n` +]; -// encode wasm as base64 into a json for cjs and esm that can be natively imported -// in js modules in order to avoid using fetch or fs operations -const wasmCjsBytes = fs.readFileSync(`./temp/node/${package}/${package}_bg.wasm`); -fs.writeFileSync( - `./dist/cjs/orderbook_wbg.json`, - JSON.stringify({ - wasm: Buffer.from(wasmCjsBytes, 'binary').toString('base64') - }) -); -const wasmEsmBytes = fs.readFileSync(`./temp/web/${package}/${package}_bg.wasm`); -fs.writeFileSync( - `./dist/esm/orderbook_wbg.json`, - JSON.stringify({ - wasm: Buffer.from(wasmEsmBytes, 'binary').toString('base64') - }) -); +module.exports.buildCjs = function (pkg) { + // generate node bindgens for cjs output + fs.mkdirSync(`./dist/cjs`, { recursive: true }); + execSync( + `wasm-bindgen --target nodejs ../../target/wasm32-unknown-unknown/release-wasm/${ + packagePrefix + pkg + }.wasm --out-dir ./temp/node/${pkg} --out-name ${pkg}` + ); -// prepare the dts -let dts = fs.readFileSync(`./temp/node/${package}/${package}.d.ts`, { - encoding: 'utf-8' -}); -dts = dts.replace( - `/* tslint:disable */ + // encode wasm as base64 into a json for cjs that can be natively imported + // in js modules in order to avoid using fetch or fs operations + const wasmCjsBytes = fs.readFileSync(`./temp/node/${pkg}/${pkg}_bg.wasm`); + fs.writeFileSync( + "./dist/cjs/orderbook_wbg.json", + JSON.stringify({ + wasm: Buffer.from(wasmCjsBytes, 'binary').toString('base64') + }) + ); + + // prepare the dts + let dts = fs.readFileSync(`./temp/node/${pkg}/${pkg}.d.ts`, { + encoding: 'utf-8' + }); + dts = dts.replace( + `/* tslint:disable */ /* eslint-disable */`, - '' -); -dts = '/* this file is auto-generated, do not modify */\n' + dts; -fs.writeFileSync(`./dist/cjs/index.d.ts`, dts); -fs.writeFileSync(`./dist/esm/index.d.ts`, dts); + '' + ); + dts = '/* this file is auto-generated, do not modify */\n' + dts; + for (const dup of dups) { + const index = dts.indexOf(dup); + const lastIndex = dts.lastIndexOf(dup); + if (index === -1 || lastIndex === index) continue; + dts = dts.replaceAll(dup, ""); + const start = dts.slice(0, index); + const end = dts.slice(index); + dts = start + dup + end; + } + fs.writeFileSync(`./dist/cjs/index.d.ts`, dts); -// prepare cjs -let cjs = fs.readFileSync(`./temp/node/${package}/${package}.js`, { - encoding: 'utf-8' -}); -cjs = cjs.replace( - `const path = require('path').join(__dirname, '${package}_bg.wasm'); + // prepare cjs + let cjs = fs.readFileSync(`./temp/node/${pkg}/${pkg}.js`, { + encoding: 'utf-8' + }); + cjs = cjs.replace( + `const path = require('path').join(__dirname, '${pkg}_bg.wasm'); const bytes = require('fs').readFileSync(path);`, - ` + ` const { Buffer } = require('buffer'); -const wasmB64 = require('../cjs/orderbook_wbg.json'); +const wasmB64 = require('./orderbook_wbg.json'); const bytes = Buffer.from(wasmB64.wasm, 'base64');` -); -cjs = cjs.replace('const { TextEncoder, TextDecoder } = require(`util`);', ''); -cjs = '/* this file is auto-generated, do not modify */\n' + cjs; -fs.writeFileSync(`./dist/cjs/index.js`, cjs); + ); + cjs = cjs.replace('const { TextEncoder, TextDecoder } = require(`util`);', ''); + cjs = '/* this file is auto-generated, do not modify */\n' + cjs; + fs.writeFileSync(`./dist/cjs/index.js`, cjs); +} + +module.exports.buildEsm = function (pkg) { + // generate web bindgens for esm output + fs.mkdirSync(`./dist/esm`, { recursive: true }); + execSync( + `wasm-bindgen --target web ../../target/wasm32-unknown-unknown/release-wasm/${ + packagePrefix + pkg + }.wasm --out-dir ./temp/web/${pkg} --out-name ${pkg}` + ); + + // encode wasm as base64 into a json for esm that can be natively imported + // in js modules in order to avoid using fetch or fs operations + const wasmEsmBytes = fs.readFileSync(`./temp/web/${pkg}/${pkg}_bg.wasm`); + fs.writeFileSync( + `./dist/esm/orderbook_wbg.json`, + JSON.stringify({ + wasm: Buffer.from(wasmEsmBytes, 'binary').toString('base64') + }) + ); -// prepare esm -let esm = fs.readFileSync(`./temp/web/${package}/${package}.js`, { - encoding: 'utf-8' -}); -if (isTauriBuild) { - esm = esm.replace( - `export { initSync }; + // prepare the dts + let dts = fs.readFileSync(`./temp/web/${pkg}/${pkg}.d.ts`, { + encoding: 'utf-8' + }); + dts = dts.replace( + `/* tslint:disable */ +/* eslint-disable */`, + '' + ); + dts = '/* this file is auto-generated, do not modify */\n' + dts; + for (const dup of dups) { + const index = dts.indexOf(dup); + const lastIndex = dts.lastIndexOf(dup); + if (index === -1 || index === lastIndex) continue; + dts = dts.replaceAll(dup, ""); + const start = dts.slice(0, index); + const end = dts.slice(index); + dts = start + dup + end; + } + fs.writeFileSync(`./dist/esm/index.d.ts`, dts); + + // prepare esm .js + let esm = fs.readFileSync(`./temp/web/${pkg}/${pkg}.js`, { + encoding: 'utf-8' + }); + esm = esm.replace(`export { initSync }; export default __wbg_init;`, - `import { Buffer } from 'buffer'; -import wasmB64 from '../esm/orderbook_wbg.json'; -const bytes = Buffer.from(wasmB64.wasm, 'base64'); + `import { Buffer } from 'buffer'; +import wasmB64 from './orderbook_wbg.json'; +const bytes = Buffer.from(wasmB64.wasm, 'base64');\n initSync(bytes);` -); -} else { - esm = esm.replace( - `export { initSync }; -export default __wbg_init;`, - `import { Buffer } from 'buffer'; -import wasmB64 from '../esm/orderbook_wbg.json'; -const bytes = Buffer.from(wasmB64.wasm, 'base64'); -await __wbg_init(bytes);` -); + ); + esm = '/* this file is auto-generated, do not modify */\n' + esm; + fs.writeFileSync(`./dist/esm/index.js`, esm); } -esm = '/* this file is auto-generated, do not modify */\n' + esm; -fs.writeFileSync(`./dist/esm/index.js`, esm); diff --git a/packages/orderbook/scripts/sizeCheck.js b/packages/orderbook/scripts/sizeCheck.js new file mode 100644 index 0000000000..ecad981051 --- /dev/null +++ b/packages/orderbook/scripts/sizeCheck.js @@ -0,0 +1,11 @@ +const fs = require('fs'); + +const SIZE_LIMIT = 8_388_608; // 8 MB binary + +module.exports.sizeCheck = function (pkg) { + // we only need to check size on web/esm + const wasmEsmBytes = fs.readFileSync(`./temp/web/${pkg}/${pkg}_bg.wasm`); + if (wasmEsmBytes.length > SIZE_LIMIT) { + throw new Error("ESM wasm size exceeds 8 MB limit!") + } +} diff --git a/packages/webapp/package.json b/packages/webapp/package.json index 6ccfd3676c..fcae9426d5 100644 --- a/packages/webapp/package.json +++ b/packages/webapp/package.json @@ -6,8 +6,8 @@ "author": "Rain Open Source Software Ltd", "private": true, "scripts": { - "dev": "npm run build -w @rainlanguage/orderbook && vite dev", - "build": "npm run build -w @rainlanguage/orderbook && vite build", + "dev": "vite dev", + "build": "vite build", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", diff --git a/tauri-app/package.json b/tauri-app/package.json index ba9aa31d53..57bf4d94be 100644 --- a/tauri-app/package.json +++ b/tauri-app/package.json @@ -5,8 +5,8 @@ "license": "LicenseRef-DCL-1.0", "author": "Rain Open Source Software Ltd", "scripts": { - "dev": "npm run build-tauri -w @rainlanguage/orderbook && vite dev", - "build": "npm run build-tauri -w @rainlanguage/orderbook && npm run build-bindings && vite build", + "dev": "vite dev", + "build": "npm run build-bindings && vite build", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",