编程 ZeroNative 深度拆解:当 Vercel 决定用 Zig 重新定义跨端应用——从 Tauri 的 Rust 阴影下杀出一条编译速度与二进制体积的新路

2026-08-09 08:15:34 +0800 CST views 7

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)
核心语言RustZig
内存安全编译期 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 上测试:

指标ElectronTauriZeroNative
安装包大小128 MB8.2 MB5.7 MB
启动时间3.2s0.8s0.6s
内存占用180 MB45 MB38 MB
CPU 占用(空闲)2.5%0.8%0.6%
编译时间(clean)N/A52s15s
编译时间(incremental)N/A9s3s

移动端支持对比

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 构建)
  • 使用 jarsignerapksigner

坑 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 的场景

  1. 需要同时支持桌面 + 移动:Day-one 移动端支持是最大卖点
  2. 编译速度敏感:频繁迭代的开发阶段,Zig 编译快 4-5 倍
  3. 包体积敏感:需要极致小的安装包(1-2MB)
  4. 前端团队为主:团队熟悉前端技术,不想深入 Rust
  5. 需要 C ABI 集成:需要与大量 C 库互操作

不适合 ZeroNative 的场景

  1. 生产环境稳定性要求极高:项目还在 v0.1.9,未经大规模验证
  2. 需要成熟的生态:Zig 生态远不如 Rust 成熟
  3. 内存安全是第一优先级:没有 borrow checker,需要手动管理
  4. 团队已熟悉 Tauri/Rust:迁移成本高,收益不明显
  5. 移动端需求不紧急: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 的真正竞争者,取决于:

  1. Vercel 是否持续投入
  2. 社区是否愿意尝试 Zig
  3. 移动端支持是否稳定可靠
  4. 是否有足够多的生产案例验证

现在下结论还太早。但有一点是确定的:前端工具链的语言多元化趋势已经不可逆转。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

推荐文章

php 统一接受回调的方案
2024-11-19 03:21:07 +0800 CST
html一个包含iPhoneX和MacBook模拟器
2024-11-19 08:03:47 +0800 CST
为什么要放弃UUID作为MySQL主键?
2024-11-18 23:33:07 +0800 CST
一个简单的打字机效果的实现
2024-11-19 04:47:27 +0800 CST
浅谈CSRF攻击
2024-11-18 09:45:14 +0800 CST
Vue3中的组件通信方式有哪些?
2024-11-17 04:17:57 +0800 CST
程序员茄子在线接单