第一章:打造坚不可摧的工程化起点

第一章:打造坚不可摧的工程化起点

摘要: 欢迎来到第一章。在这里,我们将完成从“编写代码”到“构建工程”的第一次关键跃迁。本章将引导您使用 Vite 和 pnpm 快速搭建一个现代化的 TypeScript 项目,并首先对这个专业的工程“蓝图”进行全面解读。更重要的是,我们将一同解构 tsconfig.json 文件,您会发现它并非一张枯燥的选项列表,而是您为项目制定的“架构宣言”。我们将学习如何做出关键的工程决策,例如为什么 strict: true 是专业项目的“铁律”,以及如何通过路径别名来提升代码的可维护性。


1.1. 现代化项目蓝图:解构 Vite 项目结构

一个专业、高效的开发环境是工程化的起点。在 2025 年,我们选择 Vite,因为它提供了无与伦比的开发服务器启动速度和开箱即用的 TypeScript 支持。我们将使用 pnpm,一个速度更快、磁盘空间效率更高的包管理工具。

第一步:初始化项目

请打开您的终端,执行以下命令,创建一个名为 my-ts-app 的项目:

1
2
3
# 确保你已经安装了 pnpm (npm install -g pnpm)
# --template vanilla-ts 指定我们使用原生 JS + TypeScript 模板
pnpm create vite my-ts-app --template vanilla-ts

第二步:认知项目结构

命令执行完毕后,您将得到一个清晰、专业的工程骨架。让我们深入理解这个结构的内涵:

1
2
3
4
5
6
7
8
9
10
11
12
. 📂 my-ts-app
├── 📄 index.html # <-- Web 应用的入口 HTML 文件
├── 📄 package.json # <-- 项目的“身份证”,定义依赖和脚本
├── 📄 pnpm-lock.yaml # <-- 精确锁定依赖版本,保证团队环境一致性
├── 📂 public/
│ └── 📄 vite.svg # <-- 存放无需编译的静态资源
├── 📂 src/ # <-- 我们的主战场:所有源代码存放于此
│ ├── 📄 main.ts # <-- TypeScript 的主入口文件
│ ├── 📄 style.css # <-- 全局样式文件
│ ├── 📄 vite-env.d.ts # <-- Vite 提供的 TypeScript 类型声明
│ └── ... 其他模板文件
└── 📄 tsconfig.json # <-- 本章的核心:TypeScript 编译器的“宪法”
  • index.html: 这是应用的起点,它通过 <script type="module" src="/src/main.ts"></script> 引入了我们的 TypeScript 主入口文件。
  • src/main.ts: 所有 TypeScript 逻辑的起点。Vite 会从这里开始分析依赖,构建整个应用。
  • package.json: 定义了项目元信息、依赖(dependencies & devDependencies)以及可执行脚本(scripts),例如 pnpm run dev
  • tsconfig.json: 这是项目的灵魂。它告诉 TypeScript 编译器如何检查和编译我们的代码。

1.2. tsconfig.json:不是选项列表,而是架构宣言

请不要把它看作一份普通的配置文件。tsconfig.json 是您项目的“宪法”,它定义了代码的编写规范、编译规则和工程边界。您在这里做出的每一个决策,都将深刻影响代码的健壮性和可维护性。

Vite 为我们生成的默认 tsconfig.json 已经是一个很好的起点。接下来,我们将聚焦其中几个 最关键 的配置项,理解它们背后的工程化考量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{
"compilerOptions": {
// 代码将编译为 ES2022,利用最新 JS 特性(如顶层 await)
"target": "ES2022",
// 类型检查时可用的 API(现代 JS + 浏览器环境)
"lib": ["ES2022", "DOM", "DOM.Iterable"],
// 类字段行为与标准一致,避免初始化陷阱
"useDefineForClassFields": true,

// 输出标准 ES 模块,交由 Vite 等打包工具处理
"module": "ESNext",
// 按现代打包工具的方式解析模块
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
// 只做类型检查,不生成 js 文件
"noEmit": true,

// 兼容单文件转译工具(如 esbuild、Babel)
"isolatedModules": true,

// 严格模式,提升类型安全
"strict": true,
// 检查未使用的局部变量
"noUnusedLocals": true,
// 检查未使用的函数参数
"noUnusedParameters": true,
// 防止 switch 语句穿透 bug
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}

1.3 铁律:"strict": true

痛点背景:回想一下序章中那些因为 nullundefined 导致的运行时崩溃。strict 模式就是为了从根源上终结这类问题而生的。

深度解析"strict": true 并非单个选项,而是一个“全家桶”,它会开启所有严格类型检查的选项(如 strictNullChecks, noImplicitAny 等)。这等于告诉 TypeScript 编译器:“请用最严格的标准来审查我的代码,不要放过任何潜在的类型风险。”

工程决策
2025-09-03

我应该在我的新项目里开启 strict: true 吗?

资深工程师

答案是唯一的、武断的:是,必须是。对于任何严肃的、期望长期维护的项目,关闭 strict 模式就等于主动放弃了 TypeScript 带来的 80% 的安全保障。

但这会让我的代码写起来更麻烦。

资深工程师

短期的“麻烦”,换来的是长期的健壮和可维护性。它会强迫你从一开始就思考所有边界情况,这正是一名专业工程师所必需的思维模式。

代码对比

1
2
3
4
5
6
// 在 "strict": false 的情况下
// 这段代码可以通过编译,但存在巨大的运行时风险
function getGreeting(name: string | null) {
// 如果 name 是 null, .toUpperCase() 会在运行时抛出错误
return `Hello, ${name.toUpperCase()}`;
}

1
2
3
4
5
6
7
8
9
10
11
12
// 在 "strict": true 的情况下
function getGreetingStrict(name: string | null): string {
// 编译器会立刻报错:'name' is possibly 'null'.
// return `Hello, ${name.toUpperCase()}`;

// 我们必须处理 null 的情况,代码才能通过编译
if (name) {
return `Hello, ${name.toUpperCase()}`;
} else {
return "Hello, Guest";
}
}

最佳实践: 始终保持 "strict": true。将它视为项目的“安全带”,从第一天就系好它。


1.4. 沟通协议:模块解析与路径别名

痛点背景:告别 import { logger } from '../../../../utils/logger'; 这种脆弱且丑陋的相对路径。

解决方案:通过 模块解析策略路径别名,让模块导入变得清晰、稳定。

1. 模块解析策略

tsconfig.json 中,确保使用 bundler 模式。

1
2
3
4
5
{
"compilerOptions": {
"moduleResolution": "bundler"
}
}
  • 核心价值:让 TypeScript 的模块解析行为与 Vite 等现代打包工具完全对齐,从根源上避免“开发时正常,打包后报错”的问题。

2. 路径别名:双重配置,缺一不可

路径别名(如 @/*)必须同时在 TypeScript 和 Vite 中配置,才能在 代码提示项目运行 两个环节都生效。

步骤一:配置 tsconfig.json (为 TS 和 IDE 服务)

1
2
3
4
5
6
7
8
9
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
  • "baseUrl": 定义路径解析的基准目录(项目根目录)。

  • "paths": 创建别名规则,将 @/ 指向 src/

步骤二:配置 vite.config.ts (为 Vite 打包服务)

1
2
3
4
5
6
7
8
9
10
11
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'

// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
  • 依赖:此配置需要 Node.js 类型声明,请执行 pnpm add -D @types/node

最终效果对比

配置前import { logger } from '../../utils/logger';
配置后import { logger } from '@/utils/logger';

核心价值: 路径别名极大地提升了代码的 可读性可维护性。当您重构或移动文件时,其内部的导入路径无需任何修改。

完成上述配置后,即可启动项目。

1
2
3
4
5
# 1. 安装依赖
pnpm install

# 2. 启动 Vite 开发服务器
pnpm run dev

Vite 将启动一个具备 热模块替换 (HMR) 功能的开发服务器,任何代码修改都会即时在浏览器中响应,无需手动刷新。


1.5 本章小结

分类关键项核心价值
包管理pnpm(推荐) 高效、快速的现代包管理工具。
项目结构Vite 模板(推荐) 提供了一个清晰、专业、开箱即用的工程骨架。
核心配置strict: true(强制) 开启所有严格类型检查,是保证代码健壮性的基石。
模块解析moduleResolution: "bundler"(推荐) 确保 TS 编译器与打包工具的模块解析行为一致。
路径管理paths: { "@/*": [...] }(推荐) 提升代码可读性与可维护性,简化重构。

在打下坚实的工程化地基之后,我们将在下一章正式开始为真实世界的数据建模,学习如何使用 TypeScript 的核心类型系统来绘制我们应用的 “蓝图”。