state and target shapes — what build hooks receive
When you write a
cppjs.build.jshook (prepare(state),build(state),getBuildParams(state, target), etc.), or anextensions[]plugin, you receive astateobject and atargetobject. This doc enumerates every key on both. Source:cppjs-core/cpp.js/src/state/index.js.
state — top-level keys
state = {
targets: Target[], // 20 built-in build targets (see § Target inventory)
config: ResolvedConfig, // merged cppjs.config.js + cppjs.build.js + system
cache: BuildCache, // persisted to .cppjs/cache.json
system: SystemConfig, // ~/.cppjs.json + defaults from systemKeys
}
state.config (the resolved configuration)
The output of loadConfig() in src/state/loadConfig.js. Every key:
state.config = {
general: { name: string }, // package name; OPFS namespace = /opfs/<name>/
dependencies: ResolvedConfig[], // immediate cpp.js deps (each is its own resolved config)
allDependencies: ResolvedConfig[], // flattened transitive deps, deduped by paths.project
paths: {
config: string, // import.meta.url of cppjs.config.js
project: string, // absolute project root
base: string, // = project unless overridden
cache: string, // .cppjs (build cache root)
build: string, // .cppjs/build (staging dir for sources)
output: string, // dist artifacts dir
native: string[], // C++ source roots (ARRAY)
module: string[], // SWIG .i source roots
header: string[], // C++ header roots
bridge: string[], // bridge code roots = [...native, build]
cmake: string, // resolved CMakeLists.txt path
cmakeDir: string, // dirname of cmake
cli: string, // path to cpp.js CLI install
cliCMakeListsTxt: string, // bundled fallback CMakeLists.txt
systemConfig: string, // = ~/.cppjs.json
},
ext: {
header: string[], // ['h','hpp','hxx','hh']
source: string[], // ['c','cpp','cxx','cc']
module: string[], // ['i'] — SWIG interface extensions
},
export: {
type: 'cmake' | 'source', // distribution shape
header: string, // include dir name in dist
libPath: string, // .a output dir
libName: string[], // .a basenames (one per item; e.g. ['ssl','crypto'])
binHeaders: string[], // headers to ship as raw binary blobs
},
target: { // FILTER (restrict which targets are built)
platform?: 'wasm' | 'android' | 'ios',
arch?: string, // 'wasm32' | 'wasm64' | 'arm64-v8a' | …
runtime?: 'st' | 'mt',
buildType?: 'release' | 'debug',
runtimeEnv?: 'browser' | 'node' | 'edge',
},
targetSpecs: TargetSpec[], // per-target overrides (see § targetSpecs)
build: { // merged from cppjs.build.js (package authors only)
withBuildConfig: boolean, // true if a cppjs.build.js was loaded
buildType?: 'cmake' | 'configure',
setState?: (state) => void,
beforeRun?: (cmakeDir) => Array<{program, parameters}>,
getBuildParams?: (state, target) => string[],
getExtraLibs?: (target) => string[],
sourceReplaceList?: (target, depPaths) => Array<{regex, replacement, paths}>,
env?: ((target) => string[]) | string[],
copyToSource?: Record<string, string[]>,
copyToDist?: Record<string, string[]>,
},
extensions: Extension[], // plugin hooks (see § Extensions)
functions: {
isEnabled: (target) => boolean, // default: checks ${cmakeDir}/{target.path} or .xcframework exists
},
package: object | null, // parsed package.json of paths.project
allDependencyPaths: { // per-target dependency lookup
[target.path]: {
cmake: { ... },
[libName]: { root, header, libPath, lib, bin },
},
},
dependencyParameters: ..., // CMake -D injection from calculateDependencyParameters()
}
state.cache
Persisted to .cppjs/cache.json between builds:
state.cache = {
hashes: Record<string, string>, // file hash → seen?
interfaces: Record<string, string>, // SWIG .i hash cache
bridges: Record<string, string>, // bridge code cache
}
isSourceNewer checks mtime against this cache to decide whether to rebuild a target.
state.system
Loaded from ~/.cppjs.json, merged with defaults from cppjs-core/cpp.js/src/utils/systemKeys.js:
state.system = {
XCODE_DEVELOPMENT_TEAM: string, // default '' (required for iOS device builds)
RUNNER: 'DOCKER_RUN' | 'DOCKER_EXEC' | 'LOCAL', // default 'DOCKER_RUN'
LOG_LEVEL: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR', // default 'INFO'
}
target — single build target
A single entry from state.targets[]. Every key:
target = {
platform: 'wasm' | 'android' | 'ios',
arch: string, // 'wasm32' | 'wasm64' | 'arm64-v8a' | 'x86_64' | 'iphoneos' | 'iphonesimulator'
runtime: 'st' | 'mt',
buildType: 'release' | 'debug',
runtimeEnv?: 'browser' | 'node' | 'edge', // wasm only
path: string, // computed: '{platform}-{arch}-{runtime}-{buildType}'
releasePath: string, // = path with buildType→'release' (always release variant)
// wasm-only computed names:
rawJsName?: string, // pre-bundled emcc output: '{name}-{path}.{runtimeEnv}.js'
jsName?: string, // final bundled JS: '{name}-{path}.{runtimeEnv}.js'
wasmName?: string, // wasm binary: '{name}-{path}.{runtimeEnv}.wasm'
dataName?: string, // emcc preload: '{name}-{path}.{runtimeEnv}.data'
dataTxtName?: string, // preload manifest: '{name}-{path}.{runtimeEnv}.data.txt'
}
Target inventory — 20 built-in targets
From cppjs-core/cpp.js/src/state/index.js lines 8–197:
Wasm32
| platform | arch | runtime | buildType | runtimeEnv |
|---|---|---|---|---|
| wasm | wasm32 | st | release | browser |
| wasm | wasm32 | st | release | edge |
| wasm | wasm32 | st | release | node |
| wasm | wasm32 | st | debug | browser |
| wasm | wasm32 | st | debug | edge |
| wasm | wasm32 | st | debug | node |
| wasm | wasm32 | mt | release | browser |
| wasm | wasm32 | mt | release | node |
| wasm | wasm32 | mt | debug | browser |
| wasm | wasm32 | mt | debug | node |
mt has no
edgeruntimeEnv. Edge runtimes (Cloudflare Workers, Deno Deploy) don't expose Web Workers / SharedArrayBuffer. Seethreading.md.
Wasm64 (memory-64; same axes)
Same shape as wasm32 above, but arch: 'wasm64'. Used when you need >4GB linear memory. Browser support is partial (Chrome 119+).
Android
| platform | arch | runtime | buildType |
|---|---|---|---|
| android | arm64-v8a | mt | release |
| android | arm64-v8a | mt | debug |
| android | x86_64 | mt | release |
| android | x86_64 | mt | debug |
Android is always
mt. No single-thread variant. NDK API 33, NDK 27.3.13750724.
iOS
| platform | arch | runtime | buildType |
|---|---|---|---|
| ios | iphoneos | mt | release |
| ios | iphoneos | mt | debug |
| ios | iphonesimulator | mt | release |
| ios | iphonesimulator | mt | debug |
iOS is always
mt. No single-thread variant. iOS deployment target 13.0; bitcode embedded.
targetSpecs[] — per-target override entries
When you want to override defaults for a specific subset of targets, push entries to cppjs.config.js targetSpecs[]:
type TargetSpec = {
// Filter (any combination — entries match if all set fields match)
platform?: 'wasm' | 'android' | 'ios',
arch?: string,
runtime?: 'st' | 'mt',
buildType?: 'release' | 'debug',
runtimeEnv?: 'browser' | 'node' | 'edge',
// Overrides
specs: {
cmake?: string[], // extra -D flags appended to cmake configure
emccFlags?: string[], // extra -s/-O flags appended to emcc command
env?: Record<string, string>, // env vars passed to Wasm process at runtime (or via CFLAGS/LDFLAGS at build)
data?: Record<string, string>, // bundle data files: { 'src-dir': 'dest-dir' }
ignoreLibName?: string[], // suppress specific .a names from being linked
},
}
Example (wasm-only SIMD, all archs):
targetSpecs: [{
platform: 'wasm',
specs: { emccFlags: ['-msimd128', '-DUSE_SIMD'] },
}]
Example (gdal-wasm: disable threading on st runtime via env):
targetSpecs: [{
platform: 'wasm',
runtime: 'st',
specs: { env: { GDAL_NUM_THREADS: '0' } },
}]
extensions[] — plugin hooks
type Extension = {
loadConfig?: {
after?: (config) => void, // mutate resolved config after load
},
buildWasm?: {
beforeBuild?: (emccFlags) => void, // all wasm targets
beforeBuildBrowser?: (emccFlags) => void, // browser only
beforeBuildEdge?: (emccFlags) => void, // edge only
beforeBuildNodeJS?: (emccFlags) => void, // node only
},
createLib?: {
setFlagWithBuildConfig?: (buildEnv, cFlags, ldFlags) => void,
setFlagWithoutBuildConfig?: (buildEnv) => void,
},
}
Used internally by built-in extensions (e.g. for OpenSSL Android cert injection). Most users never write extensions; prefer targetSpecs first.
When you write a hook, here's what to do
| You want to | Use this |
|---|---|
| Read which target is being built | target.platform, .arch, .runtime, .buildType, .runtimeEnv |
| Find where source code is | state.config.paths.build (extracted upstream sources land here) |
| Find where artifacts go | state.config.paths.output |
| Find a dep's installed headers/libs | state.allDependencyPaths[target.path][libName].header / .lib |
| Add a CMake flag | targetSpecs[].specs.cmake (preferred) or getBuildParams return value |
| Add an emcc flag | targetSpecs[].specs.emccFlags |
| Inject env to the running Wasm | targetSpecs[].specs.env or cppjs.config.js env: {} |
| Patch upstream source | cppjs.build.js replaceList or sourceReplaceList(target, depPaths) hook |
| Bundle data files into the .data preload | targetSpecs[].specs.data or sub-arch data: {} |
| Run something before cmake | cppjs.build.js beforeRun(cmakeDir) |
See also
overrides.md— full catalog of override mechanisms with priority order.cppjs-config.md— consumer-side config field-by-field.cppjs-build.md— package-author hooks (setState, beforeRun, getExtraLibs, sourceReplaceList, env, copyToSource, copyToDist, prepare, build).- Source:
cppjs-core/cpp.js/src/state/index.js,loadConfig.js,actions/target.js.