Importing C++ Straight into TypeScript
April 2, 2026
A while ago I was building an n-body gravity simulation in the browser. Once the number of bodies grows, the inner loop is the obvious thing to move out of JavaScript, and WebAssembly is the obvious place to move it. What I wanted was simple: write a C++ file, import it from TypeScript, and call it like any other function. That wish became vite-plugin-cpp-loader, which is now on npm.
Where it started
In the gravity grid project there is a small experiment: a C++ counter
class, wrapped in a handful of extern "C" functions, with the
compile command pasted as a comment at the bottom of the file. On the
TypeScript side I fetched the .wasm file, instantiated it,
and wrote an interface by hand describing what the exports looked like.
It worked, but every step was manual. Change a signature in C++ and the TypeScript interface silently lies until something breaks at runtime. Re-run the command by hand. Keep the output path in sync. None of this is hard, it is just friction, and friction decides whether you experiment or not.
What it looks like now
You install the plugin, add it to the Vite config, and make sure
em++ from Emscripten is on your PATH. Then you write plain
C++:
// math.cpp
int add(int a, int b) {
return a + b;
}
// main.ts
import { add } from './math.cpp';
add(2, 3); // 5
That is the whole interface. Top-level functions are exported, and if
you want one to stay private you mark it static. Passing a
string to add is a type error in the editor, because the
types come from the C++ signature.
How it works
Vite calls the plugin whenever something imports a .cpp
file. The plugin reads the source and hashes it. If it has compiled that
exact content before, it reuses the cached output from
node_modules/.cpp-wasm. Otherwise it spawns
em++, with Embind linked in and an ES module output.
The part I like most is that you do not write any binding code. If your
file has no EMSCRIPTEN_BINDINGS block, the plugin finds the
non-static top-level functions with a regular expression, appends a
generated bindings block to a temporary copy of your file, and compiles
that. Your source is never touched. I started out requiring the
Emscripten boilerplate in the C++ file, and removing that requirement
was one of the changes that made the 2.0 release feel right.
For the types, I ask the compiler first. Emscripten can emit a
TypeScript definition file for the bindings, and the plugin parses
function names, parameters and return types out of it. If that file is
missing or empty, it falls back to a regex pass over the C++ source. The
result is written as a math.cpp.d.ts file next to the
source, and the module Vite actually sees is a small generated wrapper
that awaits the Emscripten module and re-exports each function.
In dev, the wasm is served as a separate file. In a production build the
plugin passes -sSINGLE_FILE, so the wasm is inlined into the
JavaScript and there is nothing extra to deploy.
The limits
The type mapping is deliberately small: numbers, booleans, strings and
void. Anything else, including STL containers and your own classes,
falls back to any, and the README says plainly that those
are not supported as parameters or return types. The regex fallback is
also exactly that: a regex, not a C++ parser, so unusual signatures can
confuse it. And because the generated module uses top-level await, your
build target has to allow it. I would rather document these edges than
pretend they are not there.
How it evolved
The first commits are from May 2025: a stub plugin, then real compilation, then declaration files, and a first npm release within a couple of days. After that the project sat for months. In April 2026 I came back, simplified the structure, dropped the Emscripten boilerplate requirement, renamed the package, and added support for the latest Vite. That was 2.0.
I do not have a benchmark to show you, and this post is not an argument that you should rewrite your hot loops in C++. The point of the tool is narrower: if you want to try, the cost of trying should be one file and one import. What you learn after that is up to you.