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;这样才能享受少配置,而不是把隐患藏到运行时。

标签:TypeScriptDeno