CHAPTER 01 · 起步

生态全景地图

TS 生态之所以让人眼花缭乱,是因为 Java 里一个 JDK 包办的事情,在这里被拆成了五六个可替换的工具。拆开看每一层,其实都能在你熟悉的世界里找到对应物。

⏱ 约 30 分钟会用知道其中“类型检查 ≠ 转译”是 原理
读完本章,你应该能够:
  • 看到 node、pnpm、tsc、vite、next、wrangler 这些命令时,说出它们分别处于哪一层。
  • 解释为什么“代码能运行”不代表“类型是正确的”。
  • 说出 2026 年三条主流技术路线(Vercel / Cloudflare / 自托管)的典型组合及它们的共同部分。

开场:一个项目,七八个命令

开场问题

你让 Agent 生成了一个项目,package.json 的 scripts 里写着:"dev": "vite dev"、"build": "vite build"、"typecheck": "tsc --noEmit"、"deploy": "wrangler deploy"。你用 pnpm 安装依赖,用 node 运行脚本。为什么需要这么多工具?只写 TypeScript 然后“编译运行”不行吗?

答案方向:把 TS 源码变成“用户能用的应用”,要经过一条流水线:管理依赖 → 检查类型 → 去掉类型 → 打包优化 → 在某个运行时上执行 → 发布到某个平台。在 Java 世界,JDK + Maven 把其中大部分合在一起;在 TS 世界,每一步都有独立的、可替换的工具。上面每个命令,就是流水线上的一个工位。

先用你熟悉的世界做对照

下表把 TS 生态的每一层映射到 Java / Go / Python。这张表是整章的“锚点”,后面每一节都是在展开其中一行。

层Java / Go / PythonTypeScript 生态深度
语言Java / Go / PythonTypeScript(= JavaScript + 类型标注)原理
类型检查javac / go build(检查与编译一体)tsc(只检查,通常不产出文件)原理
转译(去类型)—(编译器一起做了)esbuild、SWC、Oxc、Node 内置的类型剥离知道
运行时JVM / Go runtime / CPythonNode.js、Bun、Deno、浏览器、Cloudflare workerd会用
包管理Maven / go mod / pip、uvpnpm、npm、bun(仓库:npm registry)会用
打包打 jar / 静态二进制Vite(内部用 Rolldown)、Turbopack(Next.js)知道
Web 后端框架Spring / Gin / FastAPIHono、Express、Fastify、NestJS会用
全栈框架Rails / Django(有模板)Next.js、TanStack Start、React Router会用
UI 库模板引擎 / 无React(另有 Vue、Svelte、Solid)原理
ORMHibernate / GORM / SQLAlchemyDrizzle、Prisma会用
测试JUnit / go test / pytestVitest(单元)、Playwright(端到端)会用
格式化 / Lintgofmt / black / ruffBiome、Prettier、ESLint、Oxlint知道
部署平台K8s / 云主机Vercel、Cloudflare、自托管 Docker会用

有两行在你熟悉的世界里没有真正的对应物,也正是新人最容易糊涂的地方:“类型检查与转译分离”和“打包”。我们先讲它们。

流水线:从 .ts 文件到运行中的应用

源码 .ts / .tsx 类型检查 tsc 只报错,不产出代码 转译:剥离类型 esbuild / Oxc / Node 打包 bundler 合并 · 拆分 · 压缩 浏览器产物 JS · CSS · 资源 服务器产物 Node / Worker 代码 两条路互不等待: 类型有错,下面这条路照样能产出可运行代码 开发时:dev server 按需转译, 改一行,浏览器秒级热更新(HMR) Vite / Next.js 把“转译 + 打包 + 开发服务器”包成一个命令;tsc 通常单独在编辑器和 CI 里跑
红色虚线是“类型检查”,它是旁路:它能阻止你合并代码(在 CI 里),但不参与生成产物。

核心原理:类型检查和转译是两件事 原理

在 Java 里,类型错误意味着编译失败,没有 .class 文件,程序根本跑不起来。TS 不一样。TypeScript 的设计目标之一是:类型只存在于编写阶段,运行时完全不存在。这叫类型擦除(type erasure)。转译器要做的事情简单到近乎机械:把类型标注删掉,剩下的就是合法的 JavaScript。

正因为“删类型”不需要理解类型,esbuild、SWC 这类用 Go / Rust 写的转译器才能快到每秒处理几十万行代码。从 Node.js 23.6 起,Node 可以直接运行 .ts 文件(内置的类型剥离,type stripping),Node 26 更把它作为稳定的默认行为。但请注意:Node 只是删掉类型,它不做类型检查。

这对你驾驭 Agent 意味着什么

Agent 说“我运行了一下,没问题”,只能说明转译后的 JS 能跑,不能说明类型正确。你的验收标准里应该明确要求类型检查通过(例如 pnpm tsc --noEmit 零错误),并且在 CI 里强制执行。第 10 章会把它变成流水线的一部分。

2026 年 7 月发布的 TypeScript 7 把编译器用 Go 重写为原生程序,大型项目类型检查快了约 10 倍。它改变的是速度,不是语言:你写的代码、类型规则都一样。对你的实际影响是:类型检查便宜了,没理由再跳过它。

为什么前端需要“打包” 知道

Go 把程序编译成一个二进制,服务器从本地磁盘加载,文件多大、分几个文件都无所谓。浏览器不一样:代码要通过网络下载到每个用户的设备上。一个项目可能有几千个模块文件(你的代码加上 node_modules 里的依赖),逐个下载会慢到无法接受。打包器(bundler)要解决的就是这个问题:

2026 年你最常遇到的两个名字:Vite(Vite 8 起内部统一使用 Rust 写的 Rolldown 打包器;TanStack Start、React Router、Astro、SvelteKit 等都基于它)和 Turbopack(Next.js 的默认打包器)。它们属于“知道”档:你几乎不需要手动配置,出问题时也通常是明确的构建报错。

运行时:JS 代码实际在哪里执行

JavaScript 引擎(如 V8)只负责执行语言本身:变量、函数、对象。“读文件”“发网络请求”“操作页面”这些能力,是宿主环境提供的。不同的宿主环境,就是不同的运行时(runtime)。

运行时在哪里特点深度
浏览器用户设备有 DOM(页面结构)、事件;没有文件系统;代码对用户完全可见原理
Node.js服务器 / 你的电脑事实标准;生态最全;偶数版本是长期支持(LTS)版本会用
Bun服务器 / 你的电脑一体化:运行时 + 包管理 + 测试 + 打包;启动快;高度兼容 Node知道
Deno服务器 / 你的电脑默认安全(需显式授权文件和网络访问);兼容 npm 包知道
workerdCloudflare 边缘节点V8 isolate(隔离沙箱),启动极快;有 CPU 和内存限制;兼容大部分 Node API会用

一个让这些运行时能共享代码的关键事实:它们都在向Web 标准 API靠拢——fetch、Request、Response、URL、ReadableStream、crypto.subtle。如果你的服务器代码只用这些标准 API,它就能在 Node、Bun、Deno 和 Cloudflare 上原样运行。这就是 Hono 这类框架“一份代码到处部署”的原理。

类比

可以把 V8 想成 JVM 里的“字节码执行引擎”,把运行时想成“JDK 标准库 + 启动器”。Java 在服务器和 Android 上是同一门语言、不同的标准库;JS 在浏览器和 Node 上也是同一门语言、不同的宿主 API。类比的失配点:JS 没有统一的“标准库发行方”,各运行时 API 的交集才是可移植的部分。

包管理:package.json 和 node_modules

每个 JS/TS 项目的根目录有一个 package.json,相当于 pom.xml 或 go.mod:

用哪个包管理器?推荐 pnpm:它把所有包存在一个全局的内容寻址仓库里、通过链接复用,磁盘占用小、安装快;而且它的依赖结构是“严格”的——你没在 package.json 里声明的包,代码里就 import 不到,避免了“在我机器上能跑”的幽灵依赖。npm 是 Node 自带的默认选择;bun 速度很快,适合已经在用 Bun 运行时的项目。

供应链安全(一定要知道)

npm 生态依赖层级很深,一个项目动辄上千个间接依赖,近几年多次发生恶意包、账号被盗后发布带毒版本的事件。几条最低限度的习惯:提交锁文件;CI 里用 pnpm install --frozen-lockfile;pnpm 10 起默认不执行依赖的安装脚本(postinstall),不要随手全局放开;让 Agent 新增依赖时,要求它说明为什么需要、周下载量、最近维护情况。

框架分层:谁管界面,谁管路由,谁管数据

“框架”这个词在 TS 生态里指代的东西差异很大。按职责分三层来看就清楚了:

元框架 / 全栈框架(meta-framework) 路由 · 服务端渲染 · 数据加载 · 服务端函数 · 构建。例:Next.js、TanStack Start、React Router、Astro UI 库 把状态变成界面。例:React(主流)、Vue、Svelte 后端 / API 框架 处理 HTTP 请求。例:Hono、Express、Fastify、NestJS 周边库 样式 Tailwind · 组件 shadcn/ui · 校验 Zod · 数据库 Drizzle/Prisma · 请求缓存 TanStack Query · 鉴权 Better Auth · AI 调用 AI SDK
元框架“包着”UI 库,并内置了一个后端;你也可以不用元框架,单独用 UI 库 + 独立的后端框架。

和 Java 世界最大的不同在于:一个元框架项目同时是前端和后端。Next.js 应用里既有 React 组件,又有处理 HTTP 请求、读写数据库的服务端代码,它们在同一个仓库、同一种语言、甚至可以互相 import 类型。这带来巨大的便利(端到端类型安全,第 08 章),也带来新的风险(服务器代码被误打包进浏览器,第 07 章)。

2026 年的三条主流路线

下面三种组合覆盖了绝大多数新项目。它们的共同部分远大于差异:TypeScript + React + Tailwind + Zod + Drizzle + Vitest 在三条路线里都适用。差异主要在元框架和“运行在哪”。

▲ Vercel 路线

Next.js(App Router、React Server Components、Server Actions)+ shadcn/ui + Postgres(Neon / Supabase)+ Better Auth 或 Clerk + AI SDK → 部署到 Vercel。

长处:资料最多、Agent 最熟悉、部署零配置。

☁ Cloudflare 路线

TanStack Start / React Router / Hono + Vite + D1(SQLite)/ KV / R2 + Durable Objects + Agents SDK → 部署到 Workers。

长处:成本低、全球低延迟、存储与有状态计算原生集成,适合 AI Agent 和实时应用。

🖥 自托管路线

TanStack Start / Next.js(standalone 输出)/ Hono 跑在 Node 或 Bun 上 + Postgres + Redis → Docker 部署到 VPS(可用 Coolify、Dokploy、Kamal)。

长处:无平台限制、成本可预测、数据完全自控。

选择顺序的建议

先学共同部分,平台最后选。第一次做项目,选你能最快部署上线的那条——通常是 Vercel + Next.js(资料最多)或 Cloudflare + TanStack Start/Hono(成本最低)。第 09 章会给出更细的决策树。

版本快照(2026 年 9 月)

这张表会过时,但它能帮你判断 Agent 给出的代码是否“太旧”(模型的训练数据有截止时间,经常写出上一代 API)。

项目当前状态对你意味着什么
TypeScript7.x(Go 原生编译器)类型检查快约 10 倍,语言本身几乎不变
Node.js24 为 LTS;26 于 2026 年 10 月转 LTS可直接运行 .ts(只剥离类型);新项目选最新 LTS
React19.x;React Compiler 1.0 已稳定手写 useMemo/useCallback 的需求大幅减少
Next.js16.xApp Router 是默认;Turbopack 是默认打包器
TanStack Startv1基于 Vite 的全栈框架,类型安全路由是招牌
Vite8(内置 Rolldown)生产构建显著变快
Tailwind CSSv4配置写在 CSS 里而不是 tailwind.config.js;Agent 常写成 v3 风格
Zodv4运行时校验的事实标准
AI SDK(Vercel)v6提供 Agent 抽象(ToolLoopAgent)、工具审批等
对 Agent 怎么说
模糊
精确
“帮我建一个全栈网站。”
“用 pnpm 创建一个 TanStack Start 项目:TypeScript strict 模式,Tailwind v4,部署目标 Cloudflare Workers。先只做一个首页和一个 /api/health 路由,不加数据库和鉴权。完成后运行 pnpm tsc --noEmit 和 pnpm build,把输出贴给我。”
“加个库处理日期。”
“需要格式化相对时间(如‘3 天前’)。优先用平台自带的 Intl.RelativeTimeFormat;如果必须加依赖,先列出候选、体积和维护状态,等我确认。”

精确版本做了三件事:指定了每一层的选择(包管理、框架、样式、平台),限定了范围(先不加什么),给出了可检查的完成标准。

面试角

问:TypeScript 和 JavaScript 是什么关系?TS 代码是怎么运行的?

答题要点:TS 是 JS 的超集,只增加了静态类型系统;类型在编译期检查、运行时被完全擦除。运行前要把 TS 转成 JS——可以由 tsc 完成,但实践中通常由 esbuild / SWC / Vite 或 Node 内置的类型剥离完成,而 tsc 只负责类型检查(--noEmit)。所以类型不能替代运行时校验,外部输入仍需用 Zod 之类的库校验。

自检

1. 同事用 node app.ts 运行成功了,于是说“类型没问题”。他对吗?

不对。Node 的类型剥离只删除类型标注,不检查类型。代码能运行只说明转译后的 JS 语法合法。要确认类型正确,必须跑 tsc --noEmit(或编辑器里的 TS 语言服务)。

2. 一段服务器代码用了 fs.readFileSync 读取本地模板文件,现在要把它部署到 Cloudflare Workers。你预计会遇到什么问题?

Workers 运行在 V8 isolate 里,没有传统意义上可写、可任意读取的本地文件系统。虽然有 Node 兼容层,但“读部署机器上的任意文件”这种假设不成立。通常做法是:在构建时把模板打包进代码、放进静态资源,或存到 R2/KV 中读取。这说明“运行时的形状”会影响代码写法。

3. 为什么 Go 后端不需要“打包器”这个概念,前端却离不开?

因为前端代码要通过网络下载到每个用户的浏览器里执行。文件数量、体积、缓存策略直接决定加载速度。打包器负责合并、摇树、分割、压缩和给文件加哈希。Go 二进制从本地磁盘加载,没有这个问题。