Deno vs Node.js:运行时之争
// 目录 · contents
前言
Deno由Node.js的原作者Ryan
Dahl于2018年发布,旨在解决Node.js的设计缺陷。经过多年发展,Deno
2.0在保持其安全优先理念的同时,大幅提升了Node.js兼容性。本文将从多个维度深入对比这两个运行时。
架构对比
graph TB
subgraph "Node.js Architecture"
NJS[JavaScript/TypeScript] --> NV8[V8 Engine]
NV8 --> NAPI[Node API / N-API]
NAPI --> NLIBUV[libuv<br>Event Loop]
NAPI --> NOPENSSL[OpenSSL]
NAPI --> NHTTP[llhttp]
NJS --> NNPM[npm/yarn/pnpm<br>node_modules]
end
subgraph "Deno Architecture"
DJS[JavaScript/TypeScript] --> DV8[V8 Engine]
DV8 --> DRUST[Rust Core<br>deno_core]
DRUST --> DTOKIO[Tokio<br>Async Runtime]
DRUST --> DRUSTLS[Rustls<br>TLS]
DRUST --> DHTTP[hyper<br>HTTP]
DJS --> DMOD[URL Imports<br>+ npm: specifier]
end
关键区别:Deno使用Rust和Tokio构建,而Node.js使用C++和libuv。
安全模型
Deno最显著的特性是默认安全——默认情况下,程序没有任何文件系统、网络、环境变量的访问权限。
graph LR
subgraph "Node.js"
NS[脚本] --> NFS[文件系统 - 完全访问]
NS --> NNET[网络 - 完全访问]
NS --> NENV[环境变量 - 完全访问]
NS --> NSUB[子进程 - 完全访问]
end
subgraph "Deno"
DS[脚本] --> DFS[文件系统 - 需要--allow-read/write]
DS --> DNET[网络 - 需要--allow-net]
DS --> DENV[环境变量 - 需要--allow-env]
DS --> DSUB[子进程 - 需要--allow-run]
end
style NFS fill:#d32f2f,color:#fff
style NNET fill:#d32f2f,color:#fff
style DFS fill:#388e3c,color:#fff
style DNET fill:#388e3c,color:#fff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
deno run script.ts
deno run --allow-read=/data script.ts
deno run --allow-net=api.example.com,cdn.example.com script.ts
deno run --allow-read=/data --allow-write=/output --allow-net script.ts
deno run --allow-env=API_KEY,DATABASE_URL script.ts
deno run --allow-run=git,node script.ts
deno run --allow-all script.ts
deno run -A script.ts
|
1 2 3 4 5 6 7 8 9
| const status = await Deno.permissions.query({ name: "read", path: "/etc" }); console.log(status.state);
const result = await Deno.permissions.request({ name: "net", host: "api.example.com" }); if (result.state === "granted") { const resp = await fetch("https://api.example.com/data"); }
|
TypeScript支持
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| interface User { id: number; name: string; email: string; }
async function fetchUser(id: number): Promise<User> { const resp = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`); return resp.json(); }
const user = await fetchUser(1); console.log(`Hello, ${user.name}!`);
|
模块系统
graph TB
subgraph "Node.js Module System"
NPM[npm Registry] --> NM[node_modules/]
NM --> PKG[package.json<br>dependencies]
NM --> |嵌套依赖| NESTED[node_modules/<br> express/<br> node_modules/<br> ...]
end
subgraph "Deno Module System"
URL[URL Import<br>https://deno.land/std] --> CACHE[全局缓存<br>~/.cache/deno/]
NPMSPEC[npm: specifier<br>npm:express@4] --> CACHE
JSR[JSR Registry<br>jsr:@std/path] --> CACHE
end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import express from 'express'; import { readFile } from 'fs/promises'; import { myUtil } from './utils.js';
import { serve } from "https://deno.land/[email protected]/http/server.ts";
import express from "npm:express@4"; import chalk from "npm:chalk@5";
import { join } from "jsr:@std/path@1"; import { parse } from "jsr:@std/csv@1";
import { join } from "@std/path"; import express from "express"; import { config } from "~/config.ts";
|
deno.json配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| // deno.json { // Import Maps "imports": { "@std/": "jsr:@std/", "express": "npm:express@4", "hono": "jsr:@hono/hono@4", "~/": "./src/" },
// TypeScript编译选项 "compilerOptions": { "strict": true, "jsx": "react-jsx", "jsxImportSource": "react" },
// 任务(类似npm scripts) "tasks": { "dev": "deno run --watch --allow-net --allow-read src/main.ts", "start": "deno run --allow-net --allow-read src/main.ts", "test": "deno test --allow-read", "lint": "deno lint", "fmt": "deno fmt", "check": "deno check src/**/*.ts" },
// 格式化配置 "fmt": { "indentWidth": 2, "semiColons": true, "singleQuote": true },
// Lint配置 "lint": { "rules": { "tags": ["recommended"] } } }
|
标准库对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
|
import { readFile, writeFile } from 'fs/promises'; const data = await readFile('file.txt', 'utf-8'); await writeFile('output.txt', data);
const data = await Deno.readTextFile('file.txt'); await Deno.writeTextFile('output.txt', data);
import { createServer } from 'http'; const server = createServer((req, res) => { res.writeHead(200); res.end('Hello'); }); server.listen(3000);
Deno.serve({ port: 3000 }, (req) => { return new Response('Hello'); });
const resp = await fetch('https://api.example.com/data'); const json = await resp.json();
const resp = await fetch('https://api.example.com/data'); const json = await resp.json();
import { test, describe, it } from 'node:test'; import assert from 'node:assert';
describe('math', () => { it('should add', () => { assert.strictEqual(1 + 1, 2); }); });
Deno.test('math - should add', () => { assertEquals(1 + 1, 2); });
import { describe, it, expect } from "jsr:@std/testing/bdd";
describe('math', () => { it('should add', () => { expect(1 + 1).toBe(2); }); });
|
Web框架对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import express from 'express'; const app = express();
app.get('/api/users/:id', async (req, res) => { const user = await getUser(req.params.id); res.json(user); });
app.listen(3000);
import { Hono } from 'hono'; const app = new Hono();
app.get('/api/users/:id', async (c) => { const user = await getUser(c.req.param('id')); return c.json(user); });
Deno.serve(app.fetch);
import { Application, Router } from "jsr:@oak/oak@16";
const router = new Router(); router.get('/api/users/:id', async (ctx) => { const user = await getUser(ctx.params.id); ctx.response.body = user; });
const app = new Application(); app.use(router.routes()); await app.listen({ port: 3000 });
|
Deno Deploy
graph TB
CODE[Source Code] --> DEPLOY[Deno Deploy]
DEPLOY --> EDGE1[Edge Node<br>Asia]
DEPLOY --> EDGE2[Edge Node<br>Europe]
DEPLOY --> EDGE3[Edge Node<br>Americas]
USER1[Users Asia] --> EDGE1
USER2[Users Europe] --> EDGE2
USER3[Users Americas] --> EDGE3
EDGE1 --> KV[(Deno KV<br>Global Database)]
EDGE2 --> KV
EDGE3 --> KV
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { Hono } from "jsr:@hono/hono";
const app = new Hono(); const kv = await Deno.openKv();
app.get("/api/visits", async (c) => { const key = ["visits", "total"]; const result = await kv.get<number>(key); const count = (result.value ?? 0) + 1; await kv.set(key, count); return c.json({ visits: count }); });
app.get("/", (c) => { return c.html(`<h1>Hello from the Edge!</h1>`); });
Deno.serve(app.fetch);
|
1 2 3 4 5 6 7
|
deno install -Agf jsr:@deno/deployctl deployctl deploy --project=my-project src/main.ts
|
Node.js兼容性(Deno 2.0+)
flowchart TB
DENO2[Deno 2.0] --> COMPAT{兼容性}
COMPAT --> NPM[npm包支持<br>大多数npm包可用]
COMPAT --> NODEAPI[Node.js API<br>fs, path, http...]
COMPAT --> PKGJSON[package.json支持]
COMPAT --> NM[node_modules支持]
NPM --> EXAMPLE1["import express from 'npm:express'"]
NODEAPI --> EXAMPLE2["import { readFile } from 'node:fs'"]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
import { readFileSync } from "node:fs"; import { join } from "node:path"; import { createServer } from "node:http";
import chalk from "npm:chalk@5"; import lodash from "npm:lodash@4";
console.log(chalk.green("Hello from Deno with npm packages!"));
|
迁移指南
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
import { helper } from './utils';
import { helper } from './utils.ts';
import fs from 'fs';
import fs from 'node:fs';
const buf = Buffer.from('hello');
import { Buffer } from 'node:buffer'; const buf = Buffer.from('hello');
console.log(__dirname); console.log(__filename);
console.log(import.meta.dirname); console.log(import.meta.filename);
console.log(process.env.HOME);
console.log(Deno.env.get('HOME'));
import process from 'node:process'; console.log(process.env.HOME);
|
特性对比总结
| 语言 |
C++/libuv |
Rust/Tokio |
| TypeScript |
需要配置 |
原生支持 |
| 安全模型 |
无沙箱 |
默认安全,权限控制 |
| 包管理 |
npm/yarn/pnpm |
URL导入 + npm: + JSR |
| 标准库 |
需要npm包 |
内置标准库 |
| 测试 |
需要Jest等 |
内置测试运行器 |
| 格式化 |
需要Prettier |
内置 deno fmt |
| Lint |
需要ESLint |
内置 deno lint |
| 生态系统 |
最大(npm) |
较小但兼容npm |
| 部署 |
PM2/Docker |
Deno Deploy(边缘计算) |
| Web标准 |
逐步支持 |
优先使用Web标准API |
| 编译 |
pkg/nexe |
deno compile(单一可执行文件) |
选择建议
flowchart TB
START[新项目选择运行时] --> Q1{需要大量npm包?}
Q1 -->|是| Q2{对安全性要求高?}
Q1 -->|否| DENO_REC[推荐Deno]
Q2 -->|是| DENO_NPM[Deno + npm兼容]
Q2 -->|否| NODE_REC[Node.js]
DENO_REC --> Q3{需要边缘部署?}
Q3 -->|是| DENO_DEPLOY[Deno + Deno Deploy]
Q3 -->|否| DENO_GENERAL[Deno]
NODE_REC --> Q4{企业级项目?}
Q4 -->|是| NODE_MATURE[Node.js<br>生态最成熟]
Q4 -->|否| EITHER[两者皆可]
总结
Node.js和Deno各有优势:
- Node.js:生态系统最大,企业采用最广,适合需要大量第三方库的项目
- Deno:安全优先,开发体验好(内置TypeScript/测试/格式化/Lint),适合新项目
- Deno 2.0:npm兼容性大幅提升,迁移成本降低
- Deno Deploy:边缘计算部署方案,全球低延迟
- 选择建议:新项目可以优先考虑Deno,现有Node.js项目无需急于迁移
两个运行时在互相学习中共同进步——Node.js引入了权限模型(实验性),Deno提升了npm兼容性。最终受益的是开发者生态。