原生 TypeScript:无需任何配置
Deno 最广受好评的特性之一:直接运行 .ts 文件,无需安装 ts-node、无需 tsconfig.json、无需任何预处理步骤。
# 安装依赖
npm install -D typescript ts-node
# 或安装 tsx
npm install -D tsx
# 创建 tsconfig.json
npx tsc --init
# 运行
npx tsx server.ts
# 无需任何安装
# 无需 tsconfig.json
# 直接运行
deno run app.ts
# 带权限运行
deno run --allow-net app.ts
Deno 的 TypeScript 是"类型擦除":Deno 运行 TypeScript 时仅剥离类型注解,不进行类型检查。类型错误不会阻止程序运行。你需要单独运行 deno check app.ts 来执行类型检查(等同于 tsc --noEmit)。
deno.json 中的 TypeScript 配置
Deno 通过 deno.json 的 compilerOptions 字段来配置 TypeScript,与 tsconfig.json 的字段基本一致:
// deno.json
{
"compilerOptions": {
"strict": true, // 严格模式(推荐)
"noImplicitAny": true, // 禁止隐式 any
"strictNullChecks": true, // 严格 null 检查
"lib": ["deno.window"], // 包含 Deno 全局 API 类型
"jsx": "react-jsx", // JSX 支持(用于 Fresh)
"jsxImportSource": "preact" // JSX 运行时
}
}
lib 选项说明
| lib 值 | 适用场景 |
|---|---|
deno.window | 默认,Deno 主线程(包含 Deno 全局、Web API) |
deno.worker | Deno Web Worker 环境 |
deno.ns | 仅 Deno 命名空间 API(不含 Web API) |
dom | 浏览器 DOM API(当你写前端代码时) |
ES 模块:Deno 的模块系统
Deno 从一开始就只支持 ES 模块(ESM),没有 CommonJS。这是 Deno 与 Node.js 最显著的设计差异之一。
四种导入方式
1. 相对路径导入(必须带扩展名)
// main.ts
import { add } from "./math.ts"; // 必须写 .ts 扩展名
import type { Config } from "./types.ts";
console.log(add(1, 2));
扩展名是必须的:Node.js 允许省略扩展名(require('./math')),但 Deno 严格遵循 Web 标准,必须写完整扩展名。这使模块解析更明确,也与浏览器行为一致。
2. URL 导入(远程模块)
// 从 URL 直接导入(Deno 1.x 风格,仍然支持)
import { serve } from "https://deno.land/std@0.224.0/http/server.ts";
// Deno 会自动下载、缓存模块
// 缓存位置:~/.cache/deno/deps/
3. npm: 前缀(npm 包)
// 使用 npm 包,无需 npm install
import { Hono } from "npm:hono@4";
import express from "npm:express@4";
import { z } from "npm:zod@3";
// Deno 会自动解析 npm 包,无需 node_modules
4. node: 前缀(Node.js 内置模块)
// 兼容 Node.js 内置模块
import { readFileSync } from "node:fs";
import path from "node:path";
import { EventEmitter } from "node:events";
import { createHash } from "node:crypto";
// Deno 2 完整实现了 Node.js 兼容层
5. jsr: 前缀(JSR 注册表)
// 从 JSR 导入(推荐方式)
import { assertEquals } from "jsr:@std/assert@^1.0.0";
import { join } from "jsr:@std/path@^1.0.0";
import { walk } from "jsr:@std/fs@^1.0.0";
import maps:统一管理依赖
import maps 让你为长 URL 或版本号定义别名,统一管理所有依赖版本。在 deno.json 的 imports 字段定义:
// deno.json
{
"imports": {
// JSR 包
"@std/assert": "jsr:@std/assert@^1.0.0",
"@std/path": "jsr:@std/path@^1.0.0",
"@std/fs": "jsr:@std/fs@^1.0.0",
// npm 包
"hono": "npm:hono@^4.0.0",
"zod": "npm:zod@^3.22.0",
// 路径别名(目录映射需以 / 结尾)
"@/": "./src/"
}
}
// 代码中使用别名,不再需要写版本号
import { assertEquals } from "@std/assert";
import { Hono } from "hono";
import { UserService } from "@/services/user.ts";
顶层 await
Deno 原生支持顶层 await(Top-level await),无需将异步代码包裹在 async 函数中:
// fetch-data.ts — 顶层 await,无需 async 包装
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users: Array<{ id: number; name: string }> = await response.json();
console.log(`获取到 ${users.length} 位用户:`);
for (const user of users.slice(0, 3)) {
console.log(` - ${user.name}`);
}
// 运行:deno run --allow-net fetch-data.ts
实战:无需编译的 TypeScript 脚本
下面用一个完整例子展示 Deno TypeScript 脚本的能力:读取 CSV、处理数据、输出报告。
// report.ts — 零依赖、原生 TypeScript 脚本
import { parse } from "jsr:@std/csv@^1.0.0";
interface SalesRecord {
date: string;
product: string;
amount: number;
}
// 顶层 await 读取文件
const csvText = await Deno.readTextFile("./sales.csv");
const records = parse(csvText, {
skipFirstRow: true,
columns: ["date", "product", "amount"],
}) as SalesRecord[];
// 按产品聚合销售额
const totals = records.reduce((acc, r) => {
acc[r.product] = (acc[r.product] ?? 0) + Number(r.amount);
return acc;
}, {} as Record<string, number>);
// 排序输出
const sorted = Object.entries(totals)
.sort(([, a], [, b]) => b - a);
console.log("=== 销售报告 ===");
for (const [product, total] of sorted) {
console.log(`${product.padEnd(20)} ¥${total.toLocaleString()}`);
}
// deno run --allow-read report.ts
本章小结:Deno 的 TypeScript 支持是开箱即用的——不需要 tsconfig.json,不需要 ts-node,直接 deno run xxx.ts。模块系统完全基于 ES 模块标准,支持 URL、jsr:、npm:、node: 四种导入方式。import maps 统一管理依赖版本。顶层 await 让异步脚本写起来如呼吸般自然。