OpenCascade.js

Two-channel config model

Compile-time OCJS_* env vars vs link-time emccFlags — what each channel controls and when to reach for it.

Maintainer track. Skip this page if you consume the published npm package — every shipped build already pins the right channel-1 flags. The channels matter when you rebuild OCJS from source.

OpenCascade.js exposes two configuration channels with different lifecycles and different scope. Treat them as orthogonal — confusion between the two is the most common cause of build errors.

Channel 1 — compile-time OCJS_* env vars

Set at the bindgen + C++ compile stage. Bake into every .o file the final wasm is linked from.

VariableEffect
OCJS_EXCEPTIONS=1Compile with -fwasm-exceptions everywhere
OCJS_SIMD=1Compile with -msimd128
OCJS_RELAXED_SIMD=1Additionally emit -mrelaxed-simd (Chrome/Firefox only)
OCJS_BIGINT=1Compile with -sWASM_BIGINT
OCJS_EVAL_CTORS_LEVEL=2-sEVAL_CTORS=2
OCJS_STRICT_TYPES=1Escalate missing-typedef warning to a build failure (default 0: warn-only — the triage summary is always printed to stderr)

These flags pin into a build-flags manifest alongside each cached .o. Mixing builds with mismatched flags fails-loud at link time — never silent ABI breakage.

Set per-consumer-build in your YAML. Apply only to the final emcc link step.

mainBuild:
  emccFlags:
    - -O3
    - -sMODULARIZE=1
    - -sEXPORT_ES6=1
    - -sALLOW_MEMORY_GROWTH=1

These flags control loader shape (ESM vs CommonJS), memory limits, and environment detection. They cannot retroactively change the exception model or SIMD configuration the .o files were compiled with.

Why the split?

The bindgen produces ~4,400 .o files in the maintainer Docker pipeline. Caching them across consumer builds turns a 30-minute full build into a 60-second link. The cache key must be deterministic — that's what the compile-time channel fingerprint guards.

If consumers could change compile-time flags via YAML, the cache invariant would break: a downstream -fexceptions request would silently rebuild every class, defeating the cache. Splitting the channels makes the contract explicit.

When you actually need to change channel 1

Almost never as a consumer. The published build is the named single-threaded preset from build-configs/configurations.json, which pins:

  • OCJS_EXCEPTIONS=1 + OCJS_EH_MODE=wasm (native wasm exceptions)
  • OCJS_SIMD=1 (baseline SIMD, Safari-compatible)
  • OCJS_BIGINT=1 (no i64 legalisation)
  • OCJS_EVAL_CTORS=true + OCJS_EVAL_CTORS_LEVEL=2
  • OCJS_CLOSURE=true + OCJS_CONVERGE=true
  • THREADING=single-threaded

Presets are addressed by name, not by raw env vars — see Named compile-time configurations for the full list (single-threaded, single-threaded-smallest, multi-threaded, debug).

The combinations not yet pre-built are exotic — OCJS_RELAXED_SIMD=1 for Chrome-only deploys, custom allocator pairings, non-default WASM-opt budgets. To get one, fork the Docker image build pipeline and rebuild from source.

Diagnostic checklist

If a build acts strangely:

  1. Confirm channel-1 fingerprint is what you expect. The full flag set is recorded in the in-repo build manifest (regenerated by every bindings stage):
    cat build/build-flags.json
    For the published tarball, the consumer-facing equivalent is dist/opencascade_full.provenance.json, which captures the active preset, compile flags, and commit SHA the wasm was built from.
  2. Confirm channel-2 flags in the YAML actually made it into the wasm:
    strings dist/my-build.wasm | grep -E 'STACK_SIZE|MAXIMUM_MEMORY'
  3. Recompare against a known-good cached build — drift here points at channel-1 cache poisoning (rebuild the deps layer).

On this page