Quickstart — custom build
Install @libcascade/toolchain, write libcascade.config.ts, run libcascade build, and consume the artifacts from dist/.
Most applications should install the prebuilt libcascade
package and stop there. Reach for the toolchain when you need a WASM binary the
prebuilt one cannot be: a trimmed symbol set, your own C++ wrappers, or
different Emscripten link settings.
Pre-release. @libcascade/toolchain ships lockstep-versioned with
libcascade — the toolchain that built the binary you depend on is the one
whose types describe it. Both are at 3.0.0-beta.0 until the v3 release.
Prerequisites
- Node 22+.
- A container engine: Docker Desktop, colima,
Rancher Desktop, OrbStack, or Podman. The toolchain drives the published image
for you — you never write a
dockercommand. - A working directory under your home folder on macOS/Windows. Docker Desktop
does not share
/tmpor/optinto its VM by default, and outputs vanish silently.
GitHub-hosted macOS runners ship no container engine at all. Run toolchain builds on a Linux runner.
1. Install
npm install --save-dev @libcascade/toolchainIt is a devDependency on purpose: it shells out to a container engine and
must never appear in a production dependency graph. It has no postinstall
hook and does nothing until you invoke it.
2. Write the config
import { defineBuild } from '@libcascade/toolchain';
export default defineBuild({
name: 'myapp',
bindings: [
'BRepPrimAPI_MakeBox',
'BRepAlgoAPI_Cut',
'BRepMesh_IncrementalMesh',
'STEPControl_Reader',
'STEPControl_Writer',
'TopExp_Explorer',
'TopoDS_Shape',
'gp_Pnt',
],
settings: {
MODULARIZE: true,
EXPORT_ES6: true,
ALLOW_MEMORY_GROWTH: true,
INITIAL_MEMORY: '100MB',
MAXIMUM_MEMORY: '4GB',
// Required with `exceptions: 'wasm'` on emsdk 6.0.5 — the link fails
// without these three. See the exception-helpers note below.
EXPORTED_RUNTIME_METHODS: [
'FS',
'getExceptionMessage',
'incrementExceptionRefcount',
'decrementExceptionRefcount',
],
ENVIRONMENT: ['web', 'worker', 'node'],
// OCCT's OSD_MemInfo references mallinfo, which Emscripten does not provide.
ERROR_ON_UNDEFINED_SYMBOLS: false,
},
compilerFlags: { optimize: 'O3', simd: true, exceptions: 'wasm', noEntry: true },
variants: [{ name: 'single' }],
assemble: { exports: 'factory' },
});Everything in that file is compile-checked. A typo in bindings is a
TypeScript error with a "did you mean" suggestion, an unknown -s name in
settings does not exist on the type, and INITIAL_MEMORY: '100mb' — which
emcc silently misparses — does not match the MemorySize template literal.
See Config reference for every field.
Do not have a bindings list yet? npx libcascade detect src scans your source
and prints one to paste in. Read
detect and check first — it is an onboarding
tool, not a size optimizer.
3. Build
npx libcascade buildFor each variant the CLI renders the container-side yml into .libcascade/,
pulls the digest-pinned image, runs the link with the artifact directory
mounted, and moves the results into dist/ only after the run exits 0 — a
failed build never leaves half an artifact behind.
dist/
myapp_single.js # Emscripten glue
myapp_single.wasm # the binary
myapp_single.d.ts # generated TypeScript declarations
myapp_single.js.symbols # symbol map
myapp_single.build-manifest.json # requested / compiled / missing symbols
myapp_single.provenance.json # toolchain + source commitsAdd .libcascade/ to your .gitignore. The rendered ymls and the raw
container output stay there for inspection after a failure.
Useful flags:
npx libcascade build --variant single # one variant instead of all
npx libcascade build --render-only # render the yml(s) and stop — no engine needed
npx libcascade build --config path/to/libcascade.config.tsbuild fails loudly when the container's own build-manifest.json reports
validation_passed: false, printing the missing symbols. That matters because
a missing binding links successfully and fails at runtime with a
BindingError — the link step alone proves nothing.
4. Assemble the package surface
npx libcascade assembleassemble never touches a container. It reads the per-variant .d.ts and
build manifests from dist/ and writes the npm-publishable surface next to
them: one shared types.d.ts, an ./init entry exposing createInstance, a
root entry, and an exports.json fragment. Add --write-exports to merge that
fragment straight into your package.json.
See Variants and assemble for what each
generated file does and when to pick exports: 'eager' over 'factory'.
5. Consume the artifacts
With assemble: { exports: 'factory' }, the root entry re-exports the factory
and nothing is instantiated until you ask:
import { createInstance } from 'my-occt-package';
export const oc = await createInstance();Inside the package that produced dist/, import the generated entry directly:
import { createInstance } from './dist/init.js';
const oc = await createInstance();
using box = new oc.BRepPrimAPI_MakeBox(10, 10, 10);
const shape = box.Shape();createInstance owns the plumbing consumers used to copy by hand: variant
selection, the glue self-reference pthread workers spawn from, Node file:
URL → path conversion, and OCCT thread-pool sizing.
Exception helpers on emsdk 6.0.5
The image's Emscripten removed -sEXPORT_EXCEPTION_HANDLING_HELPERS. With
compilerFlags: { exceptions: 'wasm' } the link pipeline hard-fails unless
EXPORTED_RUNTIME_METHODS exports the three helpers directly:
EXPORTED_RUNTIME_METHODS: [
'getExceptionMessage',
'incrementExceptionRefcount',
'decrementExceptionRefcount',
],Add whatever else your app needs ('FS', 'wasmMemory') to the same array.
Keep it honest in CI
npx libcascade check srccheck recomputes the symbols your source references and exits non-zero when
any of them is missing from bindings. It converts the runtime-BindingError
failure class into a build-time one. Put it next to your typecheck.
Next steps
Config reference
Every defineBuild field, its type, and what the generated unions check.
Variants and assemble
One bindings list, N binaries, one types.d.ts, and the generated entries.
Migrate from a yml build
Field-by-field mapping from defaults.yml + ytt + docker run scripts.
Extend with C++
Declare your own wrapper files and the symbols they provide.