Runtime Environments & Deployment
WaSHI runs across JavaScript and TypeScript runtimes—from Node.js, Bun, and Deno to modern bundlers, vanilla browser <script> tags, and air-gapped intranets—without native C/C++ compilation.
🟢 Node.js
WaSHI supports Node.js (v18+) out of the box with standard ECMAScript Modules (ESM). The built-in HiGHS WebAssembly binary is loaded directly from the installed package.
import { Model, expr, maximize } from "@ohtaman/washi";
const m = new Model("NodeExample");
const x = m.continuous("x", { lb: 0, ub: 10 });
const y = m.continuous("y", { lb: 0, ub: 20 });
m.objective(maximize(expr`2 * ${x} + 3 * ${y}`));
m.constraint(expr`${x} + ${y} <= 15`, "capacity");
await m.solve();
console.log(`Status: ${m.status}, Objective: ${m.objectiveValue}`);
console.log(`x = ${x.value}, y = ${y.value}`);Run directly with tsx or node:
npx tsx app.ts🌐 Bundlers (Vite, Webpack, Rollup, Next.js)
Modern Bundlers (Vite / Rollup / Webpack)
When importing @ohtaman/washi in a browser bundler, the browser export condition resolves automatically:
import { Model, expr, maximize } from "@ohtaman/washi";
export async function runOptimization() {
const m = new Model({ solver: "highs" });
const x = m.continuous("x", { lb: 0, ub: 100 });
m.objective(maximize(x));
await m.solve();
return x.value;
}Background Web Worker Execution (Default)
In browsers, HiGHS automatically runs inside a dedicated Web Worker so long-running LP/MIP/QP solves never freeze the main UI thread, animations, or user input, while enabling real-time progress events (onProgress) and cancellation (AbortSignal).
If your environment prohibits Web Workers or you explicitly want synchronous main-thread execution, pass useWorker: false:
await m.solve({ useWorker: false });Customizing the HiGHS WASM Asset URL
By default, WaSHI resolves highs.wasm relative to the bundle (new URL("./highs.wasm", import.meta.url)). It never makes implicit requests to external CDNs (jsdelivr, unpkg, etc.).
If your bundler or static server hosts .wasm files under a custom path, configure the URL globally or per solve:
import { setHighsWasmUrl } from "@ohtaman/washi";
// Option A: Configure globally before solving
setHighsWasmUrl("/assets/wasm/highs.wasm");
// Option B: Configure per solve call
await m.solve({ wasmUrl: "/assets/wasm/highs.wasm" });Next.js & Server-Side Rendering (SSR)
In Next.js App Router or Pages Router, optimization models can run either in Server Actions / API Routes (Node.js runtime) or on the client inside "use client" components:
"use client";
import { useState } from "react";
import { Model, maximize } from "@ohtaman/washi";
export default function OptimizerComponent() {
const [val, setVal] = useState<number | null>(null);
const handleSolve = async () => {
const m = new Model();
const x = m.continuous("x", { lb: 0, ub: 50 });
m.objective(maximize(x));
await m.solve();
setVal(x.value);
};
return (
<div>
<button onClick={handleSolve}>Run Optimization</button>
{val !== null && <p>Result: {val}</p>}
</div>
);
}📦 Browser ESM (<script type="module">)
You can import the pre-bundled browser ESM modules directly from a local static server (or from an npm CDN once a compatible release is published) without a build step:
<script type="module">
// Serve from your local static server / dist:
import { Model, expr, maximize } from "./dist/browser/washi.js";
// Or from an npm CDN once a compatible release is published:
// import { Model, expr, maximize } from "https://cdn.jsdelivr.net/npm/@ohtaman/washi@<version>/dist/browser/washi.js";
// Optional solver plugins:
// import "./dist/browser/solvers/glpk.js";
// import "./dist/browser/solvers/z3.js";
const m = new Model("BrowserESM");
const x = m.integer("x", { lb: 0 });
const y = m.integer("y", { lb: 0 });
m.objective(maximize(expr`30 * ${x} + 50 * ${y}`));
m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood");
m.constraint(expr`2 * ${x} + ${y} <= 50`, "labor");
await m.solve();
console.log("Optimal Objective:", m.objectiveValue);
</script>🟨 Classic <script> Tags (IIFE / Standalone Build / CDN)
For static HTML pages, CodePen, or JSFiddle, load washi.min.js via a standard <script> tag. All exports are attached to the global window.Washi object:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>WaSHI Browser Demo</title>
</head>
<body>
<h1>WaSHI Browser Optimization</h1>
<pre id="output">Solving...</pre>
<!-- Core WaSHI + HiGHS (MIT) from local static build / assets -->
<script src="./dist/browser/washi.min.js"></script>
<!-- Or via CDN once a compatible release is published: -->
<!-- <script src="https://cdn.jsdelivr.net/npm/@ohtaman/washi@<version>/dist/browser/washi.min.js"></script> -->
<!-- Optional plugins (auto-register on window.Washi): -->
<!-- <script src="./dist/browser/solvers/glpk.min.js"></script> -->
<!-- <script src="./dist/browser/solvers/z3.min.js"></script> -->
<script>
(async () => {
const { Model, expr, maximize } = Washi;
const m = new Model("BrowserDemo");
const x = m.continuous("x", { lb: 0, ub: 10 });
const y = m.continuous("y", { lb: 0, ub: 20 });
m.objective(maximize(expr`2 * ${x} + 3 * ${y}`));
m.constraint(expr`${x} + ${y} <= 15`);
await m.solve();
document.getElementById("output").textContent =
`Status: ${m.status}\n` +
`Objective: ${m.objectiveValue}\n` +
`x = ${x.value}, y = ${y.value}`;
})();
</script>
</body>
</html>NOTE
When loading washi.min.js from a cross-origin CDN (such as jsDelivr or unpkg), browsers block direct new Worker("https://cdn...") construction due to the same-origin policy. WaSHI automatically bridges cross-origin workers via a lightweight Blob wrapper using importScripts(), so Web Worker execution works out of the box from CDNs.
🔒 Offline, Intranet & Strict CSP Environments
WaSHI never phones home or falls back to public CDNs. To self-host in an air-gapped intranet or under a strict Content Security Policy (CSP):
- Copy Static Assets: Serve the files from
node_modules/@ohtaman/washi/dist/browser/on your own origin:washi.js/washi.min.js,highs.wasm, andhighs-worker.js- Optional GLPK:
solvers/glpk.js/solvers/glpk.min.js - Optional Z3:
solvers/z3.js/solvers/z3.min.js,solvers/z3-built.js,solvers/z3-built.wasm
- Content Security Policy (CSP) Headers:
- Allow WebAssembly compilation (
'wasm-unsafe-eval'inscript-src). - If using the default Web Worker or cross-origin Blob worker fallback, allow
worker-src 'self' blob:;. - If
blob:workers are prohibited by policy, either servehighs-worker.jsfrom'self'or run on the main thread withawait m.solve({ useWorker: false }).
- Allow WebAssembly compilation (
- Cross-Origin Isolation (Z3 Only):
- HiGHS and GLPK run in standard browser contexts without special headers.
- Z3 WASM uses pthreads +
SharedArrayBufferand requiresCross-Origin-Opener-Policy: same-originandCross-Origin-Embedder-Policy: require-corp(orcredentialless). See the Cross-Origin Isolation Guide for setup details.
⚡ Bun & Deno
Bun
bun add @ohtaman/washi
bun run app.tsDeno
In Deno (v1.30+ with npm: specifiers):
import { Model, maximize } from "npm:@ohtaman/washi";
const m = new Model();
const x = m.continuous("x", { lb: 0, ub: 10 });
m.objective(maximize(x));
await m.solve();
console.log("x =", x.value);