React企业中台实战 - 第二章. 奠基:项目初始化与环境配置

第二章. 奠基:项目初始化与企业级配置

在本章中,我们将卷起袖子,开始 Prorise-Admin 的奠基工作。我们将从一个空目录开始,使用 pnpm create vite 初始化项目骨架。随后,我们将 立即 着手进行一系列至关重要的企业级配置,包括:规范化开发环境配置 TypeScript 严格模式实现 @/* 路径别名,并为项目注入现代化的 代码质量工具链。每一步都旨在为后续的高效、稳定开发铺平道路。

2.1. 项目初始化:一切的起点

2.1.1. pnpm create vite:选择最快的脚手架

我们的第一步,是使用 Vite 官方脚手架创建一个最小化的 React + TypeScript 项目。正如第一章所述,Vite 提供了无与伦比的开发体验,而 pnpm 以其高效的磁盘空间利用和快速的安装速度,成为我们的首选包管理器。

打开终端,执行以下命令:

1
2
3
4
# 使用 pnpm 执行 vite 的创建命令
# 'prorise-admin' 是我们的项目名称
# '--template react-ts' 指定了我们使用的技术栈模板
pnpm create vite prorise-admin --template react-ts

脚手架会引导我们完成创建。成功后,终端会提示我们进入项目目录并安装初始依赖:

1
2
cd prorise-admin
pnpm install

此刻,一个基础的 Vite + React + TS 项目已经诞生。但在我们编写任何业务代码之前,架构师的首要任务是建立规范和约束,确保项目在未来的迭代和协作中不会走向混乱。

2.1.2 环境一致性:根除“在我这儿是好的”

在企业级协作中,最常见的内耗源于环境不一致(如 Node.js 版本、包管理器混用)导致的各类诡异 Bug。我们必须在项目初始化的第一刻就通过配置来杜绝这种可能性。

锁定 Node.js 版本 (.nvmrc)

不同的 Node.js 版本可能导致依赖安装失败或运行时行为不一致。我们将通过 .nvmrc 文件来声明项目期望的 Node.js 版本。

我们在项目根目录 (prorise-admin/) 下创建 .nvmrc 文件。

文件路径: .nvmrc

1
lts/iron

为什么是 lts/iron?
我们不直接写 20,而是使用 LTS (长期支持版) 的代号 iron (Node.js 20.x 的代号)。这更具可读性,并且允许团队成员使用 20.x 系列中的任何最新稳定版,而不是强制锁定到一个特定的微小版本。

工作流提示:团队成员在首次拉取项目后,只需在项目根目录运行 nvm usenvm install 命令。NVM (Node Version Manager) 会自动读取此文件,并下载或切换到指定的 Node.js 版本,从而无感地统一了环境。

2.1.3. 强制 pnpm 版本与使用 (package.json + .npmrc)

仅仅建议使用 pnpm 是不够的,我们需要强制执行。这分为两步:声明规则开启执行

第一步:在 package.json 中声明规则

我们打开 package.json,手动添加 engines 字段,这里是我们声明项目所依赖的 Node 和 pnpm 版本的地方。

文件路径: package.json

1
2
3
4
5
6
7
8
9
10
11
12
{
"name": "prorise-admin",
"private": true,
"version": "0.0.0",
"type": "module",
"engines": {
"node": ">=20.0.0",
"pnpm": ">=9.0.0"
},
"scripts": {
"dev": "vite",
// ...

架构师视角engines 字段本身只是一种“建议”。如果用户环境不匹配,默认情况下 pnpm 只会给出一个警告,而不会阻止安装。这对于强制规范来说是远远不够的。

第二步:在 .npmrc 中开启强制执行

为了让 pnpm 严格执行 engines 中的规则,我们需要创建 .npmrc 文件来改变 pnpm 的默认行为。

我们在项目根目录创建 .npmrc 文件。

文件路径: .npmrc

1
2
3
4
# 这行配置是关键:它告诉 pnpm,必须严格检查 `engines` 字段。
# 如果当前环境不满足 `package.json` 中声明的版本要求,
# pnpm 将直接报错并终止安装,而不是仅仅给出警告。
engine-strict=true

通过这两步的结合,我们形成了一个完整的约束链:package.json 定义了版本规则,.npmrc 则赋予了这些规则强制执行的能力。

2.1.4. (可选但推荐) 脚本防御:阻止 npm 和 yarn

为了做到万无一失,我们可以利用 package.jsonpreinstall 脚本,在安装依赖前进行主动检查,彻底阻止团队成员误用 npmyarn

文件路径: package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"name": "prorise-admin",
"private": true,
"version": "0.0.0",
"type": "module",
"engines": {
"node": ">=20.0.0",
"pnpm": ">=9.0.0"
},
"scripts": {
// preinstall 是一个特殊的 npm 脚本,它会在 `pnpm install` 执行之前自动运行
"preinstall": "npx only-allow pnpm",
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
// ...
深度解析
M

为什么不直接写一个 preinstall 脚本来检查 process.env.npm_config_user_agent

E
expert

当然可以,only-allow 这个包本质上就是做了这件事。但直接使用它有两个好处:第一,意图明确,npx only-allow pnpm 这行代码的可读性远高于一个自定义的 JS 脚本。第二,社区最佳实践,它已经处理了各种边缘情况,我们无需重复造轮子。

至此,我们的项目环境规范化配置已经完成。任何试图使用错误环境的开发者都会被立即、明确地阻止,这为后续的顺利协作打下了坚如磐石的基础。


2.2. 核心配置 (一):tsconfig.json 与路径别名

环境规范只是我们的第一道防线。第二道防线,则是代码的“类型规范”。Vite 默认的 tsconfig.json 非常宽松,这对于企业级项目来说是不可接受的。

2.2.1. tsconfig.json 的企业级强化

我们打开项目根目录的 tsconfig.json,对其进行一次彻底的“加固”升级。

文件路径: 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
34
35
36
37
38
39
{
"compilerOptions": {
/* 编译目标配置 */
"target": "ESNext", // 编译目标版本,ESNext表示最新的ECMAScript标准
"useDefineForClassFields": true, // 使用标准的类字段定义语义
"lib": ["DOM", "DOM.Iterable", "ESNext"], // 包含的类型库:DOM API、DOM迭代器、最新ES特性

/* JavaScript支持 */
"allowJs": false, // 不允许编译JavaScript文件,只编译TypeScript
"skipLibCheck": true, // 跳过声明文件的类型检查,提高编译速度

/* 模块互操作性 */
"esModuleInterop": true, // 启用ES模块与CommonJS模块的互操作性
"allowSyntheticDefaultImports": true, // 允许从没有默认导出的模块中默认导入

/* 开启最严格的类型检查 */
"strict": true, // 启用所有严格类型检查选项
"forceConsistentCasingInFileNames": true, // 强制文件名大小写一致性
"noUnusedLocals": true, // 报告未使用的局部变量错误
"noUnusedParameters": true, // 报告未使用的参数错误
"noFallthroughCasesInSwitch": true, // 报告switch语句中fallthrough情况的错误

/* 模块与路径解析 */
"module": "ESNext", // 生成的模块代码格式为ESNext
"moduleResolution": "bundler", // 模块解析策略,适用于现代打包工具如Vite
"resolveJsonModule": true, // 允许导入JSON文件
"isolatedModules": true, // 确保每个文件都可以独立编译
"noEmit": true, // 不生成输出文件,只进行类型检查
"jsx": "react-jsx", // JSX编译方式,使用React 17+的新JSX转换

/* 路径别名配置 */
"baseUrl": ".", // 解析非相对模块名的基准目录
"paths": {
"@/*": ["src/*"] // 路径映射,@/* 映射到 src/* 目录
}
},
"include": ["src"], // 包含src目录下的所有文件进行编译
"references": [{ "path": "./tsconfig.node.json" }] // 项目引用,引用Node.js相关的TypeScript配置
}
深度解析:为何如此配置?
M

这份配置比 Vite 默认的多很多,重点在哪里?

E
expert

重点在三个方面:

E
expert

开启严格模式 ("strict": true):这是 TypeScript 的精髓。它会开启一系列(如 strictNullChecks, noImplicitAny 等)检查,强迫我们编写更健壮、更可预测的代码。在项目初期就开启它,远比项目中期再去修复成百上千的类型错误要容易得多。

E
expert

提升代码质量:"noUnusedLocals""noUnusedParameters" 会帮助我们发现代码中的“死代码”,保持项目的整洁。"forceConsistentCasingInFileNames" 则能避免在大小写不敏感的操作系统(如 Windows, macOS)上出现导入路径大小写混乱的问题。

E
expert
  1. 配置路径别名 ("paths"):这是本节的重点。我们定义了 @/* 作为 src/* 的别名。这意味着未来我们可以写 import '@/components/Button' 而不是 import '../../components/Button'
M

为什么 moduleResolution 要用 "bundler"

E
expert

这是 2024-2025 年的最佳实践。它最符合 Vite、Webpack 这类现代打包工具的模块解析逻辑,能更好地处理 package.json 中的 exports 字段。

2.2.2. 让 Vite 识别路径别名

仅仅在 tsconfig.json 中配置了 paths 是不够的。tsc(TypeScript 编译器)能看懂这个配置,但 Vite(我们的打包工具)看不懂。

我们需要一个“翻译官”插件,来读取 tsconfig.json 中的 paths 配置,并将其转换为 Vite 能理解的解析规则。

第一步:安装“翻译官”插件

这个插件就是 vite-tsconfig-paths。这是我们主动安装的第一个 开发依赖,因为它解决了我们“让别名在 Vite 中生效”这个迫切的基础设施需求。

1
pnpm add -D vite-tsconfig-paths

第二步:配置 vite.config.ts

我们打开 vite.config.ts,引入并使用这个插件。

文件路径: vite.config.ts

1
2
3
4
5
6
7
8
9
10
11
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths' // 1. 导入插件

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
tsconfigPaths() // 2. 加载插件
],
})

2.2.3. 验证路径别名

配置完成后,我们必须立即验证它是否生效。

我们打开 src/main.tsx,将 App.tsxindex.css 的导入路径从相对路径改为别名路径。

文件路径: src/main.tsx

1
2
3
4
5
6
7
8
9
10
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from '@/App' // 修改前: './App'
import '@/index.css' // 修改前: './index.css'

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

现在,我们清理一下 src 目录中其他默认文件:删除 src/App.csssrc/assets/react.svg。并简化 src/App.tsx

文件路径: src/App.tsx

1
2
3
4
5
6
7
8
9
function App() {
return (
<>
<h1>Prorise-Admin</h1>
</>
)
}

export default App

此时,再次运行 pnpm run dev。如果项目能正常启动并显示 “Prorise-Admin”,则证明我们的路径别名 @/* 已经成功生效!


2.3. 核心配置 (二):unplugin-auto-import 自动导入

我们已经解决了路径别名的问题。但作为架构师,我们还预见到另一个开发体验(DX)的痛点:重复的 import 语句

在 React 项目中,我们几乎在每个文件都需要 import { useState, useEffect } from 'react'。未来引入 Antd 或其他库时,import { Button, Input, ... } from 'antd' 这样的语句也会变得非常繁琐。

我们可以在项目奠基阶段,通过“自动导入”插件来一劳永逸地解决这个问题。

2.3.1. 安装与配置 unplugin-auto-import

我们将使用 unplugin-auto-import,它是一个功能强大的 Vite/Webpack 插件。

第一步:安装插件

1
pnpm add -D unplugin-auto-import

第二步:配置 vite.config.ts

我们再次打开 vite.config.ts,添加 AutoImport 的配置。

文件路径: vite.config.ts

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
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
import AutoImport from 'unplugin-auto-import/vite' // 1. 导入

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
tsconfigPaths(),
// 2. 添加配置
AutoImport({
// 目标文件类型
include: [
/\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
],
// 自动导入的预设包
// 我们这里只开启 react,未来需要时再添加 'antd', 'react-router-dom' 等
imports: ["react"],
// 自动生成 'auto-imports.d.ts' 类型声明文件
dts: true,
// 解决 ESLint/BiomeJS 报错问题
eslintrc: {
enabled: true,
},
}),
],
})

第三步:配置 TypeScript 以识别类型声明

执行 pnpm run dev。插件会自动在项目根目录生成两个文件:

  1. auto-imports.d.ts:这是给 TypeScript 看的类型声明文件,它告诉 TS useState 等是全局可用的,避免类型报错。
  2. .eslintrc-auto-import.json:这是给 ESLint/BiomeJS 看的,告诉它们这些是全局变量,避免 lint 报错。(我们将在下一章配置 Biome 时用到它)

重要提示:由于 auto-imports.d.ts 生成在项目根目录,而我们的 tsconfig.jsoninclude 配置默认只包含 ["src"] 目录,TypeScript 编译器无法自动发现这个类型声明文件。我们需要手动将其添加到配置中。

打开 tsconfig.json,找到文件末尾的 include 配置,修改如下:

文件路径: tsconfig.json

1
2
3
4
5
6
7
{
"compilerOptions": {
// ... 其他配置保持不变
},
"include": ["src", "auto-imports.d.ts"], // 添加类型声明文件
"references": [{ "path": "./tsconfig.node.json" }]
}

修改完成后,在 VSCode 中 重启 TypeScript 服务器 以使配置生效:

  • Ctrl + Shift + P(macOS 为 Cmd + Shift + P
  • 输入并执行:TypeScript: Restart TS Server

2.3.2. 验证自动导入

现在,最激动人心的一刻到来了。我们打开 src/App.tsx,直接使用 useState

文件路径: src/App.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 注意:文件顶部没有 import { useState } from 'react'
// 但 useState 仍然可以被正常使用和提示

function App() {
const [count, setCount] = useState(0)

return (
<>
<h1>Prorise-Admin</h1>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</>
)
}

export default App

刷新页面,你会发现计数器工作得非常好!useState 被自动注入了。