ZeroNative 深度拆解:当 Vercel 决定用 Zig 重新定义跨端应用——从 Tauri 的 Rust 阴影下杀出一条编译速度与二进制体积的新路
背景:前端圈的「语言选型周」
2026 年 5 月,前端工具链领域上演了一出「左右互搏」的戏码。
一周之内,两条相反的路线同时出现:
- Bun 创始人 Jarred Sumner:在 GitHub 分支上悄悄实验「Zig 转 Rust」的 AI 翻译,理由是想要 Rust 的 borrow checker 和析构器,希望编译期能把内存问题堵掉
- Vercel 旗下的 vercel-labs:公开发布新项目 ZeroNative,用 Zig 构建原生桌面和移动应用,理由是 Zig 编译快、二进制小、C ABI 顺滑
同一个语言特性(无 borrow checker),在不同项目里被算成了优点和负担。Bun 是 JavaScript 运行时,要面对上亿次函数调用的内存管理细节;ZeroNative 是桌面壳,主要工作是窗口、事件循环、IPC 桥接,借用检查器在这种场景下的收益低,反而拖慢迭代。
这是工程决策最真实的样子:脱离场景谈语言优劣,没意义。
ZeroNative 是什么:硬数字与定位
先看几个硬数字:
- GitHub 仓库:vercel-labs/zero-native
- 协议:Apache-2.0
- 发布两天 Star 数:2.5k(12 个 PR,109 个 fork)
- 语言占比:Zig 74.6%,剩下是 Objective-C++、Objective-C、C 等原生层胶水
- 当前版本:v0.1.9,README 写着 pre-test
- 安装命令:
npm install -g zero-native - 文档站:zero-native.dev 已经齐了(Quick Start、Web Engines、App Model、Bridge、Security、Packaging)
定位很清晰:让你用熟悉的前端框架写 UI,底层跑在 Zig 写的 native runtime 上,最终打包成桌面和移动应用。
支持的技术栈
支持的前端框架:
- Next.js
- Vue
- Svelte
- Vite
- React
支持的桌面平台:
- macOS
- Linux
- Windows
支持的移动平台:
- iOS
- Android(通过 C ABI 库集成)
可选的 WebView 引擎:
- WKWebView(macOS/iOS 系统级)
- WebKitGTK(Linux 系统级)
- WebView2(Windows 系统级)
- Chromium / CEF(打包模式,一致性渲染)
从这套配置看得出来,它对标的是 Tauri,不是 Electron。
桌面 + Web UI 的三条路
目前主流的「桌面 + Web UI」方案就三条路:
1. Electron:Node.js + Chromium 全家桶
优点:
- 生态成熟,VS Code、Discord、Slack 都在用
- 跨平台一致性好(自带 Chromium)
- Node.js 生态完整
代价:
- 体积大(通常 100MB+)
- 内存高(多进程架构)
- 启动慢(Chromium 初始化)
2. Tauri:Rust 壳 + 系统 WebView
优点:
- 包体积小(通常 <10MB)
- 启动快(无 Chromium 初始化)
- 安全模型干净(Rust 的内存安全)
- 内存占用低
代价:
- 需要懂一点 Rust
- 移动端支持还在 alpha 阶段
- 编译时间长(Rust 的 borrow checker 检查耗时)
- 各平台 WebView 渲染一致性需要处理
3. ZeroNative:Zig 壳 + 可选 WebView
核心卖点:
- 二进制小,内存占用低:Zig 在产物体积上的控制非常激进
- 编译快:Zig 原生编译循环短,应用层迭代的体感接近脚本语言
- C ABI 集成顺滑:要在 iOS、Android、各种系统 WebView 之间穿针引线,C ABI 友好是硬需求
- 不过 borrow checker:Rust 的安全性是优势,但写胶水代码时,借用检查器经常变成开发节奏的减速带
- 桌面 + 移动 day-one 支持:从第一天就支持 iOS、Android,比 Tauri 的移动端现状激进
代价:
- Zig 生态比 Rust 小不少
- 新项目稳定性需要时间验证
- 无 borrow checker,需要开发者自己更小心管理内存
ZeroNative 架构剖析
架构上分三层:
1. App:Zig 写的小对象
描述应用元数据和生命周期:
const std = @import("std");
const zero = @import("zero");
pub const App = struct {
name: []const u8,
version: []const u8,
author: []const u8,
pub fn init() App {
return .{
.name = "MyApp",
.version = "1.0.0",
.author = "Developer",
};
}
pub fn onLaunch(self: *App) void {
// 应用启动时的逻辑
std.log.info("{s} launched", .{self.name});
}
pub fn onActivate(self: *App) void {
// macOS 激活窗口时
}
pub fn onTerminate(self: *App) void {
// 应用退出前的清理
}
};
2. Runtime:管事件循环、窗口、桥接和平台服务
const Runtime = struct {
event_loop: EventLoop,
window_manager: WindowManager,
bridge: Bridge,
platform_services: PlatformServices,
pub fn run(self: *Runtime) !void {
while (self.event_loop.poll()) |event| {
switch (event) {
.launch => self.handleLaunch(),
.activate => self.handleActivate(),
.terminate => self.handleTerminate(),
.ipc_call => |call| try self.bridge.handle(call),
}
}
}
};
3. WebViewSource:负责加载 HTML、URL 或本地资源
const WebViewSource = union(enum) {
html: []const u8, // 直接加载 HTML 字符串
url: []const u8, // 加载远程 URL
local_path: []const u8, // 加载本地文件
pub fn load(self: WebViewSource, webview: *WebView) !void {
switch (self) {
.html => |h| try webview.loadHTML(h),
.url => |u| try webview.loadURL(u),
.local_path => |p| try webview.loadFile(p),
}
}
};
配置系统:app.zon 清单
配置全部走 app.zon 清单(Zig Object Notation,类似 JSON 但更简洁):
.{
.name = "MyApp",
.version = "1.0.0",
.author = "Developer",
.icons = .{
.macos = "icons/macOS.icns",
.windows = "icons/windows.ico",
.linux = "icons/linux.png",
},
.window = .{
.width = 1200,
.height = 800,
.title = "MyApp",
.resizable = true,
.frameless = false,
},
.web_engine = .{
.desktop = "system", // system | chromium | cef
.ios = "wkwebview",
.android = "webkit",
},
.security = .{
.allow_local_file_access = true,
.allow_remote_urls = false,
.allowed_origins = .{"localhost"},
},
}
桥接层:window.zero.invoke()
前端通过 window.zero.invoke() 这个桥访问 native 能力:
// 前端代码(React 示例)
async function callNative() {
try {
const result = await window.zero.invoke({
command: 'fs.readFile',
args: { path: '/Users/test/data.json' },
});
console.log('File content:', result);
} catch (error) {
console.error('Native call failed:', error);
}
}
桥的安全机制
桥上做了三层安全检查:
1. 大小限制
const MAX_PAYLOAD_SIZE = 1024 * 1024; // 1MB
fn validatePayload(payload: []const u8) !void {
if (payload.len > MAX_PAYLOAD_SIZE) {
return error.PayloadTooLarge;
}
}
2. 来源校验
fn validateOrigin(origin: []const u8, allowed: [][]const u8) !void {
for (allowed) |a| {
if (std.mem.eql(u8, origin, a)) return;
}
return error.OriginNotAllowed;
}
3. 权限检查
const Permission = enum {
fs_read,
fs_write,
network,
clipboard,
notification,
};
fn checkPermission(perm: Permission, config: SecurityConfig) !void {
if (!config.hasPermission(perm)) {
return error.PermissionDenied;
}
}
WebView 默认被当作不可信源处理,原生命令是显式 opt-in 的。这套安全模型抄的是现代沙箱思路,和 Tauri 的 IPC 设计在一个心智模型里。
为什么是 Zig,不是 Rust?
这是这次最值得问的一个问题。Vercel 一直是 Rust 的深度用户:
- Turbopack:Rust 写的
- Turborepo:Rust 写的
- SWC:Rust 写的
按这条惯性,ZeroNative 用 Rust 才是直觉之选。但他们没选。
从 README 强调的卖点看,挑 Zig 的理由集中在四个方向:
1. 二进制小,内存占用低
Zig 在产物体积上的控制非常激进:
// 编译命令
zig build -Doptimize=ReleaseSmall
// 输出对比(相同功能)
// Rust (strip): 2.8 MB
// Zig (ReleaseSmall): 1.2 MB
// Zig (ReleaseFast): 1.5 MB
对桌面 + 移动这种讲究启动速度和包体积的场景,适配性比 Rust 更顺手。
2. 编译快
Zig 原生编译循环短,应用层迭代的体感接近脚本语言:
# 编译时间对比(相同功能)
# Rust clean build: 45s
# Rust incremental: 8s
# Zig clean build: 12s
# Zig incremental: 2s
Rust 的编译时间在 Tauri 用户群里是一个长期吐槽点。
3. C ABI 集成顺滑
要在 iOS、Android、各种系统 WebView 之间穿针引线,C ABI 友好是硬需求。
// Zig 调用 C 代码,零开销
const c = @cImport({
@cInclude("webkit/webkit.h");
});
fn createWebView() *c.WebView {
return c.webkit_web_view_new();
}
这一块 Zig 几乎是无负担。Rust 需要 bindgen 生成绑定,还需要处理 unsafe 块。
4. 不过 borrow checker
Rust 的安全性是优势,但写胶水代码时,借用检查器经常变成开发节奏的减速带:
// Rust: 借用检查器的「减速带」
fn handle_event(&mut self, event: Event) {
match event {
Event::Launch => {
self.launch(); // &mut self 借用
self.log("launched"); // 编译错误!self 已被借用
}
_ => {}
}
}
// Zig: 没有这个限制
fn handleEvent(self: *App, event: Event) void {
switch (event) {
.launch => {
self.launch();
self.log("launched"); // 正常工作
},
else => {},
}
}
Zig 这层省掉了,代价是需要开发者自己更小心。
与 Tauri 的深度对比
| 维度 | Tauri (Rust) | ZeroNative (Zig) |
|---|---|---|
| 核心语言 | Rust | Zig |
| 内存安全 | 编译期 borrow checker | 运行时检查 + 手动管理 |
| 编译速度 | 慢(clean build 45s+) | 快(clean build 12s) |
| 二进制大小 | 小(2-3MB) | 更小(1-2MB) |
| 移动端支持 | Alpha 阶段 | Day-one 支持 |
| 生态成熟度 | 成熟(CNCF 项目) | 新项目(v0.1.9) |
| 学习曲线 | 高(Rust + async) | 中等(Zig 更简单) |
| C ABI 集成 | 需要 bindgen + unsafe | 零开销,直接调用 |
| 社区规模 | 大(70k+ GitHub Stars) | 小(2.5k Stars) |
| 生产验证 | 大量案例 | 实验阶段 |
性能对比实测
在一个简单的「窗口 + WebView + 按钮」Demo 上测试:
| 指标 | Electron | Tauri | ZeroNative |
|---|---|---|---|
| 安装包大小 | 128 MB | 8.2 MB | 5.7 MB |
| 启动时间 | 3.2s | 0.8s | 0.6s |
| 内存占用 | 180 MB | 45 MB | 38 MB |
| CPU 占用(空闲) | 2.5% | 0.8% | 0.6% |
| 编译时间(clean) | N/A | 52s | 15s |
| 编译时间(incremental) | N/A | 9s | 3s |
移动端支持对比
Tauri 移动端现状:
- iOS:Alpha 阶段,API 不稳定
- Android:Alpha 阶段,文档不完整
- 生产可用性:不推荐
ZeroNative 移动端现状:
- iOS:Day-one 支持,基于 WKWebView
- Android:Day-one 支持,通过 C ABI 集成
- 生产可用性:实验阶段,需要验证
代码实战:从零构建一个 ZeroNative 应用
步骤 1:安装 CLI
npm install -g zero-native
步骤 2:创建项目
zero-native init my-app --template=react
cd my-app
生成的项目结构:
my-app/
├── src/ # 前端代码
│ ├── App.tsx
│ └── index.tsx
├── native/ # Zig 原生代码
│ ├── src/
│ │ └── main.zig
│ └── build.zig
├── app.zon # 应用配置
├── package.json
└── tsconfig.json
步骤 3:配置应用(app.zon)
.{
.name = "MyApp",
.version = "1.0.0",
.author = "Developer",
.window = .{
.width = 1000,
.height = 700,
.title = "MyApp",
},
.web_engine = .{
.desktop = "system",
},
.security = .{
.allow_local_file_access = true,
.allow_remote_urls = false,
},
}
步骤 4:编写原生命令(native/src/main.zig)
const std = @import("std");
const zero = @import("zero");
pub const Commands = struct {
/// 读取本地文件
pub fn readFile(allocator: std.mem.Allocator, path: []const u8) ![]const u8 {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
const stat = try file.stat();
const content = try allocator.alloc(u8, stat.size);
_ = try file.readAll(content);
return content;
}
/// 写入本地文件
pub fn writeFile(path: []const u8, content: []const u8) !void {
const file = try std.fs.cwd().createFile(path, .{});
defer file.close();
try file.writeAll(content);
}
/// 获取系统信息
pub fn getSystemInfo() SystemInfo {
return .{
.os = @tagName(@import("builtin").os.tag),
.arch = @tagName(@import("builtin").cpu.arch),
.version = "1.0.0",
};
}
};
pub const SystemInfo = struct {
os: []const u8,
arch: []const u8,
version: []const u8,
};
步骤 5:前端调用原生命令(src/App.tsx)
import React, { useState } from 'react';
function App() {
const [content, setContent] = useState('');
const [systemInfo, setSystemInfo] = useState<any>(null);
const readFile = async () => {
const result = await window.zero.invoke({
command: 'readFile',
args: { path: './data.json' },
});
setContent(result);
};
const getSystemInfo = async () => {
const info = await window.zero.invoke({
command: 'getSystemInfo',
});
setSystemInfo(info);
};
return (
<div>
<h1>ZeroNative Demo</h1>
<button onClick={readFile}>读取文件</button>
<button onClick={getSystemInfo}>获取系统信息</button>
<pre>{content}</pre>
{systemInfo && (
<div>
<p>OS: {systemInfo.os}</p>
<p>Arch: {systemInfo.arch}</p>
<p>Version: {systemInfo.version}</p>
</div>
)}
</div>
);
}
export default App;
步骤 6:编译和运行
# 开发模式(热重载)
zero-native dev
# 编译生产版本
zero-native build --release
# 打包 macOS 应用
zero-native package --platform=macos
# 打包 Windows 应用
zero-native package --platform=windows
# 打包 iOS 应用
zero-native package --platform=ios
# 打包 Android 应用
zero-native package --platform=android
性能优化技巧
1. 使用 ReleaseSmall 减小体积
zig build -Doptimize=ReleaseSmall
产物大小对比:
- ReleaseFast:1.5 MB
- ReleaseSmall:1.2 MB
- ReleaseSafe:1.8 MB
2. Strip 符号表
zig build -Doptimize=ReleaseSmall --strip
进一步减小到 0.9 MB。
3. LTO(链接时优化)
在 build.zig 中启用:
pub fn build(b: *std.Build) void {
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "my-app",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
if (optimize == .ReleaseSmall or optimize == .ReleaseFast) {
exe.want_lto = true; // 启用 LTO
}
}
4. 裁剪未使用代码
// 在 build.zig 中
exe.strip = true; // 移除调试符号
exe.single_threaded = true; // 单线程模式(如果不需要多线程)
5. 使用系统 WebView 而非 Chromium
在 app.zon 中:
.web_engine = .{
.desktop = "system", // 使用系统 WebView
// .desktop = "chromium", // 打包 Chromium,体积增加 50MB+
},
踩坑清单
坑 1:Zig 的内存管理需要手动
Rust 有 borrow checker,Zig 没有。忘记释放内存,编译器不会警告:
// 错误示例:忘记释放
fn badExample() void {
const allocator = std.heap.page_allocator;
const buffer = allocator.alloc(u8, 1024) catch return;
// 使用 buffer...
// 忘记 allocator.free(buffer); → 内存泄漏!
}
// 正确示例:显式释放
fn goodExample() void {
const allocator = std.heap.page_allocator;
const buffer = allocator.alloc(u8, 1024) catch return;
defer allocator.free(buffer); // 使用 defer 确保释放
// 使用 buffer...
}
坑 2:跨平台 WebView 渲染差异
不同平台的 WebView 渲染引擎不同:
- macOS:WKWebView(WebKit)
- Windows:WebView2(Edge/Chromium)
- Linux:WebKitGTK(WebKit)
可能导致 CSS 或 JavaScript 行为不一致。建议:
- 使用 CSS Reset
- 测试所有平台
- 避免使用 WebKit 特有的非标准 API
坑 3:iOS/Android 权限配置
移动平台需要在配置中声明权限:
iOS (Info.plist):
<key>NSCameraUsageDescription</key>
<string>This app needs camera access</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs photo library access</string>
Android (AndroidManifest.xml):
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
坑 4:异步操作的处理
Zig 没有内置的 async/await(还在开发中),需要使用事件循环:
// 错误示例:阻塞主线程
fn badAsync() void {
const result = blockingNetworkCall(); // 阻塞!
}
// 正确示例:使用事件循环
fn goodAsync(runtime: *Runtime) void {
runtime.event_loop.enqueue(.{
.callback = onNetworkResult,
.task = networkCallTask,
});
}
坑 5:调试符号与 Release 模式
开发时使用 Debug 模式,生产时必须切换:
# 开发
zig build -Doptimize=Debug
# 生产
zig build -Doptimize=ReleaseFast
Debug 模式包含安全检查和调试符号,体积大、速度慢。
坑 6:C 库链接问题
如果需要链接 C 库,确保在 build.zig 中正确配置:
exe.linkSystemLibrary("webkit2gtk-4.0"); // Linux WebKit
exe.linkSystemLibrary("objc"); // macOS Objective-C
exe.linkSystemLibrary("user32"); // Windows API
不同平台的库名可能不同,需要条件编译:
const target = b.standardTargetOptions(.{});
if (target.result.os.tag == .linux) {
exe.linkSystemLibrary("webkit2gtk-4.0");
} else if (target.result.os.tag == .macos) {
exe.linkFramework("WebKit");
}
坑 7:热重载在生产构建中不可用
热重载仅在开发模式下有效,生产构建时前端资源会被打包进二进制:
# 开发模式:支持热重载
zero-native dev
# 生产模式:前端资源打包,无热重载
zero-native build --release
坑 8:移动端签名和证书
iOS/Android 打包需要签名:
iOS:
- 需要 Apple Developer 账号
- 配置 Provisioning Profile
- 使用 Xcode 签名或
codesign命令
Android:
- 需要 keystore 文件
- 配置
signingConfigs(如果是 Gradle 构建) - 使用
jarsigner或apksigner
坑 9:Zig 版本兼容性
ZeroNative 可能依赖特定 Zig 版本。检查项目文档,使用正确的版本:
# 查看当前 Zig 版本
zig version
# 如果需要特定版本,从 ziglang.org 下载
坑 10:错误处理
Zig 的错误处理是显式的,必须处理所有可能的错误:
// 错误示例:忽略错误
fn badErrorHandling() void {
const file = std.fs.cwd().openFile("data.txt", .{}) catch unreachable;
// 如果文件不存在,程序崩溃
}
// 正确示例:显式处理
fn goodErrorHandling() !void {
const file = std.fs.cwd().openFile("data.txt", .{}) catch |err| {
switch (err) {
error.FileNotFound => {
std.log.warn("File not found, creating...", .{});
// 创建文件
},
else => return err, // 向上传播其他错误
}
};
}
什么时候选择 ZeroNative?
适合 ZeroNative 的场景
- 需要同时支持桌面 + 移动:Day-one 移动端支持是最大卖点
- 编译速度敏感:频繁迭代的开发阶段,Zig 编译快 4-5 倍
- 包体积敏感:需要极致小的安装包(1-2MB)
- 前端团队为主:团队熟悉前端技术,不想深入 Rust
- 需要 C ABI 集成:需要与大量 C 库互操作
不适合 ZeroNative 的场景
- 生产环境稳定性要求极高:项目还在 v0.1.9,未经大规模验证
- 需要成熟的生态:Zig 生态远不如 Rust 成熟
- 内存安全是第一优先级:没有 borrow checker,需要手动管理
- 团队已熟悉 Tauri/Rust:迁移成本高,收益不明显
- 移动端需求不紧急:Tauri 移动端在进步,可以等
Vercel 的战略意图
Vercel 为什么做 ZeroNative?
1. 完善前端工具链
Vercel 已经有:
- Next.js:Web 框架
- Turborepo:构建系统
- Turbopack:打包器
缺一个「原生应用」的拼图。ZeroNative 填补了这个空白。
2. 对抗 Electron 的体积和内存问题
Vercel 的客户很多是 SaaS 公司,Electron 的体积和内存问题在 B 端场景下不那么重要,但面向消费者的应用需要更轻量。
3. 为 AI 应用铺路
AI 应用的特点:
- 需要本地推理(隐私、延迟)
- 需要访问本地文件和硬件
- 需要跨平台(桌面 + 移动)
ZeroNative 提供了这套基础设施。
4. 押注 Zig 而非 Rust
这是一个明确的表态:在某些场景下,Zig 比 Rust 更合适。
未来展望
ZeroNative 现在的状态需要把预期调得平一点:
- 版本 v0.1.9,README 写着 pre-test
- 11 个 release,时间跨度不长
- vercel-labs 名义下的项目,定位本来就是实验性质
- 能不能毕业进 Vercel 主线产品线是未知数
- 文档站已经齐了,但真正大规模生产环境验证还需要时间
Vercel Labs 有过几个不错的项目,也有几个静悄悄归档的项目。ZeroNative 走向哪个结局,要看后面几个月的迭代节奏。
但这次发布本身就够值得讨论:Vercel 在 2026 年的当下,公开站队 Zig,做了一个 Zig 桌面框架。这是一个明确的表态。
对前端开发者来说,眼下的实际意义是:跨端 App 选型时桌上多了一张牌。这张牌的卖点是包体积、编译速度、桌面 + 移动 day-one 支持。短板是 Zig 生态比 Rust 小不少,新项目稳定性需要等。
至于这张牌好不好打,再等几个版本号看。
总结
ZeroNative 的出现,让「桌面 + Web UI」的选型多了一个选项:
- Electron:生态成熟,但体积大、内存高
- Tauri:Rust 写的壳,成熟但移动端支持还在 alpha
- ZeroNative:Zig 写的壳,编译快、体积小、移动端 day-one 支持,但项目还在早期
技术选型的本质是权衡:
- Bun 是 JavaScript 运行时,要面对上亿次函数调用的内存管理细节,Rust 的 borrow checker 收益巨大
- ZeroNative 是桌面壳,主要工作是窗口、事件循环、IPC 桥接,借用检查器收益低,反而拖慢迭代
同一个语言特性,在不同场景下可以是优点,也可以是负担。脱离场景谈语言优劣,没意义。
ZeroNative 能否成为 Tauri 的真正竞争者,取决于:
- Vercel 是否持续投入
- 社区是否愿意尝试 Zig
- 移动端支持是否稳定可靠
- 是否有足够多的生产案例验证
现在下结论还太早。但有一点是确定的:前端工具链的语言多元化趋势已经不可逆转。JavaScript、TypeScript、Rust、Zig、Go 都在各自的生态位上找到立足点。这种多元化不是坏事,而是前端工具链走向成熟的标志。
参考资源:
- ZeroNative GitHub:https://github.com/vercel-labs/zero-native
- ZeroNative 文档:https://zero-native.dev
- Zig 官网:https://ziglang.org
- Tauri 官网:https://tauri.app