Skip to content

WASM Integration

The WASM package is the broadest Formulon surface. It runs in browsers, web workers, and Node — the same @libraz/formulon binary used by the JS API and by formulon-cell.

Hosting matters

Browser success depends on server headers, worker format, and bundler behavior. Verify the deployed environment, not only local development.

Glossary: pthread workers

The WASM module is built with -pthread. Internally, the recalc scheduler spawns Web Workers via Emscripten. Each worker uses SharedArrayBuffer to share the WASM heap, which is why browsers require cross-origin isolation.

Glossary: COOP / COEP (Cross-Origin Isolation)

Two HTTP response headers needed before browsers expose SharedArrayBuffer. Both must be set on the page:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Without them, createFormulon() falls back to a stub engine.

Load once

Initialize the module once per worker or process, then reuse workbook instances for related work.

ts
import createFormulon from '@libraz/formulon'

const Module = await createFormulon()
const result = Module.evalFormula('=SUM(1,2,3)')

createFormulon() is async; in browsers, instantiation pulls the .wasm binary and spins up the pthread pool. Keep the Module reference long-lived.

Keep bytes explicit

Pass workbook bytes in and receive workbook bytes out. That keeps the UI layer, upload layer, and persistence layer separate from calculation.

Always call workbook.delete() when done. WASM Workbook handles wrap native memory; they are not ordinary garbage-collected JavaScript objects.

ts
const workbook = Module.Workbook.loadBytes(bytes)
try {
  if (!workbook.isValid()) throw new Error(Module.lastErrorMessage())
  workbook.recalc()
} finally {
  workbook.delete()
}

Lifetime patterns

Wrap the try / finally in a helper (withWorkbook(bytes, fn)) so every call site is consistent. The cost of forgetting delete() is a memory leak inside the WASM heap that survives until the page reloads.

Watch the size budget

The WASM build has a strict size budget. Avoid adding dependencies to browser-facing paths unless they are necessary and measured. The actual numbers live in Size budgets.

Bundler requirements

For Vite, configure ES module workers:

ts
export default defineConfig({
  worker: { format: 'es' },
  optimizeDeps: { exclude: ['@libraz/formulon'] },
  build: {
    target: 'es2022',
    rollupOptions: { external: [/^node:/] }
  }
})

For browsers, serve with the COOP / COEP headers shown above when pthread workers are enabled. See Bundler setup for the same configuration applied to formulon-cell, and Troubleshooting for common bundler messages.