Skip to main content

Playbook — Integrate cpp.js into a JavaScript project

Persona 2 — Integrator. The user has an existing (or new) JavaScript / TypeScript / React Native project and wants to consume a @cpp.js/package-* (e.g. GDAL) or wrap their own C++ code from inside it.

Goal​

Drop cpp.js into the user's project with the smallest, most idiomatic config change for their framework. Choose the right plugin, wire up the bundler, set up COOP/COEP for multithread when needed, and verify with a smoke build.

Step 0 — Detect the framework first​

Don't guess. Run framework detection:

node scripts/detect-framework.js [path-to-project]

(Sprint 2 helper. From outside this repo: copy scripts/detect-framework.js or describe the target's package.json and key config files.)

The detector inspects package.json deps + filesystem signatures (vite.config.*, next.config.*, metro.config.*, wrangler.toml, etc.) and returns:

{
"framework": "vite" | "webpack" | "rspack" | "rollup" | "react-native-cli"
| "react-native-expo" | "cloudflare-worker" | "nextjs"
| "nodejs" | "vanilla" | "unknown",
"confidence": "high" | "medium" | "low",
"evidence": [...],
"recommendedPlaybook": "docs/playbooks/integration/<framework>.md"
}

If confidence is low or unknown, ask the user before continuing.

Step 1 — Decision tree​

What does package.json deps + filesystem look like?
│
├─ has "vite" → docs/playbooks/integration/vite.md
├─ has "webpack" or "@rspack/*" → docs/playbooks/integration/webpack-rspack.md
├─ has "rollup" (only) → docs/playbooks/integration/rollup.md
├─ has "next" → docs/playbooks/integration/nextjs.md
├─ has "react-native" + no "expo" → docs/playbooks/integration/react-native-cli.md
├─ has "expo" → docs/playbooks/integration/react-native-expo.md
├─ has "wrangler" or worker config → docs/playbooks/integration/cloudflare-worker.md
├─ Node-only project (no bundler) → docs/playbooks/integration/nodejs.md
└─ plain HTML / no bundler → docs/playbooks/integration/vanilla.md

Per-framework playbooks contain:

  • Which plugin to install (@cpp.js/plugin-vite, etc.)
  • The exact config diff
  • Where to call init(...)
  • Headers / build hooks specific to that bundler
  • A reference sample (cppjs-samples/cppjs-sample-web-vue-vite/, etc.) to mirror

Step 2 — Multithread decision​

Ask once, early:

Will this need to use multiple CPU threads (image processing, large data, crypto, geospatial)? Or is single-threaded fine?

User answerRecommend
"Yes, perf matters" / image / video / geo / crypto / large dataruntime: 'mt' + COOP/COEP headers in production
"No, simple use" / occasional one-off callsruntime: 'st' (default), no headers needed
"Not sure"Start with st; switching to mt later is a config flag away

When recommending mt, always mention production headers:

In dev/preview, the cpp.js bundler plugin sets these for you. In production, your hosting layer (Vercel, Netlify, Cloudflare, nginx, S3+CloudFront, …) must send:

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

Without them, browsers disable SharedArrayBuffer and threading silently fails.

The framework playbook will name the host-specific config file (vercel.json, _headers, nginx snippet, etc.).

Step 3 — Pick what to consume​

Does the user need a library cpp.js already prebuilds?
│
├─ Browse cppjs-packages/ (or https://cpp.js.org packages page) for matches:
│ gdal, openssl, geos, geotiff, proj, sqlite3, tiff, lerc, zstd, jpegturbo,
│ webp, iconv, expat, curl, zlib, spatialite
│
├─ YES → pnpm add @cpp.js/package-<name> + matching plugin.
│ Skip to Step 4.
│
└─ NO → User has their own .cpp / a library not yet packaged.
Two sub-options:
(a) Inline in their project: write `cppjs.config.js` pointing at
their src/native/, no separate package needed.
(b) Publish a reusable package: see docs/playbooks/new-package.md
(Persona 3).
Sub-option (a) is most common for "my own code".

Step 4 — Touch the config files​

The agent may edit the user's bundler config (vite.config.*, next.config.*, metro.config.js, webpack.config.*, wrangler.toml). Per-framework playbooks specify exactly what changes. Always show the diff before applying when the file isn't trivially blank.

Common touchpoints:

FileWhat changes
package.json+ @cpp.js/package-<name>, + @cpp.js/plugin-<bundler>
vite.config.* / webpack.config.* / etc.Add the cpp.js plugin to plugins: []
cppjs.config.js (new)Project-level cpp.js config (deps to consume, build target)
Public env / headers configCOOP/COEP for mt builds in production
tsconfig.json (if TS)No change needed — types ship with each @cpp.js/package-*

Step 5 — Smoke build​

After integrating:

# Install
pnpm install

# Dev (verifies plugin wires up, bundler loads cpp.js)
pnpm dev # or `pnpm start`, framework-dependent

# Production build
pnpm build

The framework playbook lists what to look for in the dev/build output (e.g. "you should see cpp.js compiled for browser in the log", "/cpp.wasm should be served at runtime").

Validation​

  • Framework detected with high confidence (or user-confirmed otherwise).
  • Correct plugin installed.
  • Bundler config diff applied and explained to the user.
  • Multithread decision made; if mt, COOP/COEP setup documented for the user's deploy target.
  • pnpm dev succeeds, the bundle loads cpp.js/cpp.wasm without 404s.
  • pnpm build produces artifacts.
  • User can call into a cpp.js function from JS (e.g. await initNative(); Module.someFn(...) returns expected result).

Common pitfalls​

  • Wrong plugin for the bundler. Vite ≠ Webpack ≠ Rspack ≠ Rollup. Use the framework playbook to pick.
  • Forgetting COOP/COEP in prod. Dev works, prod fails silently with "SharedArrayBuffer is not defined". Always tell the user upfront.
  • Mixing mt and st artifacts. Once the user picks a runtime, config it consistently. Don't half-migrate.
  • TypeScript in user project, expecting types from cpp.js. Types ship per-package; if a @cpp.js/package-* lacks .d.ts, file an issue.
  • Editing config blindly. Show the diff. Bundler configs are the user's source of truth — bad edits break their whole app, not just cpp.js.
  • Not using filter-detect on monorepos. If the user's repo has multiple apps, run detection in the right subdir.

Reference​

  • Plugin sources: cppjs-plugins/cppjs-plugin-{vite,webpack,rollup,react-native,metro}/index.js
  • Sample integrations: cppjs-samples/cppjs-sample-web-* and cppjs-samples/cppjs-sample-mobile-*
  • Framework detector: scripts/detect-framework.js
  • Per-framework playbooks: docs/playbooks/integration/<framework>.md (added in Sprint 3)