尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Deno 入门与实战:安全沙箱、TypeScript 原生支持与单文件部署

Deno 入门与实战:安全沙箱、TypeScript 原生支持与单文件部署 之前在业务迭代里需要一套同时兼顾安全沙箱、原生 TypeScript 和现代化 Web API 的服务端运行时对比了 Node.js 的各类替代方案后最终还是把目光落在了 Deno 上。Deno 是 Ryan Dahl 在 Node.js 之后发起的新一代 JavaScript/TypeScript 运行时从架构层面解决了 Node 早期遗留下来的一系列问题。本文会完整拆解 Deno 的核心概念、环境搭建、权限模型、HTTP 服务实战、单文件部署方案以及 Deno Desktop 这类桌面端方向新手可以用来快速上手后端开发者也能从中找到工程化落地的关键思路。1. 什么是 DenoNode.js 作者的下一次探索1.1 从 Node.js 到 Deno 的演进逻辑Deno 是 Ryan Dahl 在 2018 年正式对外公开的新一代 JavaScript/TypeScript 运行时底层使用 Rust 编写内核基于 Tokio 异步运行时和 V8 引擎。它的名字来自 Node 字母重新排列寓意“对 Node 的重新思考”。Ryan Dahl 在演讲中反思了 Node.js 早期设计中的几个遗憾模块系统过于依赖 npm 中心化仓库、默认权限过于宽松导致第三方包可以随意访问网络和文件系统、构建工具链分散且版本管理复杂。Deno 的诞生正是为了回答一个问题如果在今天重新设计一个 JavaScript 运行时应该是什么样的很多人把 Deno 简单理解为“下一代 Node.js”这个说法其实并不准确。Deno 在设计目标上更接近一个“安全的 JavaScript/TypeScript 执行环境”而不是某个框架的升级版。它原生支持 TypeScript内置模块缓存机制支持从 URL 直接导入模块还提供了权限沙箱、标准库和去中心化的模块分发方式。这些特性加在一起让 Deno 在处理脚本工具、微服务、云函数和桌面应用时都表现出了独特的优势。1.2 Deno 解决的核心问题Deno 最核心的改进可以归纳为四个维度第一是安全性。Node.js 中require(fs)之后就可以随意读写文件http模块启动后就能监听端口这些能力默认全部开放。Deno 反其道而行默认情况下脚本没有任何文件、网络、环境变量等系统权限必须通过命令行参数显式授予。这种“默认最小权限”的设计大大降低了恶意依赖或者误操作造成的风险。第二是 TypeScript 原生支持。在 Node.js 生态里使用 TypeScript需要额外安装 TypeScript 编译器、配置 tsconfig.json、设置 ts-node 或 tsx 等工具流程比较繁琐。Deno 内部内置了 TypeScript 编译支持.ts文件可以直接运行不需要任何额外配置Deno 会自动完成类型检查、转译和缓存。第三是模块系统的现代化。Deno 支持通过 URL 导入模块如import { serve } from https://deno.land/std0.224.0/http/server.ts也兼容 npm 包通过npm:前缀引入。这种设计摆脱了中心化包管理器的束缚也让模块版本管理变得更加透明。第四是标准库与内置工具链。Deno 内置了测试、格式化、打包、文档生成等工具不需要像 Node.js 那样在项目里分别安装jest、eslint、prettier等一堆依赖。官方标准库deno_std覆盖了 HTTP 服务、文件操作、加密、日志等常见场景可以直接复用。1.3 Deno 与 Node.js 的核心对比为了让读者更直观地理解两者差异我把关键维度整理成了下表对比维度Node.jsDeno语言支持JavaScriptTypeScript 需额外编译JavaScript 和 TypeScript 原生支持底层实现CRust模块系统CommonJS / ESMnpm 中心化仓库ESMURL 导入 npm 兼容权限模型默认完全开放默认无权限显式授权包管理package.json node_modulesimport map 远程缓存单文件可执行需要借助 pkg 等第三方工具内置deno compile异步模型基于事件循环基于 Tokio 异步运行时标准库依赖 npm 社区包官方 deno_std 标准库这里需要注意Deno 和 Node.js 并不是“非此即彼”的关系。在现有 Node.js 项目中完全没有必要为了“换框架”而迁移到 Deno。更合理的策略是新项目、微服务、工具脚本、云函数或者对安全性要求较高的场景可以优先评估 Deno存量 Node.js 项目则继续发挥其成熟的生态优势。2. 环境准备安装 Deno 并确认版本2.1 不同操作系统下的安装方式Deno 的安装方式非常简洁官方针对不同平台都提供了对应的命令行安装脚本。我这里以主流操作系统为例说明macOS 或 Linux 系统推荐使用 Homebrew 安装brew install deno如果不希望依赖 Homebrew可以通过官方安装脚本安装curl -fsSL https://deno.land/install.sh | shWindows 环境下可以使用 PowerShell 执行安装脚本irm https://deno.land/install.ps1 | iex安装完成后在终端执行下面命令验证是否安装成功deno --version正常会输出类似如下内容deno 2.0.0 (release, x86_64-apple-darwin) v8 13.0.245.6 typescript 5.6.2这里需要提醒一下Deno 的迭代速度较快具体版本号请以你安装时的最新稳定版为准。建议在生产环境固定 Deno 版本避免因为自动升级导致行为变化。2.2 升级与多版本管理Deno 提供了自升级命令执行以下命令可以升级到最新稳定版deno upgrade如果需要安装指定版本可以使用下面的命令deno upgrade --version 1.46.3对于需要管理多个项目、多种 Deno 版本的情况推荐使用dvmDeno Version Manager。安装和使用方式如下# 安装 dvm curl -fsSL https://deno.land/x/dvm/install.sh | sh # 安装指定版本 dvm install 2.0.0 # 切换版本 dvm use 2.0.02.3 IDE 与编辑器支持Deno 官方提供了 VS Code 扩展插件名称是Deno for Visual Studio Code。安装后在项目的.vscode/settings.json中加入如下配置可以启用当前工作区的 Deno 语言服务{ deno.enable: true, deno.unstable: true, deno.importMap: ./import_map.json }配置说明deno.enable开启当前项目的 Deno 语言服务。deno.unstable开启 Deno 不稳定 API如果项目用到实验性功能。deno.importMap指定导入映射文件路径便于管理模块别名。如果不使用 VS Code也可以通过命令行工具配合通用编辑器完成开发Deno 本身提供了类型检查和格式化能力。3. Deno 核心特性与基础用法拆解3.1 原生 TypeScript零配置直接运行Deno 最吸引人的一点是省去了 TypeScript 工程化的前期配置。新建一个hello.ts文件// hello.ts interface User { name: string age: number } const user: User { name: Deno, age: 6 } console.log(Hello ${user.name}, Age: ${user.age})直接运行deno run hello.ts输出Hello Deno, Age: 6在 Node.js 项目中要实现同样的效果通常需要安装typescript、ts-node或tsx创建tsconfig.json偶尔还要处理 ESM 与 CommonJS 的兼容问题。Deno 把这些步骤全部收拢到了运行时内部开发者只需要关心代码本身。这种“开箱即用”的体验在写脚本、做原型验证或者维护小工具时非常高效。3.2 权限沙箱机制默认什么都不能做Deno 的权限模型是它和其他运行时最大的区别。来看一个简单示例// read-file.ts const content await Deno.readTextFile(./data.txt) console.log(content)直接运行deno run read-file.tsDeno 会抛出类似下面的权限错误error: Uncaught (in promise) PermissionDenied: Requires read access to ./data.txt, run again with the --allow-read flag这时需要显式授予文件读取权限deno run --allow-read --allow-write read-file.tsDeno 支持的主要权限标志如下权限标志作用--allow-read允许读取文件系统--allow-write允许写入文件系统--allow-net允许网络访问--allow-env允许访问环境变量--allow-run允许创建子进程--allow-sys允许获取系统信息--allow-all允许所有权限不推荐生产环境使用更进一步Deno 还支持细粒度的权限控制。例如只允许访问某个目录和某几个域名deno run --allow-read/tmp --allow-netapi.example.com server.ts这种细粒度授权方式让开发者可以在运行时明确声明“我的脚本只需要哪些能力”既方便审计也能有效降低供应链攻击风险。实际项目里建议不要图省事直接--allow-all而是按需授予最小权限。3.3 模块解析方式URL 导入与 npm 兼容Deno 的模块系统基于 ECMAScript ModulesESM支持通过 URL 直接导入远程模块。例如从官方标准库导入 HTTP 服务模块// deps.ts export { serve } from https://deno.land/std0.224.0/http/server.ts在业务代码中引用它// main.ts import { serve } from ./deps.ts这种“URL 即依赖声明”的方式比 package.json 里的版本范围更透明因为模块内容就是从那个 URL 获取的版本一目了然。Deno 在首次导入时会下载模块并缓存到本地后续运行不需要重复下载。对于 npm 包Deno 2.x 提供了更完善的兼容方案。可以通过npm:前缀引入import express from npm:express5.1.0 const app express() app.get(/, (_req, res) { res.send(Hello from Deno Express!) }) app.listen(3000)运行命令deno run --allow-net --allow-env --allow-read npm-app.ts这种兼容模式让 Deno 可以复用 npm 生态中的大量现有库。不过需要注意不是所有 npm 包都能在 Deno 下正常工作某些依赖 Node 原生扩展的包可能会有兼容性问题需要结合实验验证。3.4 配置管理与 import map当项目模块越来越多时纯 URL 导入的方式会显得冗长且难以维护。Deno 支持使用import map对导入路径进行统一管理。创建一个import_map.json文件{ imports: { std/http/: https://deno.land/std0.224.0/http/, oak: https://deno.land/x/oakv17.1.4/mod.ts } }然后在代码中引用import { serve } from std/http/server.ts import { Application } from oak运行deno run --import-mapimport_map.json main.ts这样做的好处是所有依赖集中在单独的文件里管理升级某个依赖时只需要修改import_map.json一处代码里的导入语句保持稳定。实际项目中可以把import_map.json、deps.ts结合使用形成清晰的依赖管理层。3.5 面向桌面场景Deno 的另一种可能性“deno desktop”这个方向是社区比较关注的一个话题。简单来说它指的是把 Deno 作为桌面应用的后端运行时再结合 WebView 或前端框架来完成界面渲染从而使用 Web 技术构建跨平台桌面应用。目前社区已经有相关方向的探索常见的思路是用 Rust 或 Go 编写一个 shell 窗口壳内部嵌入 WebView然后在主进程里调用 Deno 来执行业务逻辑。Deno 本身提供了 FFIForeign Function Interface能力可以通过Deno.dlopen加载本地动态链接库这样就能在 Deno 脚本里调用系统级 API从而扩展桌面应用的能力。下面是一个最小示意具体 API 需按实际使用的桌面框架调整// ffi-example.ts const libPath ./native/libnative.dylib const lib Deno.dlopen(libPath, { add: { parameters: [i32, i32], result: i32 } }) const result lib.symbols.add(2, 3) console.log(FFI result:, result)需要强调的是Deno 官方目前没有像 Electron 那样提供完整的桌面应用解决方案。如果你想尝试 Deno 桌面开发建议优先从 FFI、嵌入式 HTTP 服务、系统命令调用这几个方向入手把它作为一个“本地服务的后端引擎”来用前端表达交给 WebView 或原生 UI 框架完成。4. 完整实战构建一个带鉴权的 Deno HTTP 服务下面我们从一个实际需求出发完整走一遍 Deno 的 HTTP 服务开发流程。需求如下提供一个/api/hello接口返回 JSON 数据。提供一个/api/user接口要求请求头携带 token简单校验后返回用户信息。提供静态文件服务能访问public目录下的页面。通过import_map.json管理依赖。4.1 创建项目结构先规划目录结构deno-demo/ ├── import_map.json ├── deps.ts ├── main.ts ├── public/ │ └── index.html └── .envmain.ts是服务入口public目录存放静态资源。4.2 定义依赖映射创建import_map.json{ imports: { std/: https://deno.land/std0.224.0/, dotenv: https://deno.land/std0.224.0/dotenv/mod.ts } }这里使用std/前缀统一指向 Deno 官方标准库后续代码中只需要写std/http/server.ts这种简洁路径。4.3 编写服务端代码创建main.tsimport { serve } from std/http/server.ts import { load } from std/dotenv/mod.ts import { createServer } from std/http/server.ts import { join } from std/path/mod.ts // 加载 .env 文件中的环境变量 const env await load() const PORT Number(env[PORT] || 8000) const API_TOKEN env[API_TOKEN] || dev-token // 简单 token 校验函数 function checkToken(request: Request): boolean { const authHeader request.headers.get(Authorization) || return authHeader Bearer ${API_TOKEN} } // 生成 JSON 响应 function jsonResponse(data: unknown, status 200): Response { return new Response(JSON.stringify(data), { status, headers: { Content-Type: application/json } }) } // 路由分发 async function handleRequest(request: Request): PromiseResponse { const url new URL(request.url) const pathname url.pathname // 健康检查接口 if (pathname /api/hello request.method GET) { return jsonResponse({ message: Hello Deno, timestamp: new Date().toISOString() }) } // 需要鉴权的接口 if (pathname /api/user request.method GET) { if (!checkToken(request)) { return jsonResponse({ error: Unauthorized }, 401) } const userInfo { id: 1, name: Deno User, role: admin } return jsonResponse(userInfo) } // 静态文件服务 if (request.method GET) { const publicDir join(Deno.cwd(), public) const filePath pathname / ? join(publicDir, index.html) : join(publicDir, pathname) try { const file await Deno.open(filePath) const contentType filePath.endsWith(.html) ? text/html; charsetutf-8 : application/octet-stream return new Response(file.readable, { headers: { Content-Type: contentType } }) } catch (_error) { return jsonResponse({ error: Not Found }, 404) } } return jsonResponse({ error: Method Not Allowed }, 405) } // 启动服务 const server serve(handleRequest, { port: PORT }) console.log( Server running on http://localhost:${PORT}) // 优雅退出 Deno.addSignalListener(SIGINT, () { console.log(Server shutting down...) server.shutdown() Deno.exit(0) })代码中的几个关键点serve函数接受一个请求处理函数作为参数这是 Deno 标准库提供的最小 HTTP 服务抽象。Response和Request都是 Web 标准 APIDeno 直接实现了这些标准开发者不需要学习额外的框架 API。静态文件服务使用Deno.open读取文件把文件流直接作为响应体返回性能表现不错。4.4 添加配置文件与环境变量创建.env文件PORT8000 API_TOKENmy-secret-token需要说明的是生产环境不要使用这种明文 token 方式更推荐利用密钥管理服务或者在部署平台配置环境变量代码中只负责读取。4.5 创建前端页面创建public/index.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleDeno Demo/title /head body h1Deno HTTP Server/h1 p idmessageLoading.../p script fetch(/api/hello) .then(res res.json()) .then(data { document.getElementById(message).textContent data.message }) /script /body /html4.6 运行与验证在项目根目录执行deno run --import-mapimport_map.json --allow-net --allow-read --allow-env main.ts注意这里需要授予三个权限--allow-net监听端口、--allow-read读取静态文件、--allow-env读取.env文件。启动后打开另一个终端分别验证接口# 验证 /api/hello curl http://localhost:8000/api/hello # 不带 token 访问 /api/user应该返回 401 curl http://localhost:8000/api/user # 带 token 访问 /api/user应该返回用户信息 curl -H Authorization: Bearer my-secret-token http://localhost:8000/api/user预期输出分别如下。/api/hello的返回结果{message:Hello Deno,timestamp:2025-01-01T12:00:00.000Z}不带 token 访问/api/user的返回结果{error:Unauthorized}带 token 访问/api/user的返回结果{id:1,name:Deno User,role:admin}浏览器访问http://localhost:8000/可以看到页面加载后自动显示接口返回的消息。5. 单文件部署使用 deno compile 打包应用Deno 提供了一个非常有用的功能deno compile可以将整个应用打包成单个可执行文件。这个特性在部署脚本、分发 CLI 工具、交付离线应用时非常实用。5.1 打包命令在项目根目录执行deno compile --import-mapimport_map.json --allow-net --allow-read --allow-env -o deno-demo main.ts参数说明-o deno-demo指定输出可执行文件的名称。--import-map打包时使用同一个导入映射文件。各--allow-*参数在编译时声明运行时需要的权限最终可执行文件会自动带上这些权限声明。执行完成后当前目录会生成一个大小为几十 MB 的可执行文件具体大小取决于平台和依赖。此时直接运行./deno-demo就能启动服务甚至可以不依赖本机安装 Deno。5.2 使用场景与注意事项deno compile适合以下场景分发给其他开发者使用的 CLI 工具对方无需安装 Deno。部署到容器镜像中镜像里不需要再包含 Deno 运行时。批量脚本和数据分析工具编译后即开即用。需要注意的是deno compile对动态导入支持有限如果代码中使用了import()动态加载模块打包时可能会遇到问题。建议在编译前进行充分测试。6. 常见问题与排查思路由于 Deno 迭代速度快、生态和 Node.js 有差异新手在实践过程中容易遇到一些问题。下面整理了几个高频问题。问题现象常见原因解决思路运行时报 PermissionDenied未授予文件、网络、环境变量等权限根据提示补充对应的--allow-*参数导入模块时报 Not Found依赖 URL 拼写错误或版本不存在确认deno.land/std或 npm 包版本号是否存在TypeScript 类型检查不通过依赖的第三方库类型定义不完整使用--no-check跳过类型检查验证运行情况但生产环境建议保留检查deno compile后静态资源找不到编译后的可执行文件工作目录变化使用import.meta.dirname定位真实资源路径或把资源文件一并分发npm 包导入后报错包依赖 Node 原生模块或__dirname等 Node 专有变量优先选择纯 JS 实现的替代包或使用npm:兼容模式加--node-modules-dir端口被占用操作系统存在其他进程监听同一端口检查端口状态调整PORT环境变量中文乱码静态文件响应头缺少 charset设置Content-Type: text/html; charsetutf-8排查建议遵循以下顺序先看权限错误再看模块缓存最后检查代码逻辑。Deno 有比较清晰的命令行输出大部分错误信息会直接指出缺失的权限和依赖按提示修改即可。7. 工程实践与生产落地建议7.1 依赖管理统一收敛到 deps.ts在实际项目中不建议散落地在业务文件里写长 URL 导入。推荐把所有外部依赖统一收口到deps.ts或import_map.json中。这样升级依赖时只需要改动一个文件也方便后续做依赖审计。示例deps.ts// deps.ts export { serve } from std/http/server.ts export { load } from std/dotenv/mod.ts export { join } from std/path/mod.ts业务代码只需要从相对路径引用import { serve, join } from ./deps.ts7.2 权限最小化生产环境部署时严格按照“只授予必要权限”的原则配置权限参数。比如一个服务只需要监听端口和读取配置文件那就不要加上--allow-write和--allow-run。Deno 在启动时声明的权限可以配合 CI/CD 流水线做静态检查让服务和运维同学都能清楚知道每个服务拥有哪些系统能力。7.3 错误处理与日志HTTP 服务中要重点处理两类错误一类是请求参数异常另一类是运行时异常。建议在路由入口统一捕获异常返回结构化的 JSON 错误响应async function handleRequest(request: Request): PromiseResponse { try { return await routeHandler(request) } catch (error) { console.error(Request failed:, error) return jsonResponse({ error: Internal Server Error }, 500) } }日志方面Deno 标准库没有提供特别重量级的日志方案小型服务可以直接使用console.log生产环境可以接入标准输出日志采集再交给外部日志平台统一处理。7.4 与现有 Node.js 生态共存大多数团队都保留了存量 Node.js 项目让 Deno 完全替代 Node.js 并不现实。比较务实的做法是让 Deno 承担新项目、工具脚本、云函数、边缘计算这些对安全性、启动速度、类型支持要求更高的场景存量项目继续稳定运行。Deno 对 npm 包的兼容已经做了很多工作团队可以在小范围内先试点一个非核心服务验证依赖兼容性和团队上手速度后再扩大范围。7.5 版本锁定与安全审计Deno 官方提供了deno.lock锁文件功能用于锁定依赖版本和校验哈希。默认情况下Deno 在第一次解析依赖后会自动生成锁文件建议把它提交到版本库保证团队所有成员的依赖完全一致。关于安全审计Deno 生态还没有形成像 npmnpm audit那样非常统一的安全审计命令但依赖来源透明、权限最小化、统一 deps 管理这些手段已经可以在很大程度上降低风险。如果使用 npm 包可以结合静态扫描工具做检查。8. 总结与后续学习方向这篇文章从 Deno 的设计初衷讲起对比了它和 Node.js 的核心差异然后逐步完成了 Deno 安装、TypeScript 原生运行、权限模型解析、模块管理、HTTP 服务实战、单文件编译部署以及常见问题排查。相信你已经能明显感受到 Deno 的几个关键词安全、现代、开箱即用。下一步可以根据自己的兴趣继续深入。想强化 Web 服务开发可以研究oak、hono等 Deno 生态的 Web 框架想了解云端部署可以尝试deno deploy和边缘运行时相关实践想探索桌面方向可以研究 Deno FFI 和 WebView 结合的方案想深入底层则可以阅读 Deno 的 Rust 源码和 Tokio 异步模型。如果这篇文章对你有帮助建议收藏备用。动手写第一个 Deno 服务比想象中简单权限参数试错几次就能理解它的设计逻辑祝你玩得开心。
返回列表