Browser
In the browser your C++ runs as WebAssembly behind a JavaScript loader. A bundler plugin turns each header you import into a JavaScript module, compiles the wasm while you develop and emits it with your bundle; without a bundler, the CLI builds the loader and the wasm for a <script> tag.
Setup
A browser project needs three things: the plugin for its bundler, a crossbind.config.js at the project root and a header imported from app code. The scaffolder sets up all three - React, Vue or Svelte on Vite, React on Rspack, or a page with no bundler:
In an existing app, add the plugin for your bundler from the sections below and this config; Configuration has the rest of what the file takes.
C++ lives in src/native. Import a header and call initNative() once: it boots the module and binds every header the app imports.
The call returns a promise because the module runs in a Web Worker by default - see Workers and storage.
Vite
The plugin sits next to your framework's plugin; React, Vue and Svelte need nothing more.
Webpack
getRule() hands headers and .rs files to the loader package, which is why both are installed. getDevServerConfig() serves the compiled module in dev and sends the COOP/COEP headers; if you already have a devServer block, spread it in and keep its headers and setupMiddlewares.
await, and Node cannot require() a module that does: a CommonJS webpack.config.js that requires the plugin fails with ERR_REQUIRE_ASYNC_MODULE. Name it webpack.config.mjs, or set "type": "module".Rspack
Rspack takes the same two packages through its webpack-compatible plugin API.
Rollup
The Rollup plugin is the core the Vite plugin wraps; use it directly only for a plain Rollup build. Rollup has no dev server: rollup -c -w rebuilds the wasm when a native file changes, and nothing reloads a page.
The bundle step writes crossbind.js and crossbind.wasm into the output directory. Plain Rollup has no base path, so the generated code loads them from the site root.
No bundler
A static page builds with the CLI and loads the result with a <script> tag. Point paths.output at the folder you serve and build for the browser:
That writes dist/my-app-wasm-wasm32-st-release.browser.js and its .wasm. The script is a UMD bundle that defines a global initNative, so load it with a classic <script> tag; path says where the files are, relative to the page.
Nothing reads your imports here, so the build binds every .h file under src/native, and a library reaches the page through a header of your own. There is no dev server and no rebuild on save.
Workers and storage
In a browser, initNative() starts the module in a Web Worker: useWorker defaults to on wherever Worker exists. The page thread never blocks on C++, and every call returns a promise, construction included (await new X()). Set dts: 'promise' in crossbind.config.js so the generated TypeScript says the same.
Pass useWorker: false to keep the module on the page thread, where calls return their values directly and a long call blocks the page:
Files under /opfs/<app>/ survive a reload; OPFS needs the worker, and the generated loader turns it on by default only for mt builds. Files under /memfs/<app>/ live in memory for the session. Files covers both, and Threads and workers how useWorker and runtime: 'mt' combine - they are independent.
Development
The first dev start compiles the wasm in Docker, or on a remote runner, and takes a while; later starts reuse the cache in .crossbind/. Dev servers build the debug variant, production builds the release one.
Saving a header, a C++ source or a .rs file in src/native rebuilds the wasm and reloads the page, and a function you newly import from a library header binds when you save the importing file. The Vite and webpack dev servers, and vite preview, send the COOP/COEP headers, so a multithreaded build runs locally as it is.
Production build
vite build, rspack build or a production webpack build compiles the release variant and writes crossbind.js, crossbind.wasm and, when the project packs data files, crossbind.data.txt into the output directory. Deploy them with the bundle. The generated code loads them from Vite's base or webpack's output.publicPath (auto included), so an app served from a sub-path finds them.
A single-threaded build needs no special headers. A runtime: 'mt' build needs SharedArrayBuffer, which browsers enable only on a cross-origin isolated page, so the production host has to send:
Without them SharedArrayBuffer is undefined and the module fails to start; crossOriginIsolated must be true in the console of the deployed page. Threads and workers shows where each host takes the headers. GitHub Pages cannot send them, so a multithreaded build does not run there.
Another bundler
The plugins are thin: the work is done by functions crossbind exports, and @crossbind/plugin-rollup is the smallest complete plugin to copy. Each one starts by picking the browser target:
- Before the first transform,
buildDependencies()puts the library headers in place. - Imported headers become modules through
createBridgeFile()andgetCrossbindScript(); a library header such as@crossbind/port-zlib/zlib.hresolves throughgetDependFilePath(). - The bundle step compiles with
createLib()andbuildWasm()and emits the target'sjsName,wasmNameanddataTxtNamefromstate.config.paths.buildascrossbind.js,crossbind.wasmandcrossbind.data.txt. - In dev, the plugin serves those files and watches
state.config.paths.nativeandappSourceFiles(), whose imports decide what a library header binds.
Limits
- Next.js has no example or test in the repository, and the webpack plugin does not run under Turbopack.
- COEP in development. The dev servers send
Cross-Origin-Embedder-Policy: require-corpfor every build, single-threaded too, so a cross-origin image or script without aCross-Origin-Resource-Policyheader fails to load in dev but not in production. - Synchronous callbacks into page code do not cross the worker: design them as promise round-trips, or set
useWorker: false.
Complete apps: web-react-vite, web-vue-vite, web-svelte-vite, web-react-rspack and web-vanilla - each one is also a scaffolder template.