- 桌面应用
- 跨平台
【免费下载链接】native
Toolkit for building native desktop apps
Native SDK(ze/native 仓库,一个用于构建桌面原生应用的工具包)将 Zig 编写的原生桌面壳与 Web 技术栈前端结合。本文以仓库中的 next 示例 为核心,从零讲解如何用 Next.js 编写界面、用 Zig 编写原生壳,并通过zig build run/zig build dev一键完成依赖安装、前端构建和原生窗口启动;读完你将掌握 app.zon 清单配置、NATIVE_SDK_FRONTEND_URL环境变量切换开发/生产资源、以及native dev托管式开发服务器的完整调用链与底层原理。
示例全景:一个"超级基础"的 Next.js 桌面应用
examples/next是 Native SDK 官方示例中最简单的一个:前端使用 Next.js(静态导出模式),原生壳使用 Zig。整个工程只有两类核心文件:
examples/next/ ├── app.zon # 应用清单(manifest):前端、窗口、安全策略 ├── build.zig # 自定义构建:前端安装/构建/开发服务器步骤 + 平台链接 ├── build.zig.zon # 构建依赖声明 ├── frontend/ # Next.js 前端 │ ├── package.json │ ├── next.config.js # output: "export" 静态导出 │ ├── tsconfig.json │ └── app/ # App Router:layout.tsx / page.tsx / globals.css └── src/ ├── main.zig # 应用入口:App 定义 + WebViewSource 解析 └── runner.zig # 平台运行器:manifest 合并、运行时初始化、窗口状态恢复它演示了 Native SDK 最典型的形态:前端产物(frontend/out)直接作为应用资源打进原生壳,壳内用系统 WebView 渲染。前端只有一页(frontend/app/page.tsx),通过检测window.zero是否存在来显示 native bridge 是否可用——这正是 WebView 内 JS 桥接的最小可验证用例:
"use client"; import { useEffect, useState } from "react"; export default function Home() { const [bridge, setBridge] = useState("checking..."); useEffect(() => { setBridge((window as any).zero ? "available" : "not enabled"); }, []); return ( <main> <p className="eyebrow">Native SDK + Next.js</p> <h1>Next</h1> <p className="lede">A Next.js frontend running inside the system WebView.</p> <div className="card"> <span>Native bridge</span> <strong>{bridge}</strong> </div> </main> ); }快速运行
生产模式:zig build run
zig build run这一步由 build.zig 编排,完整链条为:
npm install --prefix frontend安装前端依赖(frontend-installstep);npm --prefix frontend run build构建 Next.js 前端,产出静态导出到frontend/out(frontend-buildstep);- 编译 Zig 原生壳可执行文件
next; - 运行可执行文件,以
frontend/out为资源目录打开桌面窗口。
关键代码在 build.zig:frontend_build.step.dependOn(&frontend_install.step)保证先装依赖再构建,run.step.dependOn(&frontend_build.step)保证先构建前端再启动应用。窗口标题为 "Next Example",尺寸 720×480(见 app.zon)。
开发模式:zig build dev
zig build dev该模式会:
- 根据
app.zon中的.frontend.dev配置启动 Next.js 开发服务器(npm --prefix frontend run dev,即next dev); - 轮询等待
http://127.0.0.1:3000/就绪(dev.zig 的waitUntilReady以 100ms 间隔探测,超时上限为timeout_ms); - 通过环境变量
NATIVE_SDK_FRONTEND_URL启动原生壳,让 WebView 指向开发服务器地址,实现热更新。
前提:
examples/next/app.zon中.platforms声明为{ "macos", "linux" };runner.zig 会按构建目标自动分派到 macOS / Linux / Windows / null 平台实现。当前仓库内的示例默认面向 macOS 与 Linux 桌面环境。
app.zon 清单:前端与窗口的声明式配置
app.zon 是整个应用的核心声明文件,完整内容如下:
.{ .id = "dev.native_sdk.next-example", .name = "next-example", .display_name = "Next Example", .version = "0.1.0", .platforms = .{ "macos", "linux" }, .permissions = .{}, .capabilities = .{ "webview" }, .frontend = .{ .dist = "frontend/out", .entry = "index.html", .spa_fallback = true, .dev = .{ .url = "http://127.0.0.1:3000/", .command = .{ "npm", "--prefix", "frontend", "run", "dev" }, .ready_path = "/", .timeout_ms = 30000, }, }, .security = .{ .navigation = .{ .allowed_origins = .{ "zero://app", "zero://inline", "http://127.0.0.1:3000" }, .external_links = .{ .action = "deny" }, }, }, .web_engine = "system", .cef = .{ .dir = "third_party/cef/macos", .auto_install = false }, .windows = .{ .{ .label = "main", .title = "Next Example", .width = 720, .height = 480, .restore_state = true }, }, }字段说明(对照 app_manifest 类型定义 与 校验实现):
| 字段 | 取值 | 含义 |
|---|---|---|
id/name/display_name/version | 字符串 | 应用标识与展示信息;display_name、version在开发运行时直接读清单,与打包后 Info.plist 一致(见 runner.zig) |
platforms | { "macos", "linux" } | 支持的桌面平台 |
capabilities | { "webview" } | 声明需要 webview 能力;runner.zig 据此决定是否生成 Reload 等 web 菜单项 |
frontend.dist | "frontend/out" | 前端静态产物目录 |
frontend.entry | "index.html" | WebView 加载的入口 HTML |
frontend.spa_fallback | true | 单页应用路由回退(默认即true) |
frontend.dev.url | "http://127.0.0.1:3000/" | 开发服务器地址 |
frontend.dev.command | { "npm", "--prefix", "frontend", "run", "dev" } | 开发服务器启动命令(必须非空数组) |
frontend.dev.ready_path | "/" | 就绪探测路径,默认"/" |
frontend.dev.timeout_ms | 30000 | 就绪等待超时(毫秒),默认 30 秒;校验器要求非 0(validation.zig) |
security.navigation.allowed_origins | { "zero://app", "zero://inline", "http://127.0.0.1:3000" } | 允许导航的来源;开发时把 3000 端口加入白名单 |
security.navigation.external_links | { .action = "deny" } | 外部链接一律拒绝打开 |
web_engine | "system" | 使用系统 WebView(macOS WebKit / Linux WebKitGTK);可选"chromium"(需 CEF,构建时可用-Dweb-engine=chromium覆盖) |
cef | { .dir = ..., .auto_install = false } | CEF 目录与自动安装开关(仅 chromium 引擎使用) |
windows | 数组 | 窗口定义;本例一个main窗口,720×480,restore_state: true表示记住上次窗口位置 |
校验规则要点(validation.zig):
dist、entry必须是安全的相对路径,../dist、/index.html这类越界/绝对路径会被拒绝(tests.zig 中有专门的反例测试);dev.url必须是http://或https://,ws://会被拒绝;dev配置必须同时提供url和command,缺失任一字段报MissingRequiredField;timeout_ms为 0 时报InvalidTimeout;ready_path需通过validateReadyPath。
源码剖析:Zig 原生壳如何加载前端
main.zig:应用入口与资源来源解析
main.zig 定义App,其source函数是前端加载的核心:
fn source(context: *anyopaque) anyerror!native_sdk.WebViewSource { const self: *@This() = @ptrCast(@alignCast(context)); return native_sdk.frontend.sourceFromEnv(self.env_map, .{ .dist = "frontend/out", .entry = "index.html", }); }sourceFromEnv的底层逻辑(src/frontend/root.zig):
pub fn sourceFromEnv(env_map: *std.process.Environ.Map, config: Config) platform.WebViewSource { if (env_map.get(config.dev_url_env)) |url| { if (url.len > 0) return platform.WebViewSource.url(url); } return productionSource(config); } pub fn productionSource(config: Config) platform.WebViewSource { return platform.WebViewSource.assets(.{ .root_path = config.dist, .entry = config.entry, .origin = config.origin, .spa_fallback = config.spa_fallback, }); }即:如果环境变量NATIVE_SDK_FRONTEND_URL(默认名,见Config.dev_url_env)非空,则 WebView 加载该 URL;否则加载本地静态资源目录frontend/out。这正是zig build dev与zig build run两种模式切换的原理——dev 模式注入环境变量指向开发服务器,生产模式退化为本地资源。
配置默认值(frontend/root.zig):dist = "dist"、entry = "index.html"、origin = "zero://app"、spa_fallback = true。单元测试验证了两种路径的行为:
- 设置了
NATIVE_SDK_FRONTEND_URL=http://127.0.0.1:5173/时返回url类型的WebViewSource; - 未设置时返回
assets类型,且root_path、entry与传入配置一致。
main.zig还通过native_sdk.debug.capturePanic挂接了 panic 捕获,并在启动时声明开发来源白名单dev_origins(与 app.zon 的allowed_origins保持一致)。
runner.zig:清单合并与运行时装配
runner.zig 是本示例"自带"的平台运行器(RunOptions+runWithOptions),它做的事包括:
- 把 app.zon 的显示名/版本/描述透传给
AppInfo,保证开发运行与打包产物的 OS 身份一致(runner.zig); - 按平台分派:
runWithOptions根据build_options.platform选择 macOS / Linux / Windows / null 实现(runner.zig); - 窗口状态恢复:
prepareStateStore读取窗口状态存储,restore_state: true的窗口会恢复上次的位置与尺寸(runner.zig); - 运行时初始化:
Runtime体积达数十 MB,特意用堆分配(page_allocator.create),避免主线程栈溢出(runner.zig); - 日志与 trace 装配:通过
StdoutTraceSink与可选的文件 sink 输出 trace 记录。
build.zig:自定义构建图
与自动生成构建图的示例不同,examples/next的 build.zig 完全自持(文件头部注释明确说明:它自己添加前端安装/构建/开发服务器步骤和手工接线的 WebView 壳)。值得关注的构建选项:
| 选项 | 默认值 | 说明 |
|---|---|---|
-Dplatform=auto\|null\|macos\|linux\|windows | auto | 桌面后端选择,auto按目标 OS 推断 |
-Dtrace=off\|events\|runtime\|all | events | trace 输出级别(runner.zig 实现过滤逻辑) |
-Ddebug-overlay=true\|false | false | 启动时打印调试叠加信息 |
-Dautomation=true\|false | false | 启用 Native SDK 自动化产物 |
-Djs-bridge=true\|false | false | 启用可选 JS 桥接桩 |
-Dweb-engine=system\|chromium | 读取 app.zon | 覆盖 web 引擎;chromium 目前要求-Dplatform=macos |
-Dcef-dir/-Dcef-auto-install | 读取 app.zon | 覆盖 CEF 目录/自动安装 |
-Dpackage-target=macos\|windows\|linux | macos | 打包目标 |
-Dnative-sdk-path | ../.. | Native SDK 框架路径(见下文"仓库外使用") |
构建图还包含zig build test(运行main.zig中的单元测试)与zig build package(调用native package打包,产出zig-out/package/next-0.1.0-<target>-<optimize>,macOS 后缀.app)。此外 build.zig 对 x86_64 Debug 构建强制 LLVM 后端(use_llvm),这是针对 Zig 0.16.0 自托管后端在 SysV 调用约定上的已知问题打的补丁(build.zig)。
开发服务器就绪探测与进程生命周期
zig build dev的执行主体是native dev --manifest app.zon --binary <exe>(build.zig),底层实现在 src/tooling/dev.zig:
- 从清单读取
frontend.dev配置(缺frontend或dev配置直接报错MissingFrontend/MissingDevConfig); - 解析
dev.url,把localhost归一化为127.0.0.1; - 以 100ms 为间隔轮询
ready_path(默认"/"),直到 HTTP 响应或超过timeout_ms(dev.zig); - 就绪后启动二进制,并注入
NATIVE_SDK_FRONTEND_URL=<dev.url>环境变量(配合 frontend/root.zig 的sourceFromEnv生效)。
进程管理上,开发服务器与应用分别放入独立的进程组(process group),一旦 CLI 收到信号,会级联杀掉整棵进程树,避免 dev server 残留(dev.zig 注释说明)。
在仓库外独立使用:覆盖 Native SDK 路径
示例通过相对路径../../引用 Native SDK(即本仓库根目录)。将examples/next拷贝到仓库外独立使用时,需要覆盖框架路径:
zig build run -Dnative-sdk-path=/path/to/native-sdk-Dnative-sdk-path是 build.zig 声明的选项,默认值../..(build.zig)。它会重定向src/root.zig、src/primitives/*、src/platform/*等 SDK 源码模块的导入路径(nativeSdkModule/externalModule实现),适用于 SDK 与示例分属不同目录的场景。
把 Next.js 前端换成其他框架
examples/next不是仓库中唯一的 Web 前端示例——react、svelte、vue 采用完全相同的结构:Zig 原生壳 + 各自框架的前端目录,只是dev.url端口不同(React/Svelte/Vue 用http://127.0.0.1:5173/)。如果你要接入自己的前端,只需对齐三处:
app.zon的frontend.dist(生产产物目录)、frontend.dev.url/frontend.dev.command;main.zig中sourceFromEnv的dist/entry参数;- 前端构建脚本产出静态可部署资源(Next 需设置
output: "export",见 frontend/next.config.js)。
对 Next.js 而言,"静态导出"是进入桌面壳的前提:next build在output: "export"下产出纯静态 HTML/JS/CSS 到out目录,Native SDK 才能以本地资源方式加载,无需 Node 运行时。这正是frontend/out与spa_fallback: true组合的意义所在。
小结
examples/next展示了 Native SDK 最精简的 Web 前端桌面应用范式:app.zon 声明式描述前端资源与窗口/安全策略,Zig 壳通过sourceFromEnv在"开发 URL"与"本地静态资源"之间自动切换,zig build run与zig build dev分别覆盖生产与热开发两种场景。示例自身的 main.zig 还附带单元测试验证生产资源指向(WebViewSourceKind.assets+root_path == "frontend/out"),可作为后续自定义应用时的断言样板。
更完整的桌面后端、打包与发布流程可继续阅读仓库的 README、examples 目录 以及 SDK 源码 src/frontend/root.zig、src/tooling/dev.zig。
- 桌面应用
- 跨平台
【免费下载链接】native
Toolkit for building native desktop apps
相关推荐
用 Vue 与 Zig 构建原生桌面应用:Native SDK Vue 示例完整实战指南
用 Vue 与 Zig 构建原生桌面应用:Native SDK Vue 示例完整实战指南 Native SDK( Toolkit for building na
桌面应用跨平台Native SDK 实战:用 Zig 与 app.zon 构建 native-first 桌面应用外壳(native-shell 示例深度解析)
Native SDK 实战:用 Zig 与 app.zon 构建 native first 桌面应用外壳(native shell 示例深度解析) 本篇技术指南
桌面应用跨平台终极PDF翻译神器:6倍速智能翻译,完美保持原格式
终极PDF翻译神器:6倍速智能翻译,完美保持原格式 PDF翻译神器 PolyglotPDF是一款革命性的多语言电子书处理工具,专为解决技术文档、学术论文和电子书
桌面应用跨平台
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考