Deno 是一个现代、安全的 JavaScript 和 TypeScript 运行时,基于 V8 引擎,使用 Rust 编写。由 Ryan Dahl(Node.js 的原始创建者)打造,Deno 通过提供一流的 TypeScript 支持、默认安全的权限系统、内置工具(格式化器、检查器、测试运行器、打包器)、Web 标准 API 和原生 npm 兼容性来解决 Node.js 的设计缺憾。本指南涵盖从 Deno 基础到生产部署的所有内容。
Deno 是使用 Rust 编写的安全 JavaScript/TypeScript 运行时。它原生运行 TypeScript,通过细粒度权限系统实施安全性,包含丰富的标准库,原生支持 npm 包,提供内置工具(deno fmt、deno lint、deno test、deno bench),使用 deno.json 配置项目和任务,提供 Deno.serve() 高性能 HTTP 服务器,并通过 Deno Deploy 部署到边缘。Fresh 框架提供基于岛屿的 SSR。
- Deno 原生运行 TypeScript 和 JSX/TSX,无需任何构建步骤、转译器配置或 tsconfig.json。
- 权限系统默认安全:脚本无法访问文件系统、网络或环境,除非明确授予权限标志。
- Deno 标准库(jsr:@std)提供稳定的、经过审查的 HTTP、文件系统、测试、加密和异步工具模块。
- 通过 npm: 说明符的原生 npm 兼容性意味着绝大多数 npm 生态系统可在 Deno 中无需修改即可工作。
- 内置工具替代了整个类别的第三方包:deno fmt(Prettier)、deno lint(ESLint)、deno test(Jest)、deno bench。
- Deno Deploy 提供全球分布式边缘托管,具有亚毫秒级冷启动、自动 HTTPS 和从 GitHub 零配置部署。
Deno 基础:安装和第一步
Deno 作为单个二进制可执行文件分发,没有外部依赖。安装只需几秒钟,即可获得完整的开发环境。
在任何平台上安装 Deno:
# Install Deno (macOS / Linux)
curl -fsSL https://deno.land/install.sh | sh
# Install Deno (Windows PowerShell)
irm https://deno.land/install.ps1 | iex
# Install via Homebrew (macOS)
brew install deno
# Verify installation
deno --version
# deno 2.x.x
# v8 12.x.x
# typescript 5.x.x
# Initialize a new project
deno init my-app
cd my-app
# Creates: main.ts, main_test.ts, deno.json
# Useful commands
deno run main.ts # Run a script
deno run --watch main.ts # Run with auto-reload
deno fmt # Format code
deno lint # Lint code
deno test # Run tests
deno bench # Run benchmarks
deno compile main.ts # Compile to binary你的第一个 Deno 脚本直接运行 TypeScript:
// main.ts — runs directly with: deno run main.ts
interface User {
name: string;
age: number;
email: string;
}
function greet(user: User): string {
return "Hello, " + user.name + "! You are " + user.age + " years old.";
}
const user: User = {
name: "Alice",
age: 30,
email: "alice@example.com",
};
console.log(greet(user));
// Fetch data from a URL (web-standard fetch)
const response = await fetch("https://api.github.com/zen");
const wisdom = await response.text();
console.log("GitHub Zen:", wisdom);安全和权限系统
Deno 默认安全。与 Node.js 不同,Deno 脚本不能读取文件、访问网络或读取环境变量,除非你明确授予权限。
权限标志说明:
# Permission flags
--allow-read Read file system
--allow-write Write file system
--allow-net Network access
--allow-env Environment variables
--allow-run Run subprocesses
--allow-ffi Foreign function interface
--allow-sys System information
--allow-all / -A All permissions (dev only)
# No permissions — script is fully sandboxed
deno run sandboxed.ts
# Runtime will prompt for permission if not granted:
# Deno requests read access to "./config.json".
# Allow? [y/n/A] (y = allow, n = deny, A = allow all)可以组合标志并将其限定到特定资源:
# Scoped permissions — only allow what is needed
deno run \
--allow-read=./data,./config \
--allow-write=./output \
--allow-net=api.example.com:443,cdn.example.com \
--allow-env=DATABASE_URL,API_KEY \
server.ts
# Deny specific permissions (blocklist)
deno run \
--allow-net --deny-net=evil.com \
--allow-read --deny-read=/etc/passwd \
app.ts
# Programmatic permission check
# const status = await Deno.permissions.query(
# { name: "read", path: "./secrets" }
# );
# console.log(status.state); // "granted" | "denied" | "prompt"一流的 TypeScript 支持
Deno 开箱即用支持 TypeScript,零配置。你可以直接运行 .ts 和 .tsx 文件。TypeScript 编译器内置在 Deno 二进制文件中。
通过 deno.json 配置 TypeScript:
// deno.json — TypeScript configuration
{
"compilerOptions": {
"strict": true,
"jsx": "react-jsx",
"jsxImportSource": "react",
"lib": ["deno.window", "dom"],
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
# Type-check your code (not done by default for speed)
# deno check main.ts
# deno run --check main.ts立即可用的 TypeScript 功能:
// Advanced TypeScript — works out of the box
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
function safeParse<T>(json: string): Result<T> {
try {
return { ok: true, value: JSON.parse(json) as T };
} catch (e) {
return { ok: false, error: e as Error };
}
}
interface Config {
port: number;
host: string;
debug: boolean;
}
const result = safeParse<Config>('{"port":3000,"host":"localhost","debug":true}');
if (result.ok) {
console.log("Server port:", result.value.port);
}
// Enums, generics, utility types — all native
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
const partial: DeepPartial<Config> = { port: 8080 };Deno 标准库
Deno 标准库(@std)是由 Deno 团队维护的高质量审查模块集合,通过 JSR 分发。
核心标准库模块:
// Deno Standard Library — import from jsr:@std
import { ensureDir, copy, walk } from "jsr:@std/fs";
import { join, resolve, basename } from "jsr:@std/path";
import { assertEquals, assertThrows } from "jsr:@std/assert";
import { delay } from "jsr:@std/async";
import { parse as parseFlags } from "jsr:@std/cli/parse-args";
import { encodeBase64, decodeBase64 } from "jsr:@std/encoding/base64";
import { crypto } from "jsr:@std/crypto";
import { serveDir } from "jsr:@std/http/file-server";
// File system utilities
await ensureDir("./output/logs");
await copy("./src", "./backup/src", { overwrite: true });
// Walk a directory tree
for await (const entry of walk("./src", { exts: [".ts"] })) {
console.log("Found:", entry.path);
}
// Parse CLI flags
const args = parseFlags(Deno.args, {
string: ["port", "host"],
boolean: ["verbose"],
default: { port: "3000", host: "localhost" },
});
// Encoding
const encoded = encodeBase64("Hello Deno");
console.log(encoded); // "SGVsbG8gRGVubw=="使用 Deno.serve() 构建 HTTP 服务器
Deno 提供 Deno.serve() 作为创建 HTTP 服务器的主要 API,基于 hyper(Rust HTTP 库)构建。
带路由的基本 HTTP 服务器:
// server.ts — Basic HTTP server with routing
// Run: deno run --allow-net server.ts
Deno.serve({ port: 3000 }, (req: Request): Response => {
const url = new URL(req.url);
// JSON API endpoint
if (url.pathname === "/api/health") {
return Response.json({ status: "ok", uptime: Deno.uptime() });
}
// Dynamic route with URL params
if (url.pathname.startsWith("/api/users/")) {
const id = url.pathname.split("/").pop();
return Response.json({ id, name: "User " + id });
}
// Query parameters
if (url.pathname === "/api/search") {
const query = url.searchParams.get("q") || "";
return Response.json({ query, results: [] });
}
// HTML response
if (url.pathname === "/") {
return new Response("<h1>Welcome to Deno</h1>", {
headers: { "Content-Type": "text/html" },
});
}
return new Response("Not Found", { status: 404 });
});
console.log("Server running at http://localhost:3000");更完整的中间件模式服务器:
// Middleware pattern with Deno.serve()
type Handler = (req: Request) => Response | Promise<Response>;
type Middleware = (req: Request, next: Handler) => Response | Promise<Response>;
const logger: Middleware = async (req, next) => {
const start = performance.now();
const res = await next(req);
const ms = (performance.now() - start).toFixed(1);
console.log(req.method + " " + new URL(req.url).pathname + " " + res.status + " " + ms + "ms");
return res;
};
const cors: Middleware = async (req, next) => {
const res = await next(req);
res.headers.set("Access-Control-Allow-Origin", "*");
return res;
};
function compose(mws: Middleware[], h: Handler): Handler {
return mws.reduceRight((next, mw) => (req) => mw(req, next), h);
}
const app = compose([logger, cors], () => {
return Response.json({ message: "Hello from Deno!" });
});
Deno.serve({ port: 3000 }, app);文件系统操作
Deno 通过 Deno 命名空间提供同步和异步文件系统 API,需要 --allow-read 和 --allow-write 权限。
常见文件系统操作:
// File system operations
// Run: deno run --allow-read --allow-write fs-demo.ts
// Read a text file
const content = await Deno.readTextFile("./config.json");
console.log(content);
// Write a text file
await Deno.writeTextFile("./output.txt", "Hello from Deno!");
// Read/write binary files
const bytes = await Deno.readFile("./image.png");
await Deno.writeFile("./copy.png", bytes);
// Create directory (recursive)
await Deno.mkdir("./output/nested/dir", { recursive: true });
// List directory contents
for await (const entry of Deno.readDir("./src")) {
console.log(entry.name, entry.isFile ? "file" : "dir");
}
// File info (stat)
const info = await Deno.stat("./config.json");
console.log("Size:", info.size, "bytes");
console.log("Modified:", info.mtime);
// Remove files and directories
await Deno.remove("./temp.txt");
await Deno.remove("./temp-dir", { recursive: true });
// Rename / move
await Deno.rename("./old.txt", "./new.txt");
// Check if file exists
try {
await Deno.stat("./maybe.txt");
console.log("File exists");
} catch {
console.log("File not found");
}大文件的流式操作:
// Streaming with ReadableStream (web standard)
const bigFile = await Deno.open("./data.csv");
for await (const chunk of bigFile.readable) {
console.log("Received", chunk.length, "bytes");
}
// Watch for file changes
const watcher = Deno.watchFs("./src");
for await (const event of watcher) {
console.log("Event:", event.kind, event.paths);
}使用 Deno 测试
Deno 包含内置测试运行器,支持测试定义、断言、异步测试、测试步骤、模拟、快照测试和代码覆盖率。
编写和组织测试:
// math.ts
export function add(a: number, b: number): number {
return a + b;
}
export function divide(a: number, b: number): number {
if (b === 0) throw new Error("Division by zero");
return a / b;
}
// math_test.ts — Run: deno test math_test.ts
import { assertEquals, assertThrows } from "jsr:@std/assert";
import { add, divide } from "./math.ts";
Deno.test("add positive numbers", () => {
assertEquals(add(2, 3), 5);
});
Deno.test("add negative numbers", () => {
assertEquals(add(-1, -2), -3);
});
Deno.test("divide numbers", () => {
assertEquals(divide(10, 2), 5);
});
Deno.test("divide by zero throws", () => {
assertThrows(
() => divide(10, 0),
Error,
"Division by zero"
);
});
# Run tests with coverage
# deno test --coverage=./cov
# deno coverage ./cov --lcov > coverage.lcov使用模拟和步骤的高级测试:
// Advanced testing with steps, mocking, and async
import { assertEquals } from "jsr:@std/assert";
import { stub, returnsNext } from "jsr:@std/testing/mock";
// Test steps — group related assertions
Deno.test("user CRUD operations", async (t) => {
let userId: string;
await t.step("create user", () => {
userId = "user-123";
assertEquals(typeof userId, "string");
});
await t.step("read user", () => {
assertEquals(userId, "user-123");
});
await t.step("delete user", () => {
userId = "";
assertEquals(userId, "");
});
});
// Mocking with stubs
Deno.test("mocking fetch", async () => {
const fetchStub = stub(
globalThis,
"fetch",
returnsNext([
Promise.resolve(new Response('{"id":1}', { status: 200 })),
])
);
try {
const res = await fetch("https://api.example.com/data");
const data = await res.json();
assertEquals(data.id, 1);
} finally {
fetchStub.restore();
}
});
// Async test with resource sanitizers
Deno.test({
name: "async database test",
sanitizeResources: false,
sanitizeOps: false,
async fn() {
// Test with open resources...
},
});使用 deno.json 配置任务
deno.json 文件是 Deno 项目的中央配置,替代 package.json 脚本、tsconfig.json 和导入映射。
完整的 deno.json 配置:
// deno.json — Complete project configuration
{
"tasks": {
"dev": "deno run --watch --allow-all src/main.ts",
"start": "deno run --allow-net --allow-read --allow-env src/main.ts",
"test": "deno test --allow-all --coverage=./coverage",
"test:watch": "deno test --allow-all --watch",
"check": "deno check src/main.ts",
"lint": "deno lint",
"fmt": "deno fmt",
"build": "deno compile --output=dist/app src/main.ts",
"bench": "deno bench --allow-all",
"ci": "deno fmt --check && deno lint && deno check src/main.ts && deno test --allow-all"
},
"compilerOptions": {
"strict": true,
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"fmt": {
"semiColons": true,
"singleQuote": true,
"lineWidth": 100,
"indentWidth": 2,
"exclude": ["dist/", "coverage/"]
},
"lint": {
"rules": {
"tags": ["recommended"],
"exclude": ["no-unused-vars"]
},
"exclude": ["dist/"]
},
"test": {
"include": ["src/", "tests/"]
},
"lock": true
}导入映射集中管理依赖:
// deno.json — Import map section
{
"imports": {
"@std/http": "jsr:@std/http@^1.0.0",
"@std/assert": "jsr:@std/assert@^1.0.0",
"@std/fs": "jsr:@std/fs@^1.0.0",
"@std/path": "jsr:@std/path@^1.0.0",
"@std/async": "jsr:@std/async@^1.0.0",
"express": "npm:express@4",
"zod": "npm:zod@^3.22",
"drizzle-orm": "npm:drizzle-orm@^0.30",
"@/": "./src/"
}
}
// Now import with clean paths:
// import { assertEquals } from "@std/assert";
// import { z } from "zod";
// import { db } from "@/lib/database.ts";npm 兼容性
Deno 通过 npm: 说明符原生支持 npm 包。包被全局下载和缓存,默认不创建 node_modules 目录。
在 Deno 中使用 npm 包:
// Using npm packages in Deno
import express from "npm:express@4";
import { z } from "npm:zod";
import chalk from "npm:chalk@5";
import _ from "npm:lodash";
// Zod schema validation
const UserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().int().positive(),
});
type User = z.infer<typeof UserSchema>;
const result = UserSchema.safeParse({ name: "Bob", email: "bob@test.com", age: 25 });
if (result.success) {
console.log(chalk.green("Valid user: " + result.data.name));
}
// Express server in Deno
const app = express();
app.get("/", (_req, res) => {
res.json({ runtime: "deno", framework: "express" });
});
app.listen(3000, () => {
console.log("Express on Deno at http://localhost:3000");
});
// Run: deno run --allow-net --allow-read --allow-env server.ts通过 node: 说明符使用 Node.js 内置模块:
// Node.js built-in modules via node: specifier
import { readFileSync, writeFileSync } from "node:fs";
import { join, resolve } from "node:path";
import { createHash, randomUUID } from "node:crypto";
import { EventEmitter } from "node:events";
import { Buffer } from "node:buffer";
import { setTimeout } from "node:timers/promises";
// These work identically to Node.js
const configPath = join(".", "config.json");
const data = readFileSync(configPath, "utf-8");
const hash = createHash("sha256").update(data).digest("hex");
console.log("Hash:", hash);
// EventEmitter
const emitter = new EventEmitter();
emitter.on("data", (msg: string) => console.log("Received:", msg));
emitter.emit("data", "hello from node:events");
// Buffer operations
const buf = Buffer.from("Hello Deno", "utf-8");
console.log(buf.toString("base64")); // SGVsbG8gRGVubw==使用 Deno Deploy 部署
Deno Deploy 是专为 Deno 构建的全球分布式边缘托管平台,在 35+ 个区域运行代码。
设置 Deno Deploy 项目:
# Deploy to Deno Deploy
# 1. Install deployctl CLI
deno install -A jsr:@deno/deployctl
# 2. Create main.ts for edge deployment
# Deno.serve((req: Request) => {
# return new Response("Hello from the edge!");
# });
# 3. Deploy from CLI
deployctl deploy --project=my-app main.ts
# 4. Or connect GitHub for auto-deploy:
# - Go to dash.deno.com
# - Create project > Link to GitHub repo
# - Set entrypoint to main.ts
# - Every push to main triggers deployment
# Docker deployment (alternative)
# FROM denoland/deno:latest
# WORKDIR /app
# COPY . .
# RUN deno cache main.ts
# EXPOSE 3000
# CMD ["run", "--allow-net", "--allow-read", "--allow-env", "main.ts"]Deno Deploy 提供 KV 存储、定时任务和 BroadcastChannel:
// Deno Deploy features: KV, Cron, BroadcastChannel
// Deno KV — distributed key-value storage
const kv = await Deno.openKv();
// Store a value
await kv.set(["users", "alice"], { name: "Alice", visits: 0 });
// Read a value
const entry = await kv.get(["users", "alice"]);
console.log(entry.value); // { name: "Alice", visits: 0 }
// Atomic transactions
await kv.atomic()
.check(entry) // Ensure no concurrent modification
.set(["users", "alice"], { name: "Alice", visits: 1 })
.commit();
// List entries by prefix
const users = kv.list({ prefix: ["users"] });
for await (const user of users) {
console.log(user.key, user.value);
}
// Deno Cron — scheduled tasks
Deno.cron("cleanup", "0 0 * * *", async () => {
console.log("Running daily cleanup...");
// Delete expired entries
});
// BroadcastChannel — cross-isolate messaging
const channel = new BroadcastChannel("updates");
channel.onmessage = (e) => console.log("Update:", e.data);Fresh 框架
Fresh 是 Deno 的官方全栈 Web 框架,使用岛屿架构进行选择性客户端水合,默认在服务器端渲染页面。
创建和理解 Fresh 项目:
# Create a Fresh project
deno run -A -r https://fresh.deno.dev my-fresh-app
cd my-fresh-app
deno task start
# Fresh project structure:
# my-fresh-app/
# routes/ File-based routes
# index.tsx GET / (server-rendered)
# about.tsx GET /about
# api/joke.ts GET /api/joke (API route)
# [name].tsx GET /:name (dynamic route)
# _middleware.ts Middleware for all routes
# islands/ Interactive components (client JS)
# Counter.tsx Ships JavaScript to the browser
# components/ Server-only components (no JS shipped)
# static/ Static files
# fresh.gen.ts Auto-generated manifest
# deno.json Project config
// routes/index.tsx — Server-rendered page
// export default function Home() {
// return (
// <div>
// <h1>Welcome to Fresh</h1>
// <Counter start={0} />
// </div>
// );
// }Fresh 岛屿架构的交互组件:
// islands/Counter.tsx — Interactive island component
// Only this component ships JavaScript to the browser
import { useSignal } from "@preact/signals";
interface CounterProps {
start: number;
}
export default function Counter(props: CounterProps) {
const count = useSignal(props.start);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => count.value--}>-1</button>
<button onClick={() => count.value++}>+1</button>
</div>
);
}
// routes/api/joke.ts — API route
// import { FreshContext } from "$fresh/server.ts";
// const JOKES = ["Why do programmers prefer dark mode?...",];
// export const handler = (_req: Request, _ctx: FreshContext) => {
// const joke = JOKES[Math.floor(Math.random() * JOKES.length)];
// return new Response(JSON.stringify({ joke }), {
// headers: { "Content-Type": "application/json" },
// });
// };Deno 与 Node.js:详细对比
Deno 和 Node.js 都是基于 V8 的 JavaScript 运行时,但在设计理念、安全模型和开发体验上有显著差异。
功能对比:
| Feature | Deno | Node.js |
|---|---|---|
| Language | Rust + V8 | C++ + V8 |
| TypeScript | Native, zero config | Via tsx/ts-node (native in v22+) |
| Security | Sandboxed by default | Unrestricted by default |
| Package Manager | Built-in (deno add) | npm / yarn / pnpm |
| Module System | ESM only (CJS via compat) | CJS + ESM |
| Formatter | Built-in (deno fmt) | Prettier (external) |
| Linter | Built-in (deno lint) | ESLint (external) |
| Test Runner | Built-in (deno test) | Built-in (node --test) / Jest |
| npm Compatibility | ~95%+ via npm: specifier | 100% native |
| Web APIs | Extensive (fetch, Streams, etc.) | Partial (fetch added in v18) |
| Edge Hosting | Deno Deploy (built-in) | Vercel / Cloudflare (external) |
| Ecosystem Maturity | 5+ years, growing | 15+ years, massive |
| Config Files | Single deno.json | package.json + tsconfig + .eslintrc + ... |
选择 Deno:新项目、安全敏感应用、边缘部署、需要内置工具的团队。选择 Node.js:遗留项目、需要特定 Node.js 功能的框架、已有 Node.js 工作流的团队。
最佳实践
- 始终使用最小所需权限。生产环境中不要使用 --allow-all。将权限限定到特定路径和主机。
- 在导入映射中固定依赖版本。使用确切版本以实现可重现的构建。
- 使用 deno.lock 锁定依赖版本。运行 deno cache --lock=deno.lock 验证完整性。
- 优先使用 Deno 原生 API,而非 Node.js 兼容模块,以获得更好的性能。
- 在 CI 管道中运行 deno fmt 和 deno lint。它们快速、有主见且团队一致。
- 使用 Deno.test 的步骤进行集成测试。步骤提供清晰的输出和独立的通过/失败状态。
- 部署到 Deno Deploy 以获得边缘性能。尽可能使用 Deno KV 代替外部数据库。
- 使用 deno compile 编译独立二进制文件,用于 CLI 工具和不需要安装 Deno 的内部工具。
# Recommended CI pipeline for Deno projects
# .github/workflows/ci.yml
#
# name: CI
# on: [push, pull_request]
# jobs:
# test:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: denoland/setup-deno@v2
# with:
# deno-version: v2.x
# - run: deno fmt --check
# - run: deno lint
# - run: deno check src/main.ts
# - run: deno test --allow-all --coverage=cov
# - run: deno coverage cov --lcov > coverage.lcov常见问题
Deno 已经准备好用于生产了吗?
是的。Deno 已被 Slack、Netlify、Supabase 和 GitHub 等公司在生产中使用。运行时自 1.0 以来一直稳定,Deno 2 巩固了向后兼容性和 npm 支持。
可以在 Deno 中使用 npm 包吗?
可以。Deno 使用 npm: 说明符原生支持 npm 包。大多数 npm 包无需修改即可工作。也可以使用 package.json 和 node_modules。
Deno 权限系统如何工作?
Deno 默认在沙箱中运行脚本。通过标志显式授予权限:--allow-read 用于文件系统,--allow-net 用于网络,--allow-env 用于环境变量。权限可限定到特定路径和主机。
需要配置 TypeScript 才能在 Deno 中使用吗?
不需要。Deno 原生运行 TypeScript,无需任何 tsconfig.json 或构建步骤。TypeScript 编译器内置在 Deno 二进制文件中。
Deno 和 Bun 有什么区别?
两者都是现代 JavaScript 运行时。Deno 使用 V8,专注于安全性和 Web 标准。Bun 使用 JavaScriptCore,专注于执行速度和 Node.js 兼容性。
Deno Deploy 是什么,费用多少?
Deno Deploy 是全球分布式边缘托管平台。免费套餐包括每天 10 万请求和 1 GB 出站传输。Pro 套餐每月 20 美元。
可以将现有 Node.js 项目迁移到 Deno 吗?
可以。Deno 2 提供强大的 Node.js 兼容性。增量迁移:添加 deno.json,更新导入使用 node: 和 npm: 前缀,替换 npm 脚本为 Deno 任务。
Fresh 框架是什么?
Fresh 是 Deno 的官方 Web 框架,使用岛屿架构。默认在服务器端渲染页面,仅为交互组件发送 JavaScript。支持文件系统路由和 JSX/TSX。