标签

Deno

Deno 是一个简单、现代且安全的 JavaScript 和 TypeScript 运行时环境,由 Node.js 的创始人 Ryan Dahl 开发,目标是解决 Node.js 的一些设计缺陷。Deno 于2020年正式发布,它内置了 V8 JavaScript 引擎和 Tokio 事件循环,提供了一系列默认的安全限制,并支持 TypeScript 的运行而无需额外的转译步骤。

Deno
服务端5月31日 17:42
Deno 为什么能直接运行 TypeScript?类型检查怎么配?Deno 对 TypeScript 的支持不是“帮你装好了 ts-node”这么简单,而是把 TypeScript 文件作为一等公民纳入运行时流程。你可以直接运行 `.ts` 文件,也可以单独做类型检查、配置编译选项、缓存远程依赖类型。它适合写脚本、服务端 API、边缘函数和共享工具库,但仍然需要理解类型检查和运行性能之间的取舍。 ## 追问 ### Deno 运行 TypeScript 时到底发生了什么? 当你执行 `.ts` 文件时,Deno 会解析依赖图,把 TypeScript 转成 JavaScript 后交给 V8 执行,并把结果缓存起来。后续运行同一份代码时,如果依赖和配置没有变化,启动成本会低很多。取舍是首次运行或依赖变更时会有编译成本,所以大型项目不要把“直接运行”理解成完全没有构建开销。踩坑点是远程依赖版本不固定会导致缓存变化,建议在导入地址、npm specifier 或 lock 文件里锁住版本。 ```bash deno run src/main.ts deno cache src/main.ts deno run --reload src/main.ts ``` ### deno check 和 deno run --check 有什么区别? `deno check` 只做类型检查,不执行代码,适合放在 CI 或提交前检查里。`deno run --check` 会在运行前做类型检查,更适合本地调试关键入口。取舍是每次运行都检查会更稳,但反馈速度可能变慢;开发期可以按场景选择,CI 必须保留明确的类型检查步骤。常见坑是以为 `deno run` 默认总会拦住所有类型错误,实际项目最好显式运行 `deno check`,避免配置或版本差异造成误判。 ```bash deno check src/main.ts deno run --check --allow-net src/main.ts ``` ### Deno 还需要 tsconfig.json 吗? 很多简单项目不需要单独的 `tsconfig.json`,一个 `deno.json` 就能管理 tasks、imports、fmt、lint 和 compilerOptions。这样配置集中,脚本和服务端项目会清爽很多。边界是如果你和 Node.js、前端框架或 monorepo 共用 TypeScript 配置,仍然可能需要兼容既有 tsconfig。踩坑是直接复制 Node 项目的 tsconfig,里面的 `moduleResolution`、路径别名或类型声明未必适合 Deno,迁移时要逐项确认。 ```json { "compilerOptions": { "strict": true, "lib": ["deno.ns", "dom", "dom.iterable"] }, "imports": { "@std/assert": "jsr:@std/assert@1" } } ``` ### 第三方库的类型怎么处理? Deno 导入的现代 ESM 模块通常自带类型,JSR 和不少 npm 包也能直接获得类型提示。遇到纯 JavaScript 或类型不完整的库,可以用声明文件补类型,或者换成类型更完整的依赖。取舍是补声明能快速推进业务,但长期看会增加维护成本,尤其是库升级后声明可能和真实行为不一致。踩坑点是 npm 包如果依赖 Node 专有 API,类型能过不代表运行能过,还要验证运行时兼容性。 ```ts import { assertEquals } from "jsr:@std/assert@1"; import express from "npm:express@4"; assertEquals(typeof express, "function"); ``` ### 严格类型会不会拖慢开发? 短期看,`strict: true` 会让你多处理空值、联合类型和外部输入校验,确实比随手写 `any` 慢。长期看,它能把很多线上问题提前变成编辑器和 CI 的提示,尤其适合 API 参数、配置文件和数据转换代码。取舍是原型验证阶段可以局部放宽,但核心模块不要长期依赖 `any`,否则 TypeScript 只剩语法高亮。常见坑是只定义接口不做运行时校验,外部 JSON 进来仍然可能是错的,必要时要配合 zod 这类校验库。 ## 小结 Deno 的 TypeScript 体验好在默认路径短:写 `.ts`、跑 `deno check`、用 `deno task` 固化流程。真正稳定的项目还需要锁依赖、明确 compilerOptions,并把类型检查放进 CI;这样才能享受少配置,而不是把隐患藏到运行时。
服务端5月31日 17:42
Deno 内置工具怎么用?哪些场景能少装依赖?Deno 的一个明显特点是把格式化、检查、测试、文档、性能测试和编译都放进了 CLI。对小团队来说,这意味着项目刚创建时不用先决定 ESLint、Prettier、Jest、ts-node、打包器分别怎么配。它不是要替代所有前端工程工具,而是把 TypeScript 服务端、脚本和库开发中最常见的流程先收拢起来。 ## 追问 ### deno fmt 和 deno lint 能替代 Prettier、ESLint 吗? 在纯 Deno 项目里,大多数情况下可以直接用 `deno fmt` 和 `deno lint`,它们开箱即用,团队不用争论缩进、引号和基础规则。取舍是规则可配置空间没有 ESLint 那么大,如果项目有大量自定义 lint 规则、React 特定规则或历史代码风格,迁移时会有摩擦。边界也很清楚:Deno 工具更适合统一风格和抓常见问题,不适合承载复杂业务规范。常见坑是把 `deno fmt` 直接作用到整个仓库,结果格式化了不该改的生成文件,所以要在 `deno.json` 里排除目录。 ```json { "fmt": { "exclude": ["dist", "coverage"] }, "lint": { "exclude": ["dist", "vendor"] } } ``` ```bash deno fmt --check deno lint ``` ### deno test 适合测哪些代码? `deno test` 适合测试工具函数、HTTP handler、Deno 服务端模块和不依赖浏览器 DOM 的逻辑。它支持权限参数,所以测试里如果要读文件或访问本地端口,需要显式声明,这能逼你看清测试到底碰了哪些资源。取舍是它不像 Jest 那样自带庞大的生态和快照习惯,迁移 React 组件测试时未必划算。踩坑点是异步测试忘记 `await` 或测试权限没写全,表现可能像代码错了,其实是运行命令不完整。 ```ts import { assertEquals } from "https://deno.land/std@0.224.0/assert/mod.ts"; Deno.test("sum works", () => { assertEquals(1 + 2, 3); }); ``` ```bash deno test --allow-read=./fixtures ``` ### deno task 有什么实际价值? `deno task` 类似 npm scripts,用来把常用命令写进 `deno.json`,让本地和 CI 使用同一套入口。它的价值不是功能多,而是减少“你本地怎么跑”的沟通成本。取舍是跨运行时项目仍然可能需要 pnpm、make 或 turborepo,Deno task 更适合 Deno 子项目内部统一命令。常见坑是把权限参数散落在文档里,最后 CI 忘加权限,最好统一写到 task 中。 ```json { "tasks": { "dev": "deno run --watch --allow-net --allow-read src/main.ts", "check": "deno fmt --check && deno lint && deno test --allow-read" } } ``` ```bash deno task check ``` ### deno compile 什么时候有用? `deno compile` 可以把脚本打成单个可执行文件,适合内部 CLI、运维工具和发给非前端同事使用的小程序。好处是对方不需要先安装 Deno,也不用理解依赖缓存。边界是产物体积通常不小,且动态读取文件、远程导入和运行时权限需要提前想清楚。踩坑最多的是编译时没有把所需权限和目标平台写进去,结果本地能跑,换机器后访问网络或文件失败。 ```bash deno compile --allow-read --allow-net --output bin/tool src/cli.ts ``` ### deno doc 和 deno bench 值得放进日常流程吗? 如果你在写共享库或内部 SDK,`deno doc` 很值得用,因为它能从类型和 JSDoc 直接生成 API 文档,减少文档和代码不一致。`deno bench` 适合比较关键函数的性能变化,但不应该拿一次本机结果做绝对结论。取舍是文档和基准测试都会增加维护成本,只有公共 API、核心算法或性能敏感路径才值得固定下来。踩坑是 benchmark 受机器负载影响很大,最好比较同一环境下的相对变化,而不是追求漂亮数字。 ## 小结 Deno 内置工具最适合解决“项目启动阶段工具太多”的问题。先用 `fmt + lint + test + task` 建立基本质量线,需要分发 CLI 时再考虑 `compile`,需要维护库时再补 `doc` 和 `bench`,这样既能少装依赖,也不会把工具链用过头。
服务端5月31日 17:42
Deno 和 Node.js 到底有什么区别?项目该怎么选?Deno 和 Node.js 都能运行 JavaScript,也都基于 V8,但它们解决问题的默认姿势不一样。Node.js 的优势是生态成熟、npm 包足够多、线上案例多;Deno 的优势是默认安全、原生 TypeScript、工具链内置,并且更贴近 Web 标准。真正选型时,不要只看“新不新”,要看项目依赖、团队经验、部署环境和安全边界。 ## 追问 ### Deno 和 Node.js 的模块管理差在哪? Node.js 早期以 CommonJS 为主,现在也支持 ES Modules,所以老项目里经常同时看到 `require` 和 `import`。Deno 默认使用 ES Modules,可以直接从 URL、npm specifier 或 JSR 导入依赖,不强制依赖 `node_modules`。这种方式的取舍很明显:Deno 的依赖入口更透明,适合小工具和新项目;Node.js 的 npm 生态更大,遇到冷门 SDK 时更稳。踩坑点是 Deno 的远程 URL 一定要锁版本,否则上游变更可能让构建不可复现。 ```ts import { serve } from "https://deno.land/std@0.224.0/http/server.ts"; import chalk from "npm:chalk@5"; serve(() => new Response(chalk.green("hello from Deno"))); ``` ### Deno 默认安全是什么意思? Node.js 程序默认可以读文件、访问网络、读取环境变量,权限通常靠容器、系统用户或部署平台隔离。Deno 反过来,脚本默认没有文件、网络和环境变量权限,运行时必须显式加 `--allow-read`、`--allow-net` 等参数。这个设计适合运行第三方脚本、CLI 插件或自动化任务,因为权限边界写在命令里。边界是后端服务最终仍要访问数据库、Redis 和配置,权限参数会变多,团队需要把它们固化到 `deno.json` 或部署脚本里。 ```bash deno run main.ts deno run --allow-net=api.example.com --allow-read=./config main.ts ``` ### TypeScript 支持是不是 Deno 一定更好? Deno 可以直接运行 `.ts` 文件,不需要先安装 `typescript`、`ts-node` 或维护一套复杂构建配置。它对写脚本、边缘函数和小型服务很舒服,类型检查和运行命令都比较直接。取舍在于大型 Node.js 项目通常已有成熟的 tsconfig、构建缓存、路径别名和框架插件,迁到 Deno 未必省事。另一个常见坑是“能运行”不等于“已经类型检查”,实际 CI 里仍建议单独跑 `deno check`,避免为了启动速度跳过类型问题。 ```bash deno check src/main.ts deno run --check src/main.ts ``` ### 生态差距会影响真实项目吗? 会,而且是最现实的差距。Node.js 的 npm 包数量、企业 SDK、监控探针、ORM、框架插件都非常丰富,遇到支付、云厂商、旧系统集成时更容易找到现成方案。Deno 已经支持很多 npm 包,但不是所有包都能无缝运行,尤其是依赖 Node 原生模块、文件系统假设或构建脚本的包。项目如果强依赖复杂 npm 生态,Node.js 通常成本更低;如果是 API 服务、脚本平台、边缘函数或 TypeScript 优先的新工具,Deno 的开发体验更清爽。 ### 线上项目应该怎么选? 已有 Node.js 项目不要为了追新整体迁移,除非你能明确量化安全、部署或工具链收益。新项目如果依赖 Express/NestJS、Prisma 复杂插件或大量 npm 中间件,Node.js 仍然是稳妥选择。Deno 适合权限边界清晰、依赖少、希望减少工具链配置的项目,例如内部 CLI、Webhook 服务、轻量 API 和自动化脚本。真正的踩坑不是选错运行时,而是没有把依赖锁定、权限参数、CI 检查和部署镜像写清楚。 ## 小结 Node.js 像一座成熟城市,路网复杂但配套齐全;Deno 更像规划更现代的新区,默认规则更干净,但有些店还没开。选择时先列出项目必须依赖的包、运行权限、团队维护成本和部署平台支持,再决定运行时,比单纯比较性能或语法更靠谱。
服务端5月31日 17:20
Deno 的 Worker 任务系统怎么用?适合哪些并行场景?Deno 里常说的任务系统,实际落到代码里主要是两件事:用 `deno task` 管理项目脚本,用 `Worker` 把耗时计算放到独立线程。原文讨论的是后台异步任务,更准确地说是 Worker 模型。Worker 有自己的执行上下文和内存空间,不能直接共享主线程变量,只能靠 `postMessage` 传数据。它适合 CPU 密集型或可拆分的批处理,不适合把普通 I/O 都丢进去;如果只是请求接口或读几个小文件,Worker 的创建和序列化成本可能比收益还高。 这个区别很重要,因为很多文章会把 “task” 和 “worker” 混着讲。面试或项目评审时,应该先说明你讨论的是哪一层:命令编排层的 `deno task`,还是运行时并行层的 Worker。前者解决“怎么启动和约束命令”,后者解决“怎么让重计算不堵住主线程”。两者都能提升工程体验,但解决的问题完全不同。 ## 追问 ### Deno 里怎么启动一个 Worker? 主线程用 `new Worker(new URL("./worker.ts", import.meta.url).href, { type: "module" })` 创建 Worker,然后通过 `postMessage` 发消息。Worker 侧监听 `self.onmessage`,处理完再 `self.postMessage` 返回结果。这里的边界是消息会被结构化克隆,函数、类实例方法、部分复杂对象不能像普通引用一样传过去。踩坑点是路径最好用 `new URL(..., import.meta.url)`,不要依赖当前工作目录,否则测试和生产启动目录一变就找不到文件。如果 Worker 文件还需要读本地模块或资源,主进程启动时仍然要给对应权限。权限不是因为进入 Worker 就自动放开,Deno 仍然会按运行命令的授权范围执行,这是它和一些传统脚本环境不一样的地方。 ```ts // main.ts const worker = new Worker(new URL("./worker.ts", import.meta.url).href, { type: "module", }); worker.onmessage = (event) => { console.log(event.data); worker.terminate(); }; worker.onerror = (event) => { console.error(event.message); worker.terminate(); }; worker.postMessage({ number: 21 }); ``` ```ts // worker.ts self.onmessage = (event) => { const { number } = event.data; self.postMessage({ result: number * 2 }); }; ``` ```bash deno run --allow-read main.ts ``` ### Worker 适合处理什么任务,不适合处理什么任务? Worker 适合图像像素处理、加密哈希、大数组计算、日志离线分析、批量文本转换这类 CPU 时间明显的任务。它不适合非常短的小任务,因为创建线程、加载模块、传递消息都有固定成本。也不适合需要频繁共享状态的逻辑,主线程和 Worker 来回通信太密会把性能优势吃掉。取舍上可以先在主线程写清楚逻辑,再用实际耗时数据决定是否拆到 Worker,而不是一开始就把架构做复杂。还有一个判断标准是数据传输成本:如果每次都要把几十 MB 的对象来回复制,Worker 的收益会明显下降。能用 `ArrayBuffer` 等可转移对象时,应该优先考虑转移所有权,减少复制带来的内存压力。 ```ts // worker.ts self.onmessage = (event) => { const nums = event.data as number[]; const sum = nums.reduce((acc, n) => acc + n, 0); self.postMessage(sum); }; ``` ### 如何把 Worker 封装成 Promise,避免回调散在业务里? 真实项目里通常不会在业务函数里到处写 `onmessage` 和 `onerror`,而是封装一个 `runWorker`,让调用方像等待普通异步函数一样等待结果。这样错误处理、超时、终止 Worker 都可以集中维护。边界是每次调用都新建 Worker 会更简单但成本更高,适合低频任务;高频任务应该考虑 Worker 池。最容易踩的坑是成功或失败后忘记 `terminate()`,导致后台线程一直占着资源,CI 或长驻服务里会越来越慢。封装时还可以加超时控制,避免 Worker 卡死后 Promise 永远不返回。超时后要主动终止 Worker,并把任务标记为失败或进入重试队列,否则调用方会以为系统只是“还在处理”。 ```ts export function runWorker<T>(file: string, data: unknown): Promise<T> { return new Promise((resolve, reject) => { const worker = new Worker(new URL(file, import.meta.url).href, { type: "module" }); worker.onmessage = (e) => { resolve(e.data as T); worker.terminate(); }; worker.onerror = (e) => { reject(e); worker.terminate(); }; worker.postMessage(data); }); } ``` ### 并行处理时为什么不能无限开 Worker? Worker 不是越多越快,它会占用线程、内存和调度资源。CPU 密集型任务通常按机器核心数附近控制并发,比如 4 核机器开 4 个左右,再多可能只是上下文切换变多。批量文件处理还要考虑磁盘 I/O,开太多 Worker 可能把磁盘打满,反而让整体变慢。更稳妥的做法是做一个简单队列或 Worker 池,让任务排队进入固定数量的 Worker。Worker 池的实现也别一开始就追求复杂调度,先做到固定并发、先进先出、失败可返回就够了。等确实遇到长短任务混排、优先级或取消需求,再增加队列策略会更稳。 ```ts const concurrency = Number(Deno.env.get("WORKER_CONCURRENCY") ?? 4); const chunks = [/* split big data here */]; for (let i = 0; i < chunks.length; i += concurrency) { const batch = chunks.slice(i, i + concurrency); await Promise.all(batch.map((chunk) => runWorker("./worker.ts", chunk))); } ``` ```bash WORKER_CONCURRENCY=4 deno run --allow-read --allow-env main.ts ``` ### Deno 的 deno task 和 Worker 是一回事吗? 不是。`deno task` 是项目脚本系统,类似 npm scripts,用来定义 `test`、`dev`、`build` 这类命令;Worker 是运行时代码里的并行执行机制。两者可以配合,例如用 `deno task process` 启动一个会创建 Worker 池的批处理程序。面试里如果把二者混为一谈,通常说明只看过标题没真正写过 Deno。项目里建议把常用命令写进 `deno.json`,把 Worker 并发、权限和入口文件固定下来,减少同事之间“我这里能跑”的差异。另外,`deno task` 里写权限时要尽量精确,比如只允许读输入目录、只允许写输出目录。把权限写进脚本后,团队成员运行同一个任务时环境更一致,也更容易在代码审查里发现权限扩大。 ```json { "tasks": { "process": "deno run --allow-read --allow-write --allow-env main.ts", "test": "deno test --allow-read" } } ``` Deno 的 Worker 任务模型真正有价值的地方,是把重计算从主流程里拆出去,同时保留清晰的权限和启动命令。用之前先判断任务是否足够重、数据是否好切分、失败后是否能重试,比盲目并行更重要。
服务端5月31日 17:20
Deno 的测试框架怎么用?异步、权限和覆盖率怎么处理?Deno 的测试框架是内置能力,不需要先装 Jest、Mocha 这类第三方 Runner。核心用法是用 `Deno.test()` 注册测试,用标准库里的断言函数验证结果,再通过 `deno test` 运行。它的特点不是“功能最多”,而是和运行时权限、TypeScript、覆盖率、并发执行放在同一套命令里管理。真正写项目时,重点不只是会写一个 `assertEquals`,还要知道异步资源怎么清理、权限怎么最小化、哪些测试适合并行,哪些测试必须隔离。 如果把 Deno 测试只理解成“能跑断言”,很容易写出本地偶尔通过、CI 经常失败的测试。Deno 的测试设计更强调运行时约束:测试代码默认没有额外权限,未关闭的异步操作会被检查,覆盖率也不需要额外插件。这些默认值会让刚上手的人觉得严格,但对长期维护很友好,因为很多隐蔽问题会在测试阶段就暴露出来。 ## 追问 ### 最小的 Deno 测试应该怎么写? 最小测试通常放在 `*_test.ts` 或 `.test.ts` 文件里,Deno 会自动发现这些文件。断言建议优先从 `jsr:@std/assert` 引入,旧项目里也可能看到 `https://deno.land/std/.../asserts.ts`,两者不要在同一个项目里混着升级。测试名要描述行为,而不是写成 `test add` 或 `works`,否则失败时排查成本很高。边界上,`assertEquals` 会做深比较,适合对象和数组;如果要比较同一个引用,才用 `assertStrictEquals`。实际项目里还要注意导入路径的稳定性,特别是从远程 URL 迁移到 JSR 或 npm 兼容包时,锁文件和版本号要一起维护。否则同一段测试在不同机器上可能解析到不同版本的断言库,报错信息也会不一致。 ```ts import { assertEquals } from "jsr:@std/assert"; function add(a: number, b: number) { return a + b; } Deno.test("add returns sum of two numbers", () => { assertEquals(add(1, 2), 3); assertEquals(add(-1, 1), 0); }); ``` ```bash deno test deno test src/math_test.ts deno test --filter="add returns" ``` ### 异步测试、异常测试和资源清理有什么坑? 异步测试的函数本身要 `async`,并且必须 `await` 被测 Promise,否则测试可能在异步错误抛出前就结束。同步异常用 `assertThrows`,Promise 拒绝用 `assertRejects`,这两个不要混用。Deno 默认会检查未关闭的 op、资源和异常退出,这比很多测试框架严格,也更容易暴露真实问题。踩坑最多的是启动 HTTP server、文件句柄或数据库连接后忘了在 `finally` 里关闭,短期看只是测试失败,长期看会让 CI 随机挂。如果确实有长连接或后台计时器无法在测试结束前自然关闭,可以临时关闭 `sanitizeOps` 或 `sanitizeResources`,但这应该是最后手段。更好的做法是把启动和关闭封装成工具函数,让每个测试都能明确释放资源。 ```ts import { assertRejects } from "jsr:@std/assert"; async function loadUser(id: number) { if (id <= 0) throw new Error("invalid id"); return { id }; } Deno.test("loadUser rejects invalid id", async () => { await assertRejects(() => loadUser(0), Error, "invalid id"); }); ``` ### 测试里需要读文件、访问网络时权限怎么给? Deno 的测试和运行代码一样受权限模型约束,读文件要 `--allow-read`,访问网络要 `--allow-net`。可以在命令行给权限,也可以在单个 `Deno.test` 的配置里声明权限;后者更适合单元测试,因为它能把权限边界写在测试旁边。取舍是命令行授权简单,适合本地临时跑;单测级授权更啰嗦,但 CI 和代码审查时更安全。不要为了省事长期使用 `--allow-all`,它会掩盖代码偷偷访问文件、环境变量或网络的行为。权限还会影响 Mock 策略:能用内存假对象替代真实文件和网络时,就不要为了测试方便放开系统权限。这样做的代价是多写一点测试替身,但收益是测试更快、更稳定,也更接近单元测试的边界。 ```ts Deno.test({ name: "reads fixture file", permissions: { read: ["./fixtures"] }, async fn() { const text = await Deno.readTextFile("./fixtures/user.json"); if (!text.includes("name")) throw new Error("bad fixture"); }, }); ``` ```bash deno test --allow-read=./fixtures deno test --allow-net=localhost:8000 ``` ### Deno 测试怎么组织才适合真实项目? 单元测试可以贴近源码放,例如 `src/user_test.ts`;集成测试可以放到 `tests/`,并在 `deno.json` 里统一配置 include 和 exclude。测试写法上推荐 Arrange、Act、Assert 三段式,但不用机械地写注释,关键是让准备数据、执行动作、验证结果一眼能分开。涉及数据库、临时目录、端口监听时,每个测试都要创建自己的隔离环境,不能依赖前一个测试留下的状态。并行执行能节省时间,但共享全局变量、固定端口、固定文件名的测试不适合直接并行。当测试之间共享 fixture 时,fixture 可以只读共享,但运行中产生的数据最好写入临时目录。固定写 `./tmp/result.json` 这类路径,在并行测试和重复运行时都容易互相污染。 ```json { "tasks": { "test": "deno test --allow-read=./fixtures", "test:ci": "deno test --coverage=coverage --fail-fast" }, "test": { "include": ["src/**/*_test.ts", "tests/**/*.ts"], "exclude": ["vendor/"] } } ``` ### 覆盖率和 CI 里应该关注什么? 覆盖率可以用 `deno test --coverage=coverage` 生成原始数据,再用 `deno coverage` 输出文本、LCOV 或 HTML。覆盖率适合发现“完全没测到”的分支,但不应该变成唯一目标,因为 90% 覆盖率也可能没有断言关键行为。CI 里更重要的是固定命令、固定权限、失败快速暴露,并把网络类测试和纯单元测试分开。一个常见取舍是本地默认跑快速单测,合并前或夜间任务再跑慢集成测试,这样不会让开发反馈变得太慢。覆盖率目录也要在 CI 中清理,避免上一次运行留下的数据影响这一次报告。对于分支很多的业务代码,可以把覆盖率报告当成提示,再回头检查断言是不是验证了业务结果,而不是只执行了代码。 ```bash deno task test:ci deno coverage coverage --lcov --output=coverage.lcov deno coverage coverage --html ``` Deno 测试框架的优势在于默认严格、命令少、和运行时边界一致。写好它的关键不是堆断言,而是把权限、资源清理、异步失败和测试隔离一起设计好。
服务端5月28日 06:55
Deno 的性能优化有哪些关键技巧?Deno 基于 Rust 和 V8 引擎构建,在运行时层面已经做了大量性能优化,但实际项目中如果不理解其底层机制,很容易写出低效的代码。Deno 的性能优化涉及启动加速、运行时调优、内存管理、并发模型和 I/O 处理等多个维度,掌握这些技巧是面试中的常见考察点。 ## Deno 的性能瓶颈通常出现在哪些环节? Deno 应用的性能瓶颈主要集中在三个环节:启动阶段的全量依赖加载、运行时的同步阻塞操作、以及内存中的大对象持有。面试中回答这个问题时,需要结合 Deno 的架构特点来分析——Deno 的 Rust 核心(crate deno_core)通过 opcall 机制与 V8 通信,每次 opcall 的开销在 2.x 版本中已优化到纳秒级,但频繁的跨边界调用仍然是性能敏感场景需要关注的重点。 ## 如何优化 Deno 的启动性能? Deno 的冷启动耗时主要花在模块解析和 TypeScript 编译上。优化启动性能的核心思路是减少启动时需要解析和编译的代码量。 **动态导入替代顶层全量加载**:将非必要依赖改为按需加载,避免启动时解析整个依赖树。 ```typescript // 顶层全量加载:启动时解析所有依赖 import { heavyProcessor } from "./heavy-processor.ts"; // 动态导入:只在需要时加载 async function processData(data: unknown) { const { heavyProcessor } = await import("./heavy-processor.ts"); return heavyProcessor(data); } ``` **利用 Deno 的编译缓存**:Deno 会将编译结果缓存在 `DENO_DIR` 目录中(默认 `~/.cache/deno`),二次启动直接读取缓存。在 CI/CD 环境中,可以通过挂载持久化的缓存目录来避免每次构建都重新编译。 **使用 `deno compile` 生成单文件可执行文件**:对于部署场景,`deno compile` 将运行时和应用代码打包成单文件,省去运行时的模块解析和编译开销,启动速度可提升 30% 以上。 **V8 Snapshot 机制**:Deno 在构建时就通过 V8 Snapshot 预先序列化了内置 API 的初始化状态,这意味着 `Deno.readFile`、`fetch` 等全局 API 在启动时无需重新初始化。自定义应用也可以利用 `deno_core` 的 snapshot 功能来预初始化重度依赖。 ## Deno 运行时有哪些关键的性能优化手段? 运行时优化的核心是减少不必要的计算开销和跨 Rust/V8 边界的调用次数。 **减少 opcall 频率**:Deno 通过 opcall(类似系统调用的机制)在 V8 和 Rust 之间通信。虽然 2.x 版本已经将单次 opcall 开销从早期的微秒级降到纳秒级(约 40ns),但在高频调用场景下仍然需要注意。例如,逐行读取文件比一次性读取产生更多的 opcall: ```typescript // 低效:多次 opcall const file = await Deno.open("large.txt"); const buf = new Uint8Array(1024); while (await file.read(buf)) { processChunk(buf); } // 高效:一次 opcall 读取全部内容 const content = await Deno.readTextFile("large.txt"); processContent(content); ``` **使用 Web 标准 API 而非 Deno 特有 API**:Deno 的 Web 标准 API(如 `fetch`、`ReadableStream`、`TextEncoder`)经过高度优化,优先使用这些 API 可以获得更好的性能和可移植性。 **TypeScript 运行时类型检查优化**:Deno 默认在开发模式下进行类型检查,但类型检查是 CPU 密集型操作。在生产环境中使用 `--no-check` 标志跳过类型检查,可显著减少启动和热重载时间: ```bash deno run --no-check app.ts ``` **选择高效的数据结构**:在频繁查找场景下,`Map` 和 `Set` 的性能优于普通对象,因为 V8 对 Map/Set 有专门的优化路径。 ## 如何处理 Deno 中的内存优化? Deno 的内存管理依赖 V8 的垃圾回收器,但应用层面的不当使用仍会导致内存泄漏和 GC 压力过大。 **及时释放资源**:Deno 的资源表(Resource Table)维护着所有打开的文件、网络连接等资源。未关闭的资源会持续占用文件描述符和内存,且资源表本身也会增长。始终在 `finally` 块中关闭资源: ```typescript const file = await Deno.open("data.txt"); try { const content = await Deno.readAll(file); return processContent(content); } finally { file.close(); // 确保资源从资源表中移除 } ``` **流式处理替代全量加载**:处理大文件时,使用 `ReadableStream` 进行流式处理,避免将整个文件加载到内存: ```typescript async function processLargeFile(path: string) { const file = await Deno.open(path); const stream = file.readable .pipeThrough(new TextDecoderStream()) .pipeThrough(new TransformStream({ transform(chunk, controller) { controller.enqueue(processChunk(chunk)); } })); for await (const result of stream) { handleResult(result); } } ``` **WeakRef 和 FinalizationRegistry**:对于缓存场景,使用 `WeakRef` 避免缓存对象阻止 GC 回收,配合 `FinalizationRegistry` 在对象被回收时清理关联资源。 **控制内存上限**:通过 `--v8-flags=--max-old-space-size=4096` 限制 V8 堆内存上限,防止内存泄漏导致进程 OOM。同时可以通过 `Deno.memoryUsage()` 监控内存使用情况。 ## Deno 的并发模型如何影响性能? Deno 的并发模型基于 V8 的事件循环,单线程内通过异步 I/O 实现非阻塞。对于 CPU 密集型任务,Deno 提供了 Web Worker 和 `Deno.UnsafelyUseFinalizationRegistry` 等机制。 **Web Worker 并行处理 CPU 密集型任务**: ```typescript // main.ts const worker = new Worker( new URL("./worker.ts", import.meta.url), { type: "module" } ); worker.postMessage({ data: largeDataset }); worker.onmessage = (e) => { console.log("Result:", e.data); }; ``` ```typescript // worker.ts self.onmessage = (e) => { const result = heavyComputation(e.data); self.postMessage(result); }; ``` **并发控制**:高并发场景下,无限制地发起异步操作会导致事件循环压力过大、文件描述符耗尽。需要实现并发控制器: ```typescript async function parallelLimit<T>( tasks: (() => Promise<T>)[], limit: number ): Promise<T[]> { const results: T[] = []; const executing = new Set<Promise<void>>(); for (const [index, task] of tasks.entries()) { const p = task().then((result) => { results[index] = result; executing.delete(p); }); executing.add(p); results[index] = undefined as T; if (executing.size >= limit) { await Promise.race(executing); } } await Promise.all(executing); return results; } ``` **Deno.serve 的并发性能**:Deno 2.x 中 `Deno.serve`(或标准库的 `serve`)是构建 HTTP 服务的主要 API,它基于 Rust 的 hyper 库实现,性能接近原生 HTTP 服务器。基准测试中,Deno 2.x 的 HTTP 吞吐量约 78k req/s(简单 JSON 响应),显著优于 Node.js 22 的约 65k req/s。 ## Deno 的 I/O 性能如何优化? I/O 是大多数 Web 应用的核心瓶颈。Deno 的 I/O 操作通过 Rust 异步运行时(tokio)实现,天然支持非阻塞 I/O。 **始终使用异步 I/O**:Deno 中同步 I/O API(如 `Deno.readTextFileSync`)会阻塞事件循环,仅适用于初始化阶段或 CLI 工具。生产环境必须使用异步版本。 **利用 HTTP/2**:Deno 的 `Deno.serve` 支持 HTTP/2(通过 ALPN 协商),多路复用特性可以在单个连接上并行传输多个请求,减少连接建立开销: ```typescript Deno.serve({ port: 8000, handler: async (req) => { return new Response(JSON.stringify({ status: "ok" }), { headers: { "Content-Type": "application/json" }, }); }, alpnProtocols: ["h2", "http/1.1"], }); ``` **响应压缩**:对响应体进行 gzip 或 brotli 压缩,显著减少传输体积。Deno 标准库提供了压缩工具: ```typescript import { gzip } from "https://deno.land/std@0.224.0/encoding/gzip.ts"; Deno.serve({ port: 8000, handler: async (req) => { const data = JSON.stringify(await getLargeDataset()); const acceptEncoding = req.headers.get("accept-encoding") ?? ""; if (acceptEncoding.includes("gzip")) { const compressed = await gzip(new TextEncoder().encode(data)); return new Response(compressed, { headers: { "Content-Encoding": "gzip", "Content-Type": "application/json", }, }); } return new Response(data, { headers: { "Content-Type": "application/json" }, }); }, }); ``` **KV 存储优化**:Deno KV 是内置的键值数据库,基于 SQLite 实现。在高写入场景下,使用原子事务批量写入,减少事务开销: ```typescript const kv = await Deno.openKv(); // 批量原子写入 const atomic = kv.atomic(); for (const entry of entries) { atomic.set(entry.key, entry.value); } await atomic.commit(); ``` ## 如何监控和诊断 Deno 的性能问题? 性能优化的前提是准确测量。Deno 提供了多种监控和诊断手段。 **Deno.memoryUsage()**:实时获取 RSS、堆内存使用量等指标,用于内存监控和泄漏检测: ```typescript const mem = Deno.memoryUsage(); console.log(`RSS: ${(mem.rss / 1024 / 1024).toFixed(2)} MB`); console.log(`Heap: ${(mem.heapUsed / 1024 / 1024).toFixed(2)} / ${(mem.heapTotal / 1024 / 1024).toFixed(2)} MB`); ``` **性能测量 API**:使用 Web 标准的 `performance.now()` 和 `PerformanceObserver` 进行精确计时: ```typescript const start = performance.now(); await someOperation(); const duration = performance.now() - start; console.log(`Operation took ${duration.toFixed(2)}ms`); ``` **V8 内置分析器**:通过 `--v8-flags` 启用 V8 的 CPU 分析器和堆分析器: ```bash # CPU 分析 deno run --v8-flags=--prof app.ts # 堆快照 deno run --v8-flags=--heap-prof app.ts ``` **Deno 命令行工具**:`deno bench` 用于基准测试,`deno task` 用于任务编排。基准测试示例: ```typescript // bench.ts Deno.bench("JSON parse 10k items", () => { JSON.parse(largeJsonString); }); Deno.bench("Map vs Object lookup", () => { userMap.get("key-9999"); }); ``` **权限对性能的影响**:Deno 的安全模型要求运行时检查权限,在高频 I/O 场景下会产生可测量的开销。如果确认环境安全,可以在生产环境中通过 `--allow-all` 减少权限检查开销,但需权衡安全性和性能。 掌握以上优化技巧,需要理解一个核心原则:Deno 的性能优势来自 Rust 层的高效实现和 V8 的 JIT 优化,应用层的优化应围绕减少不必要的跨层调用、避免阻塞事件循环、合理管理内存和资源展开。面试中回答 Deno 性能优化问题时,结合底层原理和实际场景给出具体方案,比罗列通用优化技巧更有说服力。
服务端5月27日 20:26
什么是 Deno?它和 Node.js 有什么区别?## Deno 的核心设计理念 Deno 由 Node.js 创始人 Ryan Dahl 在 2018 年发起,动机是他公开承认 Node.js 有几个设计决策无法在不破坏兼容性的前提下修复:默认不安全的执行环境、混乱的 node_modules 机制、以及 CommonJS 与 ES Modules 并存的模块系统。Deno 从零开始,用 Rust 重写了底层,试图给出更干净的答案。 ## Deno 和 Node.js 的关键区别 **安全模型**:Deno 默认沙箱执行,脚本无法读写文件、访问网络或读取环境变量,必须通过 `--allow-read`、`--allow-net` 等标志显式授权。Node.js 默认完全信任脚本,没有权限墙。 **模块与包管理**:Deno 用 URL 直接导入模块,不依赖 package.json 和 node_modules。Deno 2.0 起 `npm:` 前缀已稳定支持,可以 `import express from "npm:express"` 直接使用 npm 包。Node.js 仍以 npm + package.json 为核心。 **TypeScript**:Deno 原生执行 .ts 文件,零配置。Node.js 22 虽已实验性支持,但生产环境仍需配置转译。 **API 风格**:Deno 全部采用 Promise/async-await,Node.js 保留了大量回调风格的旧 API。 **底层实现**:Deno 基于 Rust + V8,Node.js 基于 C++ + V8 + libuv。 ```bash # Deno 权限示例 deno run --allow-net --allow-read server.ts ``` ## 2026 年选型建议 - **选 Node.js**:已有大型项目、依赖 npm 生态深度、团队招聘池大 - **选 Deno**:新项目优先安全(如跑不可信代码)、边缘部署(Deno Deploy)、TypeScript-first - **考虑 Bun**:CI/CD 追求极致速度、高吞吐 HTTP 场景 ## 追问方向 - Deno 的权限模型能防范供应链攻击吗?-- 能限制恶意包的文件和网络访问,但 `--allow-all` 等于没有限制 - Deno 2.0 的 npm 兼容性有没有坑?-- 大部分主流包可用,但依赖原生 C++ 模块的包可能失败 - 为什么 Ryan Dahl 认为node_modules是个错误?-- 它导致幽灵依赖、磁盘浪费、安装慢,Deno 用全局缓存 + URL 导入替代
服务端5月27日 20:20
Deno 生态有哪些主流库和工具?Deno 2.x 的生态已从早期的 deno.land/x 过渡到 JSR(JavaScript Registry)作为主力包注册表,同时通过 `npm:` 前缀直接引用约 98% 的 npm 包。Web 框架首选 Hono(轻量、跨 Deno/Bun/Cloudflare Workers 运行时)和 Oak(Koa 风格中间件),Fresh 是官方全栈框架但社区活跃度一般。数据库层 Drizzle ORM 是目前最成熟的 TypeScript ORM,支持 PostgreSQL/MySQL/SQLite。Deno KV 是内置键值存储,零依赖适合轻量场景。认证用 djwt 处理 JWT,@deno/kv-oauth 做 OAuth2。工具链全部内置——fmt、lint、test、compile、doc 一条命令搞定,Deno 2 还新增了 pack、bump-version、ci 等子命令。部署用 Deno Deploy,免费额度每天 10 万请求、35+ 边缘节点零冷启动。 ## 追问 ### JSR 和 npm 有什么区别? JSR 是 Deno 主导的 TypeScript 优先注册表,源码直接发 TS 无需编译,自动为 npm 生态生成兼容包。npm 以 CommonJS/编译后 JS 为主。Deno 两个都能用:JSR 用 `jsr:` 前缀,npm 用 `npm:` 前缀。新项目优先发 JSR,兼容两边的开发者。 ### Hono 和 Oak 怎么选? Hono 更轻量、跨运行时,中间件生态丰富,适合 API 和边缘计算场景。Oak 是 Deno 专属框架,API 接近 Koa 学习成本低,但生态不如 Hono。新项目建议 Hono,已有 Koa 经验的团队可以快速上手 Oak。 ### Deno KV 能替代 Redis 吗? 轻量场景可以:键值读写、原子操作、版本控制都支持,零依赖开箱即用。但不支持 TTL 自动过期、没有 pub/sub、查询只有前缀扫描。需要过期策略或发布订阅时还是得接 Redis。 ### 从 Node.js 迁移到 Deno 难吗? Deno 2 已支持 ~98% 的 npm 包,大部分依赖加 `npm:` 前缀就能跑。主要差异:文件系统用 `Deno.readTextFile` 而非 `fs.readFile`;运行时需要 `--allow-net` 等权限标志。依赖 sharp、bcrypt 等 Node 原生模块的项目可能有兼容问题,建议先用 `deno info` 检查依赖树。 ### Deno Deploy 和传统服务器部署哪个好? Deno Deploy 适合无状态 API 和边缘计算,全球 35+ 节点低延迟,免费额度 generous。限制:WebSocket 连接有超时、无持久文件系统、不支持长驻进程。传统 VPS/容器部署更灵活,适合需要完整运行时的项目。两者不冲突,API 层 Deploy + 重计算层 VPS 是常见搭配。
服务端5月27日 20:17
Deno 的部署和运维有哪些最佳实践?## 核心实践概览 Deno 生产部署的关键在于:容器化打包、权限最小化、健康检查三板斧,以及选择合适的部署平台。面试中考察的重点不是你会写多少 Dockerfile,而是你能否说清每个配置背后的取舍。 ## 容器化部署怎么选? Deno 官方提供 `denoland/deno` 镜像,生产环境推荐多阶段构建:先用 `deno compile` 将 TypeScript 编译为单文件二进制,再用精简基础镜像(如 `debian:slim`)运行。这样做的好处是最终镜像小、启动快、不暴露源码。 注意:Deno 2.x 已发布,不要再写 `denoland/deno:1.38.0` 这种过时版本。编译命令也有所变化,建议查阅最新文档。 容器内务必以非 root 用户运行,Dockerfile 末尾加 `USER deno` 是基本操作。 ## 权限控制为什么重要? Deno 和 Node.js 最大的架构差异就是默认安全。生产环境中坚决不用 `-A`(全量授权),而是按需授予: - 网络访问用 `--allow-net=api.example.com` 限定域名 - 文件读取用 `--allow-read=/app/data` 限定路径 - 环境变量用 `--allow-env=PORT,DB_URL` 限定变量名 面试中如果只答出 `--allow-net` 这种粗粒度权限,说明你没在生产环境用过 Deno。 ## 部署平台如何选择? 三种主流方案: - **Deno Deploy**:官方边缘计算平台,零配置部署,适合轻量 API 和 SSR 应用。2026 年 2 月已 GA,Classic 版将在 7 月下线,需要迁移到新平台。 - **容器化自建**:Docker + K8s,适合需要精细控制或有复杂依赖的场景。配置资源限制(requests/limits)、存活探针和就绪探针是基本要求。 - **PaaS 托管**:Railway、Vercel 等平台,适合快速上线,但定制空间有限。 选型依据:流量规模、延迟要求、团队运维能力。不要为了用 Deno Deploy 而用,数据库连接密集型场景自建集群更稳。 ## 健康检查和监控怎么做? 健康检查端点是生产标配: - `/health` 返回进程存活状态,用于 K8s livenessProbe - `/ready` 返回服务就绪状态(依赖是否连上),用于 readinessProbe 监控方面,结构化日志(JSON 格式)比 console.log 更有利于日志平台解析。Deno 2.x 支持 `Deno.memoryUsage()` 获取内存指标,配合定时上报可以排查内存泄漏。 ## CI/CD 有什么坑? Deno 项目的 CI 流程比 Node.js 简单——不需要 `npm install`,`deno cache` 一步搞定依赖。但要注意: - `deno lint` 和 `deno fmt --check` 必须加,Deno 内置了这些工具没有理由不用 - Docker 构建时先 `COPY deno.json` 再 `deno cache`,利用层缓存加速构建 - 镜像 tag 用 git SHA 而不是 `latest`,否则回滚时找不到版本 ## 追问方向 - Deno Deploy 和 Cloudflare Workers 在架构上有什么区别?(冷启动、运行时限制、KV 存储) - `deno compile` 编译出的二进制在 Alpine 镜像里能直接跑吗?(不能,Alpine 用 musl,需要静态编译或用 glibc 镜像) - Deno 的权限系统在容器内被绕过怎么办?(容器本身是安全边界,Deno 权限是应用层防护,两者互补)
服务端5月27日 20:14
如何使用 deno compile 编译可执行文件?## 什么是 deno compile? deno compile 是 Deno 内置的编译工具,能把 TypeScript/JavaScript 代码连同运行时一起打包成独立可执行文件。目标机器不需要安装 Deno,直接运行即可。它的底层原理是把代码和依赖绑定为 eszip 格式,再注入到精简版 Deno 运行时(denort)二进制中——所以它并不是真正编译成机器码,而是"打包+嵌入"。 ## 基本编译命令 ```bash deno compile --allow-net --allow-read app.ts ``` 编译时必须指定权限标志,运行时无法再修改。输出文件默认与源文件同名,可用 `--output` 自定义: ```bash deno compile --allow-net --output=myapp app.ts ``` ## 交叉编译与目标平台 一条命令即可编译到其他平台: ```bash deno compile --target=x86_64-unknown-linux-gnu --output=myapp-linux app.ts deno compile --target=aarch64-apple-darwin --output=myapp-mac app.ts deno compile --target=x86_64-pc-windows-msvc --output=myapp.exe app.ts ``` 这在 CI/CD 中做批量发布时很实用,不用准备多台构建机器。 ## 三个注意事项 **文件体积大。** 编译产物通常 50-100 MB,因为包含了完整 V8 引擎和 Deno 运行时。对体积敏感的场景可用 upx 压缩,但稳定性需自行验证。 **动态导入不会自动打包。** 静态分析能识别的动态 import 会被包含,但运行时拼装的路径不会。需要用 `--include` 显式声明: ```bash deno compile --include=./plugins/plugin.ts app.ts ``` Web Worker 的代码同理,也需 `--include` 手动加入。 **权限编译时锁定。** `--allow-net` 等标志在编译时写死,运行时无法突破,也无法降级。这意味着如果将来需要新权限,必须重新编译。 ## deno compile 与 Node.js 打包工具对比 | 维度 | deno compile | pkg (Node) | nexe (Node) | |------|-------------|------------|-------------| | 配置复杂度 | 零配置,一条命令 | 需要 package.json 和配置 | 需要配置和构建脚本 | | 跨平台编译 | 原生支持 --target | 有限支持 | 有限支持 | | 产物体积 | 50-100 MB | 40-80 MB | 40-70 MB | | 维护状态 | Deno 官方维护 | 社区维护,更新慢 | 社区维护,更新慢 | deno compile 的核心优势不在体积,而在于零配置和官方长期维护。 ## 面试追问方向 - deno compile 的产物为什么体积大?能否压缩?——因为嵌入了 V8 和 denort,upx 可压缩但有兼容风险。 - 动态导入的模块为什么不会被打包?如何解决?——编译器只做静态分析,运行时才能确定路径的 import 会被跳过,用 `--include` 显式包含。 - 编译时锁定的权限是否意味着安全性更强?——是,但也丧失了灵活性,需根据场景取舍。
服务端5月27日 20:12
Deno 如何处理模块导入和依赖管理?## 核心答案 Deno 采用 URL 直接导入模块,没有 package.json 和 node_modules,依赖管理通过 Import Maps、deps.ts 文件或 deno.json 的 imports 字段集中管理,模块全局缓存于本地。 ## 导入方式 Deno 支持 URL 导入、相对路径导入和 Import Maps 别名导入三种方式: ```typescript // URL 直接导入(指定版本) import { serve } from "https://deno.land/std@0.208.0/http/server.ts"; // 相对路径导入(必须带扩展名) import { utils } from "./utils.ts"; // 通过 Import Maps 使用别名 import { Application } from "oak"; ``` Import Maps 在 `deno.json` 中配置,可将 URL 映射为简短别名,是目前推荐的做法。 ## 依赖管理方案 早期 Deno 推荐 `deps.ts` 模式——将所有远程依赖集中到一个文件重新导出,应用代码只从 deps.ts 导入。现在更推荐使用 `deno.json` 的 imports 字段,本质上是标准 Import Maps: ```json { "imports": { "oak": "jsr:@oak/oak@^12.6.1", "std/": "https://deno.land/std@0.208.0/" } } ``` JSR(jsr.io)是 Deno 推出的现代包注册表,支持 TypeScript 原生发布,配合 `deno add jsr:@oak/oak` 直接安装依赖。 ## 与 Node.js 的关键区别 - **无 node_modules**:模块下载后全局缓存,不污染项目目录 - **无 package.json**:用 deno.json 或 deps.ts 替代 - **必须带扩展名**:本地导入必须写 `.ts`,与浏览器行为一致 - **权限控制**:导入远程模块需要 `--allow-net`,运行时受沙箱约束 ## 版本锁定 `deno.lock` 文件记录依赖的精确版本和完整性哈希,类似 npm 的 package-lock.json。CI 环境中用 `deno install --frozen` 确保依赖不可变。 ## 追问:URL 导入有什么安全隐患?如何规避? URL 导入指向的代码可能被篡改(供应链攻击)。规避方式:始终锁定版本号、使用 lock 文件校验哈希、优先从 JSR 等可信注册表安装、配置 `DENO_AUTH_TOKENS` 访问私有仓库。
服务端5月27日 20:12
Deno 标准库有哪些常用模块?## 核心回答 Deno 标准库(@std)已稳定至 v1,现通过 JSR 分发,包含 37 个独立包。最常用的有:**@std/fs**(文件系统)、**@std/path**(路径处理)、**@std/http**(HTTP 服务)、**@std/assert**(断言测试)、**@std/async**(异步工具)、**@std/encoding**(编解码)、**@std/collections**(集合操作)、**@std/fmt**(格式化输出)。 导入方式已从 `deno.land/std` 迁移至 JSR: ```typescript // 旧方式(已不推荐) import { serve } from "https://deno.land/std@0.208.0/http/server.ts"; // 新方式(JSR) import { serve } from "@std/http"; ``` ## 模块详解 **@std/fs** 提供 ensureDir、copy、walk 等文件操作,比手动调用 Deno API 更安全——自动处理目录不存在等边界情况。 **@std/path** 等价于 Node.js 的 path 模块,提供 join、resolve、basename 等,跨平台兼容。 **@std/http** 用于快速搭建 HTTP 服务,`serve()` 接收 Request/Response 标准对象,无需第三方框架。 **@std/assert** 是 Deno 测试的标配断言库,assertEquals、assertThrows 覆盖绝大多数场景。 **@std/async** 提供 delay、retry、debounce 等异步工具,retry 支持指数退避策略。 **@std/encoding** 处理 base64、hex、varint 等编解码,是网络和存储场景的基础依赖。 ## 注意事项 部分模块仍标记为 UNSTABLE(0.x 版本),如 @std/log、@std/datetime、@std/io,生产环境慎用。标准库各包独立版本管理,成熟度不同,导入前需确认稳定状态。 ## 追问方向 - Deno 标准库和 Node.js 内置模块相比,设计理念有什么不同?(提示:ESM-only、无全局污染、独立版本) - @std/http 能否替代 Oak 等框架?什么场景下需要引入第三方框架?(提示:路由、中间件、请求校验) - 标准库模块的 UNSTABLE 标记意味着什么?如何判断能否用于生产?(提示:语义化版本、JSR 标注)
服务端5月27日 20:11
Deno 的权限系统是如何工作的?Deno 采用"默认拒绝"的安全模型——脚本启动时没有任何权限,必须通过命令行标志显式授权才能访问文件、网络、环境变量等资源。这套权限系统是 Deno 区别于 Node.js 的核心安全特性。 ## 核心机制 权限以 `--allow-*` 标志授予,支持通配和精确指定两种模式: ```bash # 通配:允许所有网络访问 deno run --allow-net app.ts # 精确:只允许访问指定域名 deno run --allow-net=api.example.com app.ts ``` 主要权限标志包括 `--allow-read`、`--allow-write`、`--allow-net`、`--allow-env`、`--allow-run`、`--allow-sys`、`--allow-hrtime` 和 `--allow-ffi`,均可通过 `=` 指定白名单。Deno 1.36 起还支持 `--deny-*` 黑名单,优先级高于 `--allow-*`,可在宽泛授权下排除特定资源。 ## 运行时权限查询 代码中可通过 `Deno.permissions` API 检查和请求权限: ```typescript const status = await Deno.permissions.query({ name: "net" }); // status.state → "granted" | "prompt" | "denied" const req = await Deno.permissions.request({ name: "read", path: "/tmp" }); // 运行时弹出交互提示 ``` 也可调用 `Deno.permissions.revoke()` 主动放弃已获权限,实现最小权限的动态收缩。 ## 子进程权限的陷阱 `--allow-run` 授予的子进程不在 Deno 沙箱内运行,它继承宿主系统的完整权限,不受 Deno 权限标志约束。因此只应允许运行明确可信的命令,如 `--allow-run=git,curl`。 ## 与 Node.js 的关键区别 Node.js 默认拥有全部系统权限,依赖 `fs` 模块即可读写任意文件,安全边界完全依赖操作系统层面。Deno 则从运行时层面强制权限隔离,每个资源访问请求都需经过权限检查,恶意依赖无法静默越权。 ## 追问 - **权限白名单和黑名单同时存在时,哪个优先?** `--deny-*` 优先。即使 `--allow-read=/app` 已授权,`--deny-read=/app/secret` 仍会阻止对该目录的访问。 - **如何在 CI 中自动处理权限提示?** 使用 `--no-prompt` 标志,未授权的访问直接抛出 PermissionDenied 错误而非交互提示,适合自动化流水线。