How to Compile TypeScript Cores to Native Zig Code at Build Time in the Native SDK
Use the native-core CLI to run transpileFile() on your TypeScript core, then add that generation step as a dependency in build.zig so the resulting Zig module is compiled with your project at build time.
The Vercel Labs Native SDK ships a TypeScript-to-Zig transpiler that converts application cores written in a supported TypeScript subset into native Zig modules during compilation. This build-time workflow lets you author business logic in TypeScript while producing efficient native binaries through Zig. Learning how to drive this pipeline from packages/core/src/cli.ts and integrate it into build.zig is the key to shipping hybrid TS/Zig projects.
How the TypeScript-to-Zig Transpiler Works
The entry point for the transpilation pipeline is transpileFile(entry: string, options?: TranspileOptions): TranspileResult in packages/core/src/transpile.ts at line 53. This function moves the source code through five discrete stages before emitting a single Zig file.
- Parse and type-check the entry file and all its imports using the TypeScript compiler API via
typed_ast.ts. - Resolve the module graph in
modules.ts, ensuring only files within the app’ssrc/tree are included. - Run the subset checker from
checker.tsto enforce Native SDK rules R1–R18. - Perform integer inference in
infer.tsto map JavaScript numbers to Zigi64where required. - Emit a single Zig module from
emitter.ts, writing the runtime prelude, kernel capacity comptime parameters, JavaScript-semantics helpers, and exported core symbols.
Running the native-core CLI Tool
The command-line wrapper lives in packages/core/src/cli.ts and exposes the native-core binary. When invoked, it calls transpileFile(entry, { frameCap, heapCap }) and writes the generated Zig code to the path provided by -o <out.zig> or prints it to stdout.
You must supply two comptime constants that configure the runtime kernel (rt.zig):
--frame-capsets the frame arena size.--heap-capsets the model heap size.
A typical manual invocation looks like this:
node packages/core/src/cli.ts src/core.ts -o src/core.zig \
--frame-cap 131072 \
--heap-cap 524288
Integrating Transpilation into build.zig
Because compilation happens entirely at build time, you can treat the transpiler as a standard system command inside build.zig. Add a custom step that runs the CLI before your main executable compilation, and make the executable depend on that step.
const std = @import("std");
pub fn build(b: *std.Build) void {
const nativeCore = b.addSystemCommand(&.{
"node",
"packages/core/src/cli.ts",
"examples/system-monitor-ts/src/core.ts",
"-o", "examples/system-monitor-ts/src/core.zig",
"--frame-cap", "65536",
"--heap-cap", "262144",
});
const exe = b.addExecutable(.{
.name = "system-monitor",
.root_source_file = b.path("examples/system-monitor-ts/src/main.zig"),
.target = b.standardTargetOptions(.{}),
.optimize = b.standardOptimizeOption(.{}),
});
exe.addCSourceFile(b.path("examples/system-monitor-ts/src/core.zig"), &.{});
exe.step.dependOn(&nativeCore.step);
b.installArtifact(exe);
}
Calling exe.step.dependOn(&nativeCore.step) guarantees that zig build regenerates core.zig whenever the TypeScript source changes, preventing stale artifacts from being compiled.
Importing and Using the Generated Zig Module
The output produced by packages/core/src/emitter.ts is a self-contained Zig module that includes three major components:
- A runtime kernel (
rt) parameterized by theframe_capandheap_capcomptime values. - Helper functions that replicate JavaScript semantics, including
jsAnd,jsOr,jsShl, andnumEq. - Exported symbols matching the public API of the original TypeScript module.
You can import the generated file like any other Zig module and interact with its exports directly:
const std = @import("std");
const core = @import("core.zig");
pub fn main() void {
const phase = core.model.phase;
std.debug.print("App phase: {s}\n", .{ @tagName(phase) });
const result = core.jsAnd(0x1234, 0x00FF);
std.debug.print("jsAnd result: {x}\n", .{ result });
}
Troubleshooting Common Build-Time Errors
- Missing imports (NS1034): The resolver in
packages/core/src/modules.tsonly follows imports inside the entry file’s graph. Absolute or out-of-tree paths trigger diagnosticNS1034. Keep all core files under the samesrc/directory or define a recognized path alias. - TypeScript subset violations: The checker enforces rules R1–R18 from
checker.ts. Unsupported features raise diagnostics defined inpackages/core/src/diagnostics.ts. Rewrite the offending code to match the supported subset. - Insufficient kernel capacities: If your model exceeds default limits, the runtime kernel (
rt.zig) may run out of frame or heap space. Pass explicit--frame-capand--heap-capvalues to the CLI. - Stale generated files: When the transpilation step is not a declared dependency, Zig can reuse a cached build that skips regeneration. Always link the executable step to the CLI step with
exe.step.dependOn(&nativeCore.step).
Summary
transpileFile()inpackages/core/src/transpile.tsdrives the entire pipeline, from parsing TypeScript to emitting a single Zig module.- The
native-coreCLI inpackages/core/src/cli.tsexposes this API for build scripts and requires--frame-capand--heap-capto size the runtime kernel. - In
build.zig, declare the CLI as ab.addSystemCommand()step and make your executable depend on it to enforce correct build ordering. - The emitted file contains a runtime kernel, JS-semantics helpers such as
jsAndandnumEq, and exported TypeScript symbols ready for use via@import.
Frequently Asked Questions
What TypeScript features are supported by the Native SDK transpiler?
The transpiler enforces a strict subset defined by rules R1–R18 in packages/core/src/checker.ts. Only features that pass this validation are emitted; everything else raises a diagnostic through packages/core/src/diagnostics.ts and halts compilation.
How do I configure memory limits for the generated Zig runtime?
Pass --frame-cap and --heap-cap flags to the native-core CLI. These values become comptime constants in rt.zig that size the frame arena and model heap for your transpiled core.
Why does my build fail with diagnostic NS1034?
Diagnostic NS1034 indicates that the module resolver in packages/core/src/modules.ts encountered an import outside the allowed source tree. Move the imported file into the core src/ directory or register a proper path alias to resolve it.
Can I transpile TypeScript to Zig without using the Zig build system?
Yes. You can invoke the CLI manually with node packages/core/src/cli.ts <entry.ts> -o <out.zig> and then import the resulting file into any Zig project using @import("out.zig"). Embedding the call inside build.zig is recommended to keep generated artifacts synchronized automatically.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →