Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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
'';
};

Expand Down
5 changes: 2 additions & 3 deletions packages/orderbook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
30 changes: 13 additions & 17 deletions packages/orderbook/scripts/build.js
Original file line number Diff line number Diff line change
@@ -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);
}
Comment thread
rouzwelt marked this conversation as resolved.

// rm temp folder
execSync("npm run rm-temp");

// check bindings for possible errors
execSync("npm run check");
183 changes: 110 additions & 73 deletions packages/orderbook/scripts/buildPackage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = { 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')
})
);
Comment thread
rouzwelt marked this conversation as resolved.

// 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);
Comment thread
rouzwelt marked this conversation as resolved.
}

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);`
);
);
Comment thread
rouzwelt marked this conversation as resolved.
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);
11 changes: 11 additions & 0 deletions packages/orderbook/scripts/sizeCheck.js
Original file line number Diff line number Diff line change
@@ -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!")
}
Comment thread
rouzwelt marked this conversation as resolved.
}
Comment on lines +1 to +11

@coderabbitai coderabbitai Bot Oct 25, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Add error handling for missing package argument and file not found.

The script will throw an unhelpful error if the pkg argument is missing or if the WASM file doesn't exist. Consider adding explicit validation.

Apply these improvements:

 const fs = require('fs');
 
 const SIZE_LIMIT = 8_388_608; // 8 MB binary
 
 const [pkg] = process.argv.slice(2);
+
+if (!pkg) {
+    throw new Error("Package name argument is required");
+}
 
 // we only need to check size on web/esm
+const wasmPath = `./temp/web/${pkg}/${pkg}_bg.wasm`;
+if (!fs.existsSync(wasmPath)) {
+    throw new Error(`WASM file not found at ${wasmPath}`);
+}
-const wasmEsmBytes = fs.readFileSync(`./temp/web/${pkg}/${pkg}_bg.wasm`);
+const wasmEsmBytes = fs.readFileSync(wasmPath);
 if (wasmEsmBytes.length > SIZE_LIMIT) {
     throw new Error("ESM wasm size exceeds 8 MB limit!")
 }
🤖 Prompt for AI Agents
In packages/orderbook/scripts/sizeCheck.js around lines 1 to 11, the script
assumes a package argument and that the WASM file exists; add explicit
validation to (1) check that process.argv provides a pkg and throw a clear error
if missing, and (2) wrap the fs.readFileSync call in a try/catch or use
fs.existsSync to detect a missing file and throw a descriptive error mentioning
the expected path; ensure any thrown errors include the pkg value and expected
file path for easier debugging.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rouzwelt, understood. I've noted your preference for these build scripts.


✏️ Learnings added
Learnt from: rouzwelt
PR: rainlanguage/rain.orderbook#2246
File: packages/orderbook/scripts/sizeCheck.js:1-11
Timestamp: 2025-10-25T21:11:09.656Z
Learning: In the rain.orderbook repository, build scripts like sizeCheck.js intentionally lack defensive error handling (e.g., checking for missing arguments or file existence). The scripts should fail with raw errors to surface configuration or build pipeline issues that require immediate attention.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: rouzwelt
PR: rainlanguage/rain.orderbook#2246
File: packages/orderbook/scripts/buildEsm.js:38-53
Timestamp: 2025-10-25T21:07:19.666Z
Learning: In the rain.orderbook repository, the deduplication logic in buildEsm.js (and similar build scripts) intentionally lacks defensive checks for indexOf returning -1. If duplicates are not found, the script should throw to surface breaking changes in wasm-bindgen output that require attention.

4 changes: 2 additions & 2 deletions packages/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions tauri-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down