Deno 运行时入门与架构解析:从安装、第一个 Web 服务到 V8 + Rust + Tokio 分层设计
Deno 是一个以安全默认值和良好开发者体验为核心的 JavaScript、TypeScript 与 WebAssembly 运行时,构建在 V8、Rust 和 Tokio 之上。本文以仓库根目录 README 为主线,覆盖安装、第一个 Deno.serve 服务、权限模型等实战内容,并结合 doc/architecture.md 等仓库文档与源码,解释 deno run --allow-net server.ts 背后逐层工作的实现原理。
一、Deno 是什么
README 对 Deno 的定义非常直接:它是一个 JavaScript、TypeScript 和 WebAssembly 运行时(/ˈdiːnoʊ/,读作 “dee-no”),特性上强调两点:
- 安全默认值(secure defaults):程序默认不能读取文件、访问网络、执行子进程,必须显式通过
--allow-*参数或运行时权限询问授予能力; - 基于三大技术栈:V8(JavaScript 引擎)、Rust(系统语言)、Tokio(异步运行时)。
当前仓库的版本号为 2.9.6,记录在 cli/lib/version.txt。
二、安装 Deno
README 给出了五种官方安装途径,覆盖 macOS、Linux 与 Windows。以下命令均可直接复制运行:
Shell(Mac、Linux):
curl -fsSL https://deno.land/install.sh | sh
PowerShell(Windows):
irm https://deno.land/install.ps1 | iex
Homebrew(Mac):
brew install deno
Chocolatey(Windows):
choco install deno
WinGet(Windows):
winget install --id=DenoLand.Deno
Scoop(Windows):
scoop install main/deno
README 同时说明,以上只是部分途径,完整安装方式列表见 Deno 官方文档。若希望从源码构建安装,则参考贡献指南(.github/CONTRIBUTING.md 中的 “Building from source” 一节)。构建所需的前提是理解本仓库的工作区结构——根 Cargo.toml 定义了一个包含 cli、runtime、数十个 ext/* 与 libs/* 成员的 Cargo workspace,这正是下文分层架构在工程上的对应物。
三、你的第一个 Deno 程序:用 Deno.serve 写一个 Web 服务
README 指出,Deno 最典型的用途是构建 Web 服务器。完整步骤如下:
创建文件 server.ts:
Deno.serve((_req: Request) => {
return new Response("Hello, world!");
});
然后运行:
deno run --allow-net server.ts
启动后,本地 Web 服务默认监听 http://localhost:8000,访问该地址即可看到 Hello, world!。
3.1 为什么需要 --allow-net
这里的权限参数不是装饰,而是 Deno 安全模型的入口:
--allow-net授予程序打开网络监听/连接的许可。不带该参数运行同一文件时,Deno.serve会在权限检查处报错,而不是默认放行。- 该 flag 的解析逻辑在 cli/args/flags.rs 中,可以看到
--allow-net支持精确到地址的白名单形式(如--allow-net=8000、--allow-net=127.0.0.1:8000),源码中会将其拼接为--allow-net=<allowlist>参数。 - 权限的实际执行发生在 Rust 侧的 op 边界。doc/architecture.md 明确指出:“权限在 Rust 中、在 op 边界处检查,绝不在 JavaScript 中检查”,且“未授予的能力会让该 op 在执行任何工作之前就报错”。对应的权限模型代码位于 runtime/permissions/ 目录。
3.2 Deno.serve 的默认端口 8000 从哪里来
Deno.serve 的实现位于 ext/http/00_serve.ts。其中:
- 函数入口
serve(arg1, arg2)支持两种签名:直接传 handler 函数,或传{ handler, port, hostname, ... }选项对象(见 ext/http/00_serve.ts); - README 中省略 port 时服务落在 8000 端口,对应源码中的默认值
port: options.port ?? 8000(见 ext/http/00_serve.ts)。
也就是说,README 示例中的 http://localhost:8000 并非文档随口写的地址,而是 Deno.serve 内置的默认监听端口。
四、架构总览:README 背后的五层堆叠
README 本身是面向用户的最小文档,而 doc/architecture.md 给出了运行时本身的分层设计,这是理解“deno run 一条命令如何工作”的关键。原文档中的层叠结构如下(自顶向下,每层只依赖其下的层):
+-----------------------------------------------------------+
| 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 |
+-----------------------------------------------------------+
4.1 CLI 层(cli/):用户直接触碰的一切
deno crate 拥有 flag 解析、所有子命令(run、test、fmt、lint、compile、bundle、install、publish 等)、包管理工具、LSP,以及把模块解析与运行时连接起来的 module loader。关键入口文件:
| 文件 | 职责 |
|---|---|
| cli/main.rs | 进程入口与命令路由 |
| cli/args/flags.rs | 完整的 clap flag 与子命令定义,新增 flag 或子命令从这里开始 |
cli/tools/<tool>/ |
每个子命令一个模块(简单命令如 cli/tools/fmt.rs,复杂命令如 cli/tools/test/ 目录) |
| cli/module_loader.rs | 解析并加载模块,把 resolver 与 module graph 桥接到运行时 |
架构文档强调 CLI 层是“刻意沉重”的:它引入 TypeScript 类型检查、npm 与 JSR 解析、lockfile、bundler 等能力,而更低的层不允许反向依赖它。
4.2 运行时层(runtime/):deno_runtime crate
这一层把 deno_core 加上一组精选的 extensions 组装成一个可工作的 JavaScript 运行时,是希望嵌入“Deno 运行时”而非“Deno CLI”的外部项目使用的部分。关键文件:
- runtime/worker.rs —— 构造主 worker:isolate、op 集合、bootstrap 序列;
- runtime/web_worker.rs —— Web Worker 变体;
- runtime/permissions/ —— 权限模型,门控所有敏感 op(read、write、net、env、run、ffi、sys)。
4.3 扩展层(ext/*):平台能力的真正所在
ext/ 下每个目录都是一个自包含的 extension:一个 Rust crate,定义 ops(可从 JS 调用的原生函数),外加在其上暴露高层 API 的 JavaScript 模块。Web 平台能力(ext/web、ext/fetch、ext/crypto、ext/webgpu 等)、系统访问(ext/fs、ext/net、ext/process 等)、Deno 独有特性(ext/kv、ext/cron、ext/ffi、ext/napi)以及 Node 兼容层(ext/node/ 承载大部分 node:* 内建模块,另有 ext/node_crypto、ext/node_sqlite)都分布在这里。
一个 extension 的典型形态是三步:
- Rust 侧用
#[op2]函数完成特权操作,需要时附带权限检查; 00_*.js/01_*.js等编号 JS 模块构建公开 API,通过Deno.core.ops调用这些 op——例如本文第三节的Deno.serve就位于 ext/http/00_serve.ts,同层的原生 op 定义在 ext/http/lib.rs;- 在 runtime/worker.rs(以及 CLI 的 snapshot 构建)中注册该 extension,使其成为组装后运行时的一部分。
架构文档还给出了一条明确的扩展守则:新增原生功能时,应在对应的 ext/<name>/ crate 中添加 op,而不是伸手去改 runtime 或 CLI。
4.4 核心层(libs/*):Rust 与 V8 之间的桥
libs/ 保存 deno_core 及支撑 crate,拥有 op 基础设施、module loader trait、snapshot 机制、JsRuntime 事件循环,以及 serde_v8 序列化层。值得注意的成员:
- libs/core/ ——
deno_core本身:JsRuntime、op 注册、module map、inspector 集成; - libs/ops/ —— 生成 Rust/V8 胶合代码的
#[op2]过程宏; - libs/serde_v8/ —— Rust 类型与 V8 值之间接近零拷贝的序列化;
- libs/resolver/、libs/npm/、libs/npm_installer/、libs/lockfile/、libs/config/ 等 —— CLI 组合使用的模块解析与包管理积木。
这些 crate 刻意不含任何 CLI 关注点,以便独立单测并被其他工具复用。
4.5 横切概念:Ops、Extensions、Workers、Resources、Permissions
架构文档归纳了五个贯穿全栈的概念:
- Ops 是 JavaScript 触及原生代码的唯一途径。同步 op 立即返回,异步 op 返回一个在未来事件循环上解决的 future;
- Extensions 把 op 与其 JavaScript 打包在一起,是运行时组合的基本单元;
- Workers 是隔离的 JS 执行上下文(主 worker 与 Web Worker),各自拥有独立的 V8 isolate;
- Resources 是被管理的句柄(打开的文件、socket、reader),由
deno_core跟踪,以整型 id 跨 Rust/JS 边界传递; - Permissions 在 Rust 侧的 op 边界强制执行,未授予的能力会让 op 在任何工作发生前就报错。
五、定位你的改动:架构文档中的速查表
doc/architecture.md 末尾给出了一张“我要做 X,应该从哪开始”的表,对理解各模块职责边界非常有用:
| 需求 | 起始位置 |
|---|---|
| 添加或修改 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 |
更细粒度的目录地图见 doc/codebase-map.md,各层的测试方式见 doc/testing.md,此外 doc/ci.md 解释了 CI 工作流如何生成、以及纯文档改动(仅触碰 doc/ 的 PR)为何只需跑 lint 任务。
六、延伸阅读资源
README 列出的官方资源与本文相互印证:
- Deno Docs:运行时、部署等的官方指南与参考文档;
- Deno Standard Library(
@std):官方维护的通用工具库; - JSR:面向现代 JavaScript/TypeScript 的开源包注册表,其解析逻辑在仓库中对应
cli/jsr.js与libs/resolver等模块; - 贡献指南:见 .github/CONTRIBUTING.md,涵盖从源码构建的完整步骤。
七、小结
从仓库视角看,README 中“安装 → 写 server.ts → deno run --allow-net server.ts → 访问 localhost:8000”这条最短路径,实际上穿过了整条技术栈:cli/args/flags.rs 解析出 --allow-net,cli/ 的 module loader 拉取并解析 server.ts,runtime/worker.rs 组装 isolate 与 op 集合,ext/http 提供 Deno.serve(默认端口 8000),libs/core 与 libs/ops 承担 Rust/V8 桥接,而 net 权限在 op 边界完成裁决。理解这条调用链,也就理解了 Deno“安全默认 + 分层可复用”这两大设计目标的工程落地方式。
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