正在加载,请稍候…

Deno 2.0:现代 JavaScript 运行时 vs Node.js——迁移与对比

Deno 2.0 完整指南:默认安全权限、原生 TypeScript、内置工具链、Node.js 兼容性、npm 包支持,以及从 Express/Node 到

Deno 2.0:现代 JavaScript 运行时 vs Node.js——迁移与对比

Deno 2.0:现代 JavaScript 运行时

Deno 2.0 代表了重大的成熟:完全兼容 Node.js 和 npm,同时保持其安全优先、基于标准的设计。它现在已成为新项目中 Node.js 的现实替代方案。

Deno 的不同之处

特性 Node.js Deno 2.0
TypeScript 需要 ts-node/esbuild 原生,零配置
安全性 完全系统访问 默认拒绝
包管理器 npm/yarn/pnpm npm + jsr: imports
工具链 需要单独工具 内置(fmt, lint, test, bench)
标准 Node 特定 API Web 平台 API
Node 兼容性 原生 95%+ 通过 node: 前缀

Deno 2.0:现代 JavaScript 运行时 vs Node.js——迁移与对比插图

快速开始

curl -fsSL https://deno.land/install.sh | sh

# 直接运行 TypeScript - 无需编译步骤
deno run main.ts

# 带权限
deno run --allow-net --allow-read main.ts

# REPL
deno

安全模型

Deno 默认拒绝所有系统访问:

// 如果没有 --allow-net,这将失败
const response = await fetch('https://api.example.com/data');

// 如果没有 --allow-read,这将失败
const text = await Deno.readTextFile('./config.json');

// 细粒度权限
// --allow-net=api.example.com    (仅此主机)
// --allow-read=/app/config       (仅此路径)
// --allow-env=DATABASE_URL       (仅此环境变量)

// deno.json - 生产权限
// {
//   'tasks': {
//     'start': 'deno run --allow-net=:8000 --allow-env=DATABASE_URL --allow-read=./static main.ts'
//   }
// }

现代 HTTP 服务器

// server.ts - 无需依赖!
import { serveFile } from 'jsr:@std/http/file-server';

const handler = async (req: Request): Promise<Response> => {
    const url = new URL(req.url);

    if (url.pathname === '/api/users') {
        const users = await getUsers();
        return Response.json(users);
    }

    if (url.pathname.startsWith('/static/')) {
        return serveFile(req, '.' + url.pathname);
    }

    return new Response('Not Found', { status: 404 });
};

Deno.serve({ port: 8000, hostname: '0.0.0.0' }, handler);
console.log('Listening on http://localhost:8000');

Deno 2.0:现代 JavaScript 运行时 vs Node.js——迁移与对比插图

使用 npm 包

Deno 2.0 原生支持 npm 包:

// 直接使用 npm 包
import express from 'npm:express@4';
import { z } from 'npm:zod';
import Stripe from 'npm:stripe';

// 或使用 deno.json 的 imports 映射
// deno.json:
// {
//   'imports': {
//     'express': 'npm:express@4',
//     'zod': 'npm:zod@3'
//   }
// }
import express from 'express';
import { z } from 'zod';

const app = express();
const UserSchema = z.object({
    name: z.string().min(1),
    email: z.string().email(),
    age: z.number().int().min(0).max(150)
});

app.post('/users', async (req, res) => {
    const result = UserSchema.safeParse(req.body);
    if (!result.success) return res.status(400).json(result.error);
    // ...
});

内置工具链

# 格式化(替代 prettier)
deno fmt
deno fmt --check  # CI 检查

# 代码检查(替代 eslint 常见规则)
deno lint

# 测试(替代 jest/mocha)
deno test
deno test --coverage
deno coverage --html  # HTML 覆盖率报告

# 基准测试
deno bench

# 仅类型检查
deno check main.ts

# 打包为浏览器版本
deno bundle main.ts output.js

# 编译为单个可执行文件
deno compile --allow-net --allow-read main.ts

测试

// user.test.ts
import { assertEquals, assertRejects } from 'jsr:@std/assert';
import { getUser, createUser } from './user.ts';

Deno.test('getUser returns user by id', async () => {
    const user = await getUser('123');
    assertEquals(user.id, '123');
    assertEquals(user.name, 'Alice');
});

Deno.test('createUser rejects invalid email', async () => {
    await assertRejects(
        () => createUser({ name: 'Bob', email: 'not-an-email' }),
        Error,
        'Invalid email'
    );
});

// 分组测试,带 setup/teardown
Deno.test('user service', async (t) => {
    const db = await createTestDatabase();
    await t.step('creates user', async () => {
        const user = await createUser(db, { name: 'Test' });
        assertEquals(user.name, 'Test');
    });
    await t.step('lists users', async () => {
        const users = await listUsers(db);
        assertEquals(users.length, 1);
    });
    await db.close();
});

Deno 2.0:现代 JavaScript 运行时 vs Node.js——迁移与对比插图

从 Node.js 迁移

// Node.js - 之前
// const fs = require('fs/promises');
// const path = require('path');

// Deno - 之后(选项 1:node: 前缀)
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
const text = await readFile(join('./data', 'file.txt'), 'utf-8');

// Deno - 之后(选项 2:原生 Deno API)
const text2 = await Deno.readTextFile('./data/file.txt');

// 环境变量
// Node: process.env.DATABASE_URL
// Deno: Deno.env.get('DATABASE_URL')

// 进程退出
// Node: process.exit(1)
// Deno: Deno.exit(1)

使用 Deno Deploy 部署

// 在 Deno Deploy 的边缘网络(35+ 区域)全局运行
// 基本部署无需配置

// deploy.ts - 直接上传
const kv = await Deno.openKv(); // 内置分布式 KV 存储

Deno.serve(async (req: Request) => {
    const url = new URL(req.url);

    if (req.method === 'POST' && url.pathname === '/count') {
        const key = ['visits', url.hostname];
        const current = (await kv.get<number>(key)).value ?? 0;
        await kv.set(key, current + 1);
        return Response.json({ count: current + 1 });
    }

    return new Response('Hello from edge!', {
        headers: { 'Content-Type': 'text/plain' }
    });
});

是否应该迁移?

在以下情况下为新项目迁移到 Deno

  • 需要 TypeScript(零配置是真正的优势)
  • 安全模型很重要(微服务、处理用户上传)
  • 希望减少开发依赖(无需 eslint、prettier、ts-node、nodemon)

在以下情况下继续使用 Node.js

  • 现有大型代码库,深度依赖 npm 生态系统
  • 团队经验和现有工具严重基于 Node
  • 需要原生插件(.node 文件)

Deno 2.0 已可用于生产环境。其基于标准的方法意味着你的代码无需修改即可在浏览器和边缘运行时中工作。