Deno 架构详解:从 CLI 到 V8 的五层技术栈与 Ops 扩展机制全解析
本文基于 Deno 仓库官方文档 doc/architecture.md 展开,系统讲解 Deno 由上至下的五层架构(cli → runtime → ext → libs → V8+Tokio)如何分层协作,并深入解读贯穿各层的 Ops 扩展机制、权限模型与 Worker 隔离模型。读完后,你将能够准确定位任意一个功能(从 CLI 子命令到原生能力)在代码库中的实现位置,理解"往 JS 里加一个原生 API"的完整链路,并知道做改动时应从哪个文件入手。
分层总览:每层只依赖其下的层
Deno 被构建为一个分层技术栈(stack of layers),每一层只依赖它下面的层。这一约束保证了系统的可测试性,也让底层能力可以脱离 deno 二进制被独立复用。官方给出的层次结构如下(自上而下):
+-----------------------------------------------------------+
| cli/ the `deno` binary: subcommands, tooling |
+-----------------------------------------------------------+
| runtime/ deno_runtime: assembles the JS runtime |
+-----------------------------------------------------------+
| ext/* extensions: native capabilities for JS |
+-----------------------------------------------------------+
| libs/* deno_core + supporting crates (V8 bridge) |
+-----------------------------------------------------------+
| V8 + Tokio JavaScript engine and async runtime |
+-----------------------------------------------------------+
这个分层对应到仓库目录一目了然:顶层 cli/ 是用户直接交互的二进制;runtime/ 组装 JS 运行时;ext/ 提供原生能力;libs/ 是 Rust 与 V8 之间的桥接层;最底层则是 V8 引擎与 Tokio 异步运行时。doc/codebase-map.md 提供了更细粒度的逐目录说明,doc/testing.md 则给出了各层的测试方式。
CLI 层(cli/):用户直接触碰的一切
cli/ 目录下的 deno crate 承载了用户能直接触碰的所有东西:命令行参数解析、全部子命令(run、test、fmt、lint、compile、bundle、install、publish 等)、包管理工具、LSP 语言服务器,以及把模块解析与运行时连接起来的模块加载器。
关键入口文件:
- cli/main.rs — 进程入口与命令路由。该文件刻意保持极简,仅调用
deno::main(),真正的入口逻辑放在 cli/lib.rs 中。这样做的原因是官方注释中说明的:"We have a lib.rs and main.rs in order to be able to run tests without building a binary on the CI"——把逻辑放进库目标,CI 上跑测试就无需构建完整的二进制。 - cli/args/flags.rs — 完整的
clap标志与子命令定义。新增一个 flag 或子命令,从这里开始。 - cli/tools/ — 每个子命令一个模块:简单的如 cli/tools/fmt.rs,复杂的如 cli/tools/test/ 目录,以及
compile.rs、bundle/、lint/、pm/(包管理)、publish/、repl/、serve.rs等。 - cli/module_loader.rs — 解析并加载模块,把 resolver 与模块图(module graph)桥接到运行时。
CLI 层是有意做重的:它引入了 TypeScript 类型检查、npm 与 JSR 解析、lockfile、bundler 等重资产。作为分层纪律的一部分,底层 crate 严禁反向依赖 CLI 层。
从 cli/lib.rs 的 main() 实现还能看到 CLI 启动阶段的实际细节:进程启动后依次完成 panic hook 安装、日志初始化、文件描述符上限提升(util::unix::raise_fd_limit())、权限提示回调注册(deno_permissions::prompter::set_prompt_callbacks)、rustls 的 aws_lc_rs 默认 provider 安装,然后才进入参数处理。一个值得一提的实现细节是:当二进制通过名为 node 的符号链接被调用时,node_compat_shim::maybe_rewrite_node_arg0 会在启动最早期把 Node.js 风格的 CLI 参数翻译成 Deno 参数——这就是 Deno 能兼容 node script.js 调用方式的基础(见 cli/node_compat_shim.rs)。
运行时层(runtime/):组装"Den the runtime"
runtime/ 目录下的 deno_runtime crate 负责把 deno_core 加上一组精选扩展(extensions)组装成一个可用的 JavaScript 运行时。它是嵌入者(embedder)想要"Deno 这个运行时"但不想要"Deno 这个 CLI"时所用的那一层。
关键文件:
- runtime/worker.rs — 构建主 worker:一个 V8 isolate、op 集合(op set)以及启动(bootstrap)序列。从源码结构看,该文件大量引用
deno_web等扩展(如deno_web::deno_web::lazy_init()、CSSStyleSheet 创建等),正是"把各扩展装配进运行时"的集中体现。 - runtime/web_worker.rs — Web Worker 变体。
- runtime/permissions/ — 权限模型,为每一个敏感 op(read、write、net、env、run、ffi、sys)设卡。权限检查在 Rust 侧的 op 边界完成,绝不在 JavaScript 中完成——这是 Deno 安全模型的核心设计。
扩展层(ext/):平台能力的真正所在地
ext/ 下的每个目录都是一个自包含的扩展:一个 Rust crate,定义了 ops(可从 JS 调用的原生函数),外加在其之上暴露更高层 API 的 JavaScript。平台能力实际就"住"在这里。按用途分组:
- Web 平台:
ext/web、ext/fetch、ext/url、ext/crypto、ext/console、ext/webidl、ext/websocket、ext/webgpu、ext/canvas(以及ext/image、ext/broadcast_channel、ext/webstorage等)。 - 系统访问:
ext/fs、ext/net、ext/io、ext/os、ext/process、ext/signals、ext/tls。 - Deno 特有 API:
ext/kv、ext/cron、ext/cache、ext/ffi、ext/napi、ext/bundle。 - Node 兼容:
ext/node(大部分node:*内建模块,含 Rust ops 与 JavaScript polyfills)、ext/node_crypto、ext/node_sqlite。
一个扩展的典型形态
每个扩展遵循三步结构:
- Rust 侧用
#[op2]宏标注的函数执行特权操作,需要时携带权限检查; - 一组带数字前缀的 JavaScript 模块(
00_*.js、01_*.js……,前缀决定加载顺序),构建公开 API 并通过Deno.core.ops调用 op; - 在 runtime/worker.rs(以及 CLI 的 snapshot 构建)中注册该扩展,使其成为组装后运行时的一部分。
以文件系统扩展为例,这条链路在源码中可以完整印证:
- Rust 侧:ext/fs/ops.rs 中密集使用
#[op2]、#[op2(fast, stack_trace)]等宏定义 op; - JS 侧:ext/fs/30_fs.js 通过
const { ... } = core.ops;解构拿到全部 op 的 JS 绑定,再在其上构建Deno.readTextFile等公开 API; - 注册侧:扩展在 libs/core 中通过
deno_core::extension!宏(见 libs/core/benches/ops/async.rs 等处的用法)声明为扩展单元,最终由runtime/worker.rs装配。
数字前缀(00_、01_、20_、30_……)不仅是命名惯例,更是模块加载顺序的控制机制:例如 ext/fetch/ 下的 20_headers.js → 21_formdata.js → 22_body.js → 23_request.js → 23_response.js → 26_fetch.js,反映了 Headers、FormData、Body 到 Request/Response 的依赖顺序。
官方给出的开发准则很明确:新增原生功能时,在对应的 ext/<name>/ crate 中添加 op,不要去 runtime 或 CLI 层里找地方塞。
核心层(libs/):Rust 与 V8 之间的桥
libs/ 目录容纳 deno_core 及从原独立 deno_core 仓库并入的配套 crate。这一层是 Rust 与 V8 的桥梁,拥有 op 基础设施、模块加载器 trait、快照(snapshotting)机制、JsRuntime 事件循环,以及 serde_v8 序列化层。核心成员:
- libs/core —
deno_core本体:JsRuntime、op 注册、模块映射(module map)、inspector 集成。其内部结构包括 op 注册与指标(libs/core/ops.rs、libs/core/ops_metrics.rs)、事件循环(libs/core/event_loop.rs)、快照格式(libs/core/snapshot_format.rs)、模块系统(libs/core/modules/)等。 - libs/ops —
#[op2]过程宏(proc-macro),负责生成 Rust/V8 之间的胶水代码。 - libs/serde_v8 — Rust 类型与 V8 值之间近似零拷贝的序列化。
libs/resolver、libs/node_resolver、libs/npm、libs/npm_installer、libs/package_json、libs/lockfile、libs/config、libs/npmrc— CLI 组合使用的解析与包管理积木块(此外还有libs/npm_cache、libs/cache_dir、libs/cli_parser、libs/eszip、libs/http_h1等配套 crate)。
这些 crate 刻意保持无 CLI 关切,以便可以独立单元测试、被其他工具复用。
横切概念:五个贯穿全栈的核心抽象
无论在哪一层工作,都要理解以下五个贯穿整个技术栈的概念:
- Ops 是 JavaScript 触及原生代码的唯一通道。op 就是暴露给 JS 的 Rust 函数:同步 op 立即返回,异步 op 返回一个 future,在事件循环上解析。
- Extensions 把 op 与其 JavaScript 打包在一起,是组装一个运行时的基本单元。
- Workers 是隔离的 JavaScript 执行上下文(主 worker 与 Web Worker),各自拥有独立的 V8 isolate。
- Resources 是受管理的句柄(打开的文件、socket、reader),由
deno_core追踪,以整数 id 跨 Rust/JS 边界传递。 - Permissions 在 op 边界处由 Rust 强制。未被授予的能力会让 op 在做任何实际工作之前就报错。
改动指引:想做什么,从哪里入手
官方维护了一张"任务 → 起始位置"对照表,是日常开发最直接的导航:
| 你想做…… | 从这里开始 |
|---|---|
| 新增或修改 CLI flag/子命令 | cli/args/flags.rs、cli/tools/ |
| 给 JS 新增原生能力 | ext/<name>/(op + JS) |
| 修改运行时的组装方式 | runtime/worker.rs |
| 触碰 Rust/V8 桥或 op 宏 | libs/core、libs/ops |
| 修改模块/npm/JSR 解析 | libs/resolver、libs/npm、CLI |
结语
Deno 的架构纪律可以浓缩为一句话:上层重、下层净,依赖只向下。CLI 层承担全部用户交互与重型工具链,runtime 层负责组装,ext 层按"一个 crate + 数字前缀 JS"的统一形态扩展平台能力,libs 层以 deno_core、#[op2] 宏和 serde_v8 提供与 V8 打交道的稳定底座。想进一步深入,建议按 doc/codebase-map.md 推荐的顺序阅读 cli/main.rs → cli/args/flags.rs → runtime/worker.rs → runtime/permissions/ → cli/module_loader.rs,并参考 doc/testing.md 了解如何对每一层运行测试。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00