发布于

Deno 运行时深度解析:现代 JavaScript/TypeScript 运行环境的完整指南

作者

Deno 运行时深度解析:现代 JavaScript/TypeScript 运行环境的完整指南

Deno 是一个现代、安全的 JavaScript 和 TypeScript 运行时,由 Node.js 的创始人 Ryan Dahl 开发。它解决了 Node.js 的许多设计问题,提供了更好的安全性、开发体验和现代化的功能。

Deno 核心概念

什么是 Deno

Deno 是基于 V8 JavaScript 引擎和 Rust 构建的运行时,具有以下核心特性:

// hello.ts - Deno 的第一个程序
console.log("Hello, Deno! 🦕");

// 运行命令
// deno run hello.ts

核心特性对比

// Node.js vs Deno 对比

// Node.js 方式
const fs = require('fs');
const path = require('path');
const http = require('http');

// Deno 方式 - 内置 Web API
const text = await Deno.readTextFile('./file.txt');
const response = await fetch('https://api.example.com/data');

安全模型

// Deno 的权限系统
// 默认情况下,Deno 是安全的,需要显式授权

// 读取文件需要权限
// deno run --allow-read script.ts
const content = await Deno.readTextFile('./config.json');

// 网络访问需要权限
// deno run --allow-net script.ts
const response = await fetch('https://api.example.com');

// 环境变量访问需要权限
// deno run --allow-env script.ts
const apiKey = Deno.env.get('API_KEY');

// 运行子进程需要权限
// deno run --allow-run script.ts
const process = new Deno.Command('ls', { args: ['-la'] });
const output = await process.output();

开发环境搭建

安装和配置

# 安装 Deno
curl -fsSL https://deno.land/install.sh | sh

# Windows (PowerShell)
irm https://deno.land/install.ps1 | iex

# macOS (Homebrew)
brew install deno

# 验证安装
deno --version

# 升级 Deno
deno upgrade

项目配置

// deno.json - Deno 配置文件
{
  "compilerOptions": {
    "allowJs": true,
    "lib": ["deno.window"],
    "strict": true
  },
  "lint": {
    "rules": {
      "tags": ["recommended"]
    }
  },
  "fmt": {
    "files": {
      "include": ["src/"],
      "exclude": ["src/testdata/"]
    },
    "options": {
      "useTabs": false,
      "lineWidth": 80,
      "indentWidth": 2,
      "singleQuote": true
    }
  },
  "tasks": {
    "start": "deno run --allow-net --allow-read main.ts",
    "dev": "deno run --allow-net --allow-read --watch main.ts",
    "test": "deno test --allow-net --allow-read",
    "lint": "deno lint",
    "fmt": "deno fmt"
  },
  "imports": {
    "@std/": "https://deno.land/std@0.208.0/",
    "@oak/oak": "https://deno.land/x/oak@v12.6.1/mod.ts"
  }
}

VS Code 配置

// .vscode/settings.json
{
  "deno.enable": true,
  "deno.lint": true,
  "deno.unstable": false,
  "deno.suggest.imports.hosts": {
    "https://deno.land": true
  },
  "[typescript]": {
    "editor.defaultFormatter": "denoland.vscode-deno"
  },
  "[javascript]": {
    "editor.defaultFormatter": "denoland.vscode-deno"
  }
}

Web 服务器开发

原生 HTTP 服务器

// server.ts - 使用 Deno 原生 API
const port = 8000;

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

  // 路由处理
  switch (pathname) {
    case '/':
      return new Response('Hello, Deno Server! 🦕', {
        headers: { 'content-type': 'text/plain' },
      });

    case '/api/time':
      return new Response(JSON.stringify({
        timestamp: Date.now(),
        iso: new Date().toISOString(),
      }), {
        headers: { 'content-type': 'application/json' },
      });

    case '/api/headers':
      const headers: Record<string, string> = {};
      request.headers.forEach((value, key) => {
        headers[key] = value;
      });
      return new Response(JSON.stringify(headers, null, 2), {
        headers: { 'content-type': 'application/json' },
      });

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

console.log(`HTTP server running on http://localhost:${port}/`);
Deno.serve({ port }, handler);

使用 Oak 框架

// oak-server.ts - 使用 Oak 框架
import { Application, Router } from '@oak/oak';
import { oakCors } from 'https://deno.land/x/cors@v1.2.2/mod.ts';

const app = new Application();
const router = new Router();

// 中间件
app.use(oakCors()); // CORS 支持
app.use(async (ctx, next) => {
  console.log(`${ctx.request.method} ${ctx.request.url}`);
  await next();
});

// 路由定义
router
  .get('/', (ctx) => {
    ctx.response.body = { message: 'Welcome to Deno Oak API! 🦕' };
  })
  .get('/api/users', async (ctx) => {
    // 模拟数据库查询
    const users = [
      { id: 1, name: 'Alice', email: 'alice@example.com' },
      { id: 2, name: 'Bob', email: 'bob@example.com' },
    ];
    ctx.response.body = { users };
  })
  .post('/api/users', async (ctx) => {
    const body = await ctx.request.body({ type: 'json' }).value;
    
    // 简单验证
    if (!body.name || !body.email) {
      ctx.response.status = 400;
      ctx.response.body = { error: 'Name and email are required' };
      return;
    }

    // 模拟创建用户
    const newUser = {
      id: Date.now(),
      name: body.name,
      email: body.email,
      createdAt: new Date().toISOString(),
    };

    ctx.response.status = 201;
    ctx.response.body = { user: newUser };
  })
  .get('/api/users/:id', (ctx) => {
    const id = ctx.params.id;
    // 模拟用户查找
    const user = { id: parseInt(id), name: 'User ' + id };
    ctx.response.body = { user };
  });

// 错误处理中间件
app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    console.error('Server error:', err);
    ctx.response.status = 500;
    ctx.response.body = { error: 'Internal server error' };
  }
});

app.use(router.routes());
app.use(router.allowedMethods());

const port = 8080;
console.log(`Oak server running on http://localhost:${port}`);
await app.listen({ port });

WebSocket 服务器

// websocket-server.ts
interface Client {
  id: string;
  socket: WebSocket;
  name?: string;
}

class WebSocketServer {
  private clients = new Map<string, Client>();

  async handleConnection(request: Request): Promise<Response> {
    const { socket, response } = Deno.upgradeWebSocket(request);
    const clientId = crypto.randomUUID();

    const client: Client = {
      id: clientId,
      socket,
    };

    socket.onopen = () => {
      console.log(`Client ${clientId} connected`);
      this.clients.set(clientId, client);
      
      // 发送欢迎消息
      socket.send(JSON.stringify({
        type: 'welcome',
        clientId,
        message: 'Connected to WebSocket server',
      }));
    };

    socket.onmessage = (event) => {
      try {
        const data = JSON.parse(event.data);
        this.handleMessage(clientId, data);
      } catch (error) {
        console.error('Invalid JSON message:', error);
      }
    };

    socket.onclose = () => {
      console.log(`Client ${clientId} disconnected`);
      this.clients.delete(clientId);
      this.broadcast({
        type: 'user_left',
        clientId,
        name: client.name,
      }, clientId);
    };

    socket.onerror = (error) => {
      console.error(`WebSocket error for client ${clientId}:`, error);
    };

    return response;
  }

  private handleMessage(clientId: string, data: any) {
    const client = this.clients.get(clientId);
    if (!client) return;

    switch (data.type) {
      case 'set_name':
        client.name = data.name;
        this.broadcast({
          type: 'user_joined',
          clientId,
          name: data.name,
        }, clientId);
        break;

      case 'chat_message':
        this.broadcast({
          type: 'chat_message',
          clientId,
          name: client.name || 'Anonymous',
          message: data.message,
          timestamp: new Date().toISOString(),
        }, clientId);
        break;

      case 'ping':
        client.socket.send(JSON.stringify({ type: 'pong' }));
        break;

      default:
        console.log('Unknown message type:', data.type);
    }
  }

  private broadcast(message: any, excludeClientId?: string) {
    const messageStr = JSON.stringify(message);
    
    for (const [clientId, client] of this.clients) {
      if (clientId !== excludeClientId && client.socket.readyState === WebSocket.OPEN) {
        try {
          client.socket.send(messageStr);
        } catch (error) {
          console.error(`Failed to send message to client ${clientId}:`, error);
        }
      }
    }
  }

  getStats() {
    return {
      connectedClients: this.clients.size,
      clients: Array.from(this.clients.values()).map(client => ({
        id: client.id,
        name: client.name,
        readyState: client.socket.readyState,
      })),
    };
  }
}

// 启动服务器
const wsServer = new WebSocketServer();

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

  if (url.pathname === '/ws') {
    return wsServer.handleConnection(request);
  }

  if (url.pathname === '/stats') {
    return new Response(JSON.stringify(wsServer.getStats(), null, 2), {
      headers: { 'content-type': 'application/json' },
    });
  }

  if (url.pathname === '/') {
    return new Response(`
      <!DOCTYPE html>
      <html>
        <head><title>Deno WebSocket Chat</title></head>
        <body>
          <div id="messages"></div>
          <input type="text" id="nameInput" placeholder="Enter your name" />
          <input type="text" id="messageInput" placeholder="Type a message" />
          <button onclick="sendMessage()">Send</button>
          
          <script>
            const ws = new WebSocket('ws://localhost:8000/ws');
            let clientName = '';
            
            ws.onmessage = (event) => {
              const data = JSON.parse(event.data);
              const messages = document.getElementById('messages');
              messages.innerHTML += '<div>' + JSON.stringify(data) + '</div>';
            };
            
            function sendMessage() {
              const nameInput = document.getElementById('nameInput');
              const messageInput = document.getElementById('messageInput');
              
              if (nameInput.value && nameInput.value !== clientName) {
                clientName = nameInput.value;
                ws.send(JSON.stringify({ type: 'set_name', name: clientName }));
              }
              
              if (messageInput.value) {
                ws.send(JSON.stringify({ type: 'chat_message', message: messageInput.value }));
                messageInput.value = '';
              }
            }
          </script>
        </body>
      </html>
    `, {
      headers: { 'content-type': 'text/html' },
    });
  }

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

console.log('WebSocket server running on http://localhost:8000');
Deno.serve({ port: 8000 }, handler);

文件系统和数据处理

文件操作

// file-operations.ts
import { ensureDir, exists } from '@std/fs';
import { join } from '@std/path';

class FileManager {
  private baseDir: string;

  constructor(baseDir: string) {
    this.baseDir = baseDir;
  }

  async init() {
    await ensureDir(this.baseDir);
  }

  async writeFile(filename: string, content: string | Uint8Array): Promise<void> {
    const filePath = join(this.baseDir, filename);
    await ensureDir(join(this.baseDir, '..', filename, '..'));
    
    if (typeof content === 'string') {
      await Deno.writeTextFile(filePath, content);
    } else {
      await Deno.writeFile(filePath, content);
    }
  }

  async readFile(filename: string): Promise<string> {
    const filePath = join(this.baseDir, filename);
    return await Deno.readTextFile(filePath);
  }

  async readBinaryFile(filename: string): Promise<Uint8Array> {
    const filePath = join(this.baseDir, filename);
    return await Deno.readFile(filePath);
  }

  async fileExists(filename: string): Promise<boolean> {
    const filePath = join(this.baseDir, filename);
    return await exists(filePath);
  }

  async deleteFile(filename: string): Promise<void> {
    const filePath = join(this.baseDir, filename);
    await Deno.remove(filePath);
  }

  async listFiles(): Promise<string[]> {
    const files: string[] = [];
    
    for await (const entry of Deno.readDir(this.baseDir)) {
      if (entry.isFile) {
        files.push(entry.name);
      }
    }
    
    return files;
  }

  async getFileInfo(filename: string) {
    const filePath = join(this.baseDir, filename);
    const stat = await Deno.stat(filePath);
    
    return {
      name: filename,
      size: stat.size,
      isFile: stat.isFile,
      isDirectory: stat.isDirectory,
      created: stat.birthtime,
      modified: stat.mtime,
      accessed: stat.atime,
    };
  }

  async watchFiles(callback: (event: Deno.FsEvent) => void) {
    const watcher = Deno.watchFs(this.baseDir);
    
    for await (const event of watcher) {
      callback(event);
    }
  }
}

// 使用示例
async function fileOperationsDemo() {
  const fileManager = new FileManager('./data');
  await fileManager.init();

  // 写入文件
  await fileManager.writeFile('config.json', JSON.stringify({
    name: 'Deno App',
    version: '1.0.0',
    features: ['fast', 'secure', 'modern'],
  }, null, 2));

  // 读取文件
  const config = JSON.parse(await fileManager.readFile('config.json'));
  console.log('Config:', config);

  // 列出文件
  const files = await fileManager.listFiles();
  console.log('Files:', files);

  // 文件信息
  for (const file of files) {
    const info = await fileManager.getFileInfo(file);
    console.log(`File: ${file}`, info);
  }

  // 监听文件变化
  console.log('Watching for file changes...');
  await fileManager.watchFiles((event) => {
    console.log('File event:', event);
  });
}

数据库集成

// database.ts - SQLite 数据库操作
import { DB } from 'https://deno.land/x/sqlite@v3.8.0/mod.ts';

interface User {
  id?: number;
  name: string;
  email: string;
  createdAt?: string;
}

class UserDatabase {
  private db: DB;

  constructor(dbPath: string) {
    this.db = new DB(dbPath);
    this.init();
  }

  private init() {
    // 创建用户表
    this.db.execute(`
      CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT UNIQUE NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
      )
    `);

    // 创建索引
    this.db.execute(`
      CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)
    `);
  }

  createUser(user: User): User {
    const result = this.db.query(
      'INSERT INTO users (name, email) VALUES (?, ?) RETURNING *',
      [user.name, user.email]
    );

    if (result.length > 0) {
      const row = result[0];
      return {
        id: row[0] as number,
        name: row[1] as string,
        email: row[2] as string,
        createdAt: row[3] as string,
      };
    }

    throw new Error('Failed to create user');
  }

  getUserById(id: number): User | null {
    const result = this.db.query(
      'SELECT * FROM users WHERE id = ?',
      [id]
    );

    if (result.length > 0) {
      const row = result[0];
      return {
        id: row[0] as number,
        name: row[1] as string,
        email: row[2] as string,
        createdAt: row[3] as string,
      };
    }

    return null;
  }

  getUserByEmail(email: string): User | null {
    const result = this.db.query(
      'SELECT * FROM users WHERE email = ?',
      [email]
    );

    if (result.length > 0) {
      const row = result[0];
      return {
        id: row[0] as number,
        name: row[1] as string,
        email: row[2] as string,
        createdAt: row[3] as string,
      };
    }

    return null;
  }

  getAllUsers(): User[] {
    const result = this.db.query('SELECT * FROM users ORDER BY created_at DESC');
    
    return result.map(row => ({
      id: row[0] as number,
      name: row[1] as string,
      email: row[2] as string,
      createdAt: row[3] as string,
    }));
  }

  updateUser(id: number, updates: Partial<User>): User | null {
    const fields: string[] = [];
    const values: any[] = [];

    if (updates.name) {
      fields.push('name = ?');
      values.push(updates.name);
    }

    if (updates.email) {
      fields.push('email = ?');
      values.push(updates.email);
    }

    if (fields.length === 0) {
      throw new Error('No fields to update');
    }

    values.push(id);

    const result = this.db.query(
      `UPDATE users SET ${fields.join(', ')} WHERE id = ? RETURNING *`,
      values
    );

    if (result.length > 0) {
      const row = result[0];
      return {
        id: row[0] as number,
        name: row[1] as string,
        email: row[2] as string,
        createdAt: row[3] as string,
      };
    }

    return null;
  }

  deleteUser(id: number): boolean {
    const result = this.db.query('DELETE FROM users WHERE id = ?', [id]);
    return result.length > 0;
  }

  searchUsers(query: string): User[] {
    const result = this.db.query(
      'SELECT * FROM users WHERE name LIKE ? OR email LIKE ? ORDER BY created_at DESC',
      [`%${query}%`, `%${query}%`]
    );

    return result.map(row => ({
      id: row[0] as number,
      name: row[1] as string,
      email: row[2] as string,
      createdAt: row[3] as string,
    }));
  }

  getUserStats() {
    const totalUsers = this.db.query('SELECT COUNT(*) as count FROM users')[0][0];
    const recentUsers = this.db.query(
      'SELECT COUNT(*) as count FROM users WHERE created_at > datetime("now", "-7 days")'
    )[0][0];

    return {
      total: totalUsers,
      recentWeek: recentUsers,
    };
  }

  close() {
    this.db.close();
  }
}

// 使用示例
async function databaseDemo() {
  const db = new UserDatabase('./users.db');

  try {
    // 创建用户
    const user1 = db.createUser({
      name: 'Alice Johnson',
      email: 'alice@example.com',
    });
    console.log('Created user:', user1);

    const user2 = db.createUser({
      name: 'Bob Smith',
      email: 'bob@example.com',
    });
    console.log('Created user:', user2);

    // 查询用户
    const foundUser = db.getUserByEmail('alice@example.com');
    console.log('Found user:', foundUser);

    // 更新用户
    if (foundUser) {
      const updatedUser = db.updateUser(foundUser.id!, {
        name: 'Alice Johnson-Smith',
      });
      console.log('Updated user:', updatedUser);
    }

    // 获取所有用户
    const allUsers = db.getAllUsers();
    console.log('All users:', allUsers);

    // 搜索用户
    const searchResults = db.searchUsers('alice');
    console.log('Search results:', searchResults);

    // 统计信息
    const stats = db.getUserStats();
    console.log('User stats:', stats);

  } finally {
    db.close();
  }
}

测试和质量保证

单元测试

// user.test.ts
import { assertEquals, assertThrows } from '@std/assert';
import { UserDatabase } from './database.ts';

Deno.test('UserDatabase - Create User', () => {
  const db = new UserDatabase(':memory:');
  
  const user = db.createUser({
    name: 'Test User',
    email: 'test@example.com',
  });

  assertEquals(user.name, 'Test User');
  assertEquals(user.email, 'test@example.com');
  assertEquals(typeof user.id, 'number');
  
  db.close();
});

Deno.test('UserDatabase - Duplicate Email', () => {
  const db = new UserDatabase(':memory:');
  
  db.createUser({
    name: 'User 1',
    email: 'duplicate@example.com',
  });

  assertThrows(() => {
    db.createUser({
      name: 'User 2',
      email: 'duplicate@example.com',
    });
  });
  
  db.close();
});

Deno.test('UserDatabase - Get User by ID', () => {
  const db = new UserDatabase(':memory:');
  
  const created = db.createUser({
    name: 'Find Me',
    email: 'findme@example.com',
  });

  const found = db.getUserById(created.id!);
  
  assertEquals(found?.name, 'Find Me');
  assertEquals(found?.email, 'findme@example.com');
  
  db.close();
});

Deno.test('UserDatabase - Search Users', () => {
  const db = new UserDatabase(':memory:');
  
  db.createUser({ name: 'Alice Johnson', email: 'alice@example.com' });
  db.createUser({ name: 'Bob Smith', email: 'bob@example.com' });
  db.createUser({ name: 'Charlie Brown', email: 'charlie@example.com' });

  const results = db.searchUsers('alice');
  assertEquals(results.length, 1);
  assertEquals(results[0].name, 'Alice Johnson');
  
  db.close();
});

集成测试

// server.test.ts
import { assertEquals } from '@std/assert';

Deno.test('HTTP Server - GET /', async () => {
  const response = await fetch('http://localhost:8000/');
  const text = await response.text();
  
  assertEquals(response.status, 200);
  assertEquals(text, 'Hello, Deno Server! 🦕');
});

Deno.test('HTTP Server - GET /api/time', async () => {
  const response = await fetch('http://localhost:8000/api/time');
  const data = await response.json();
  
  assertEquals(response.status, 200);
  assertEquals(typeof data.timestamp, 'number');
  assertEquals(typeof data.iso, 'string');
});

Deno.test('HTTP Server - POST /api/users', async () => {
  const userData = {
    name: 'Test User',
    email: 'test@example.com',
  };

  const response = await fetch('http://localhost:8000/api/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(userData),
  });

  const result = await response.json();
  
  assertEquals(response.status, 201);
  assertEquals(result.user.name, userData.name);
  assertEquals(result.user.email, userData.email);
});

性能测试

// benchmark.ts
import { BenchmarkTimer } from '@std/testing/bench';

Deno.bench('Array creation and manipulation', () => {
  const arr = new Array(1000);
  for (let i = 0; i < 1000; i++) {
    arr[i] = i * 2;
  }
  return arr.reduce((sum, val) => sum + val, 0);
});

Deno.bench('String concatenation', () => {
  let result = '';
  for (let i = 0; i < 1000; i++) {
    result += `Item ${i} `;
  }
  return result;
});

Deno.bench('JSON parse/stringify', () => {
  const obj = {
    name: 'Test',
    items: Array.from({ length: 100 }, (_, i) => ({ id: i, value: i * 2 })),
  };
  
  const json = JSON.stringify(obj);
  return JSON.parse(json);
});

// 运行基准测试
// deno bench benchmark.ts

部署和生产环境

Docker 部署

# Dockerfile
FROM denoland/deno:1.38.0

# 设置工作目录
WORKDIR /app

# 复制依赖文件
COPY deno.json deno.lock ./

# 缓存依赖
RUN deno cache --lock=deno.lock deno.json

# 复制源代码
COPY . .

# 缓存应用依赖
RUN deno cache main.ts

# 暴露端口
EXPOSE 8000

# 启动应用
CMD ["deno", "run", "--allow-net", "--allow-read", "--allow-env", "main.ts"]

系统服务配置

# /etc/systemd/system/deno-app.service
[Unit]
Description=Deno Application
After=network.target

[Service]
Type=simple
User=deno
WorkingDirectory=/opt/deno-app
ExecStart=/home/deno/.deno/bin/deno run --allow-net --allow-read --allow-env main.ts
Restart=always
RestartSec=10
Environment=NODE_ENV=production
Environment=PORT=8000

[Install]
WantedBy=multi-user.target

环境配置

// config.ts
interface Config {
  port: number;
  database: {
    path: string;
  };
  cors: {
    origin: string[];
  };
  logging: {
    level: 'debug' | 'info' | 'warn' | 'error';
  };
}

function loadConfig(): Config {
  const env = Deno.env.get('DENO_ENV') || 'development';
  
  const baseConfig: Config = {
    port: parseInt(Deno.env.get('PORT') || '8000'),
    database: {
      path: Deno.env.get('DB_PATH') || './app.db',
    },
    cors: {
      origin: (Deno.env.get('CORS_ORIGIN') || 'http://localhost:3000').split(','),
    },
    logging: {
      level: (Deno.env.get('LOG_LEVEL') || 'info') as Config['logging']['level'],
    },
  };

  // 环境特定配置
  if (env === 'production') {
    baseConfig.logging.level = 'warn';
  }

  return baseConfig;
}

export const config = loadConfig();

总结

Deno 的核心优势和最佳实践:

🎯 核心优势

  1. 安全性:默认安全,显式权限控制
  2. 现代化:原生 TypeScript 支持,Web 标准 API
  3. 简单性:无需 package.json,URL 导入
  4. 工具链:内置格式化、测试、打包工具

✅ 适用场景

  • 现代 Web API 开发
  • 微服务和 Serverless 应用
  • 命令行工具和脚本
  • 安全要求高的应用
  • TypeScript 优先的项目

🚀 最佳实践

  • 合理使用权限系统
  • 利用内置工具链
  • 遵循 Web 标准
  • 编写全面的测试
  • 使用 deno.json 配置项目

💡 开发建议

  • 从小项目开始学习 Deno
  • 充分利用 TypeScript 特性
  • 关注 Deno 生态系统发展
  • 考虑与 Node.js 的兼容性需求

掌握 Deno,拥抱现代 JavaScript/TypeScript 开发!


Deno 代表了 JavaScript 运行时的未来方向,提供了更安全、更现代的开发体验。