- 发布于
Deno 运行时深度解析:现代 JavaScript/TypeScript 运行环境的完整指南
- 作者

- 姓名
- 全能波
- GitHub
- @weicracker
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 的核心优势和最佳实践:
🎯 核心优势
- 安全性:默认安全,显式权限控制
- 现代化:原生 TypeScript 支持,Web 标准 API
- 简单性:无需 package.json,URL 导入
- 工具链:内置格式化、测试、打包工具
✅ 适用场景
- 现代 Web API 开发
- 微服务和 Serverless 应用
- 命令行工具和脚本
- 安全要求高的应用
- TypeScript 优先的项目
🚀 最佳实践
- 合理使用权限系统
- 利用内置工具链
- 遵循 Web 标准
- 编写全面的测试
- 使用 deno.json 配置项目
💡 开发建议
- 从小项目开始学习 Deno
- 充分利用 TypeScript 特性
- 关注 Deno 生态系统发展
- 考虑与 Node.js 的兼容性需求
掌握 Deno,拥抱现代 JavaScript/TypeScript 开发!
Deno 代表了 JavaScript 运行时的未来方向,提供了更安全、更现代的开发体验。