Deno 的 deno compile 命令可以将 TypeScript/JavaScript 代码编译为独立的可执行文件,这使得分发和部署变得更加简单。这个功能对于创建命令行工具和独立应用程序特别有用。
deno compile 概述
deno compile 将 Deno 脚本及其所有依赖打包成一个单一的可执行文件,无需在目标机器上安装 Deno。
基本用法
1. 简单编译
bash# 编译为可执行文件 deno compile app.ts # 默认输出文件名与输入文件相同(无扩展名) # app.ts → app (Linux/Mac) 或 app.exe (Windows)
2. 指定输出文件名
bash# 指定输出文件名 deno compile --output=myapp app.ts # Windows deno compile --output=myapp.exe app.ts
3. 指定目标平台
bash# 编译为 Linux 可执行文件 deno compile --target=x86_64-unknown-linux-gnu app.ts # 编译为 macOS 可执行文件 deno compile --target=x86_64-apple-darwin app.ts deno compile --target=aarch64-apple-darwin app.ts # Apple Silicon # 编译为 Windows 可执行文件 deno compile --target=x86_64-pc-windows-msvc app.ts
4. 包含权限
bash# 编译时包含权限,运行时无需再次指定 deno compile --allow-net --allow-read app.ts # 包含所有权限 deno compile --allow-all app.ts
5. 设置环境变量
bash# 设置编译时的环境变量 deno compile --env=API_KEY=secret app.ts
实际应用示例
1. 命令行工具
typescript// cli.ts #!/usr/bin/env -S deno run const name = Deno.args[0] || "World"; const verbose = Deno.args.includes("--verbose"); if (verbose) { console.log("Verbose mode enabled"); } console.log(`Hello, ${name}!`); // 文件操作示例 const filename = Deno.args[1]; if (filename) { try { const content = await Deno.readTextFile(filename); console.log(`File content: ${content}`); } catch (error) { console.error(`Error reading file: ${error.message}`); } }
编译:
bashdeno compile --allow-read --output=hello cli.ts ./hello Deno --verbose
2. Web 服务器
typescript// server.ts import { serve } from "https://deno.land/std@0.208.0/http/server.ts"; const PORT = Deno.env.get("PORT") || "8000"; const handler = async (req: Request): Promise<Response> => { const url = new URL(req.url); if (url.pathname === "/") { return new Response("Hello from Deno server!", { headers: { "Content-Type": "text/plain" }, }); } if (url.pathname === "/health") { return new Response(JSON.stringify({ status: "ok" }), { headers: { "Content-Type": "application/json" }, }); } return new Response("Not Found", { status: 404 }); }; console.log(`Server running on port ${PORT}`); await serve(handler, { port: parseInt(PORT) });
编译:
bashdeno compile --allow-net --allow-env --output=server server.ts ./server
3. 文件处理工具
typescript// file-processor.ts import { walk } from "https://deno.land/std@0.208.0/fs/walk.ts"; const directory = Deno.args[0] || "."; const pattern = Deno.args[1]; console.log(`Scanning directory: ${directory}`); if (pattern) { console.log(`Pattern: ${pattern}`); } let count = 0; for await (const entry of walk(directory)) { if (entry.isFile) { if (!pattern || entry.name.includes(pattern)) { console.log(`Found: ${entry.path}`); count++; } } } console.log(`Total files found: ${count}`);
编译:
bashdeno compile --allow-read --output=scan file-processor.ts ./scan ./src .ts
4. API 客户端
typescript// api-client.ts const API_URL = Deno.env.get("API_URL") || "https://api.example.com"; const API_KEY = Deno.env.get("API_KEY"); if (!API_KEY) { console.error("API_KEY environment variable is required"); Deno.exit(1); } async function fetchData(endpoint: string): Promise<any> { const response = await fetch(`${API_URL}${endpoint}`, { headers: { "Authorization": `Bearer ${API_KEY}`, "Content-Type": "application/json", }, }); if (!response.ok) { throw new Error(`API request failed: ${response.status}`); } return response.json(); } const endpoint = Deno.args[0] || "/users"; console.log(`Fetching data from: ${API_URL}${endpoint}`); try { const data = await fetchData(endpoint); console.log(JSON.stringify(data, null, 2)); } catch (error) { console.error(`Error: ${error.message}`); Deno.exit(1); }
编译:
bashdeno compile --allow-net --allow-env --output=api-client api-client.ts API_KEY=your_key ./api-client /users
高级用法
1. 交叉编译
在 macOS 上编译 Linux 可执行文件:
bashdeno compile --target=x86_64-unknown-linux-gnu --output=myapp-linux app.ts
2. 压缩可执行文件
使用 upx 压缩编译后的可执行文件:
bash# 安装 upx brew install upx # macOS apt install upx # Linux # 压缩 upx --best myapp
3. 创建图标(仅限 Windows)
bash# 为 Windows 可执行文件添加图标 deno compile --icon=icon.ico --output=myapp.exe app.ts
4. 设置元数据
bash# 设置可执行文件元数据 deno compile \ --output=myapp \ --app-name="My Application" \ --app-version="1.0.0" \ app.ts
部署场景
1. Docker 部署
dockerfile# 多阶段构建 FROM denoland/deno:1.38.0 AS builder WORKDIR /app COPY . . RUN deno compile --allow-net --allow-read --output=app server.ts FROM debian:bullseye-slim WORKDIR /app COPY /app/app . EXPOSE 8000 CMD ["./app"]
2. 云函数部署
typescript// cloud-function.ts export async function handler(event: any): Promise<any> { const name = event.name || "World"; return { statusCode: 200, body: JSON.stringify({ message: `Hello, ${name}!` }), }; }
编译:
bashdeno compile --output=handler cloud-function.ts
3. 独立应用分发
bash# 为不同平台编译 deno compile --target=x86_64-unknown-linux-gnu --output=myapp-linux app.ts deno compile --target=x86_64-apple-darwin --output=myapp-macos app.ts deno compile --target=x86_64-pc-windows-msvc --output=myapp-windows.exe app.ts # 创建发布包 mkdir release cp myapp-linux release/ cp myapp-macos release/ cp myapp-windows.exe release/
限制和注意事项
1. 文件大小
编译后的可执行文件通常较大(约 50-100 MB),因为包含了整个 Deno 运行时和 V8 引擎。
2. 动态导入
动态导入的模块会在运行时下载,不会被包含在可执行文件中:
typescript// 这个模块不会被打包 const module = await import("https://example.com/module.ts");
3. 原生模块
使用 FFI 的原生模块需要确保目标平台上有相应的库。
4. 权限
编译时指定的权限在运行时生效,无法在运行时更改。
最佳实践
- 指定目标平台:明确指定编译目标,避免兼容性问题
- 最小权限:只授予必要的权限
- 测试编译结果:在目标平台上测试可执行文件
- 版本控制:记录编译命令和 Deno 版本
- 文档化:在 README 中说明如何编译和运行
- 使用 CI/CD:自动化编译和发布流程
与其他工具对比
| 特性 | deno compile | pkg | nexe |
|---|---|---|---|
| 原生支持 | 是 | 否 | 否 |
| 跨平台 | 是 | 是 | 是 |
| 文件大小 | 较大 | 中等 | 中等 |
| 性能 | 好 | 好 | 好 |
| 维护性 | 官方支持 | 社区维护 | 社区维护 |
故障排查
1. 编译失败
bash# 查看详细错误信息 deno compile --log-level=debug app.ts
2. 运行时错误
bash# 检查权限 deno compile --allow-net --allow-read app.ts # 检查环境变量 API_KEY=secret ./app
3. 兼容性问题
bash# 检查目标平台 deno compile --target=x86_64-unknown-linux-gnu app.ts # 使用 Docker 测试 docker run --rm -v $(pwd):/app debian:bullseye-slim ./app
deno compile 为 Deno 应用程序提供了简单而强大的分发方式,特别适合需要独立部署的场景。通过合理使用这个功能,可以大大简化应用程序的部署和分发流程。