Vue 生态(一):专业基石 · 构筑自动化与规范化的开发环境

第一章:专业基石 · 构筑自动化与规范化的开发环境

摘要: 卓越的应用始于一个无可挑剔的基石。在本章中,我们将摒弃“能跑就行”的临时主义,以“架构师”的视角,从零开始,亲手构筑一个工业级的 Vue 3 开发环境。我们将深入探讨为何 pnpm 是现代项目的首选,如何深度配置 Vitetsconfig.json,并最终建立一套由 ESLintPrettier 协同工作的、完全自动化的代码质量保障体系。学完本章,你将拥有一个坚如磐石、高度规范化的项目起点,为后续的高效开发扫清一切障碍。


在本章中,我们将循序渐进地完成以下核心任务,为我们的项目打下坚实的地基:

  1. 首先,我们将选择并使用最高效的包管理器 pnpm 来初始化我们的 Vite + TypeScript 项目。
  2. 接着,我们将深入 Vite 的心脏,进行核心配置,以支持更优雅的开发。
  3. 然后,我们将为项目引入代码的“纪律委员”——ESLintPrettier,并让它们协同工作。
  4. 之后,我们将解构 tsconfig.json 文件,理解 TypeScript 编译器的核心指令。
  5. 最后,我们将确立一套清晰、可扩展的“功能驱动”项目结构,为未来的代码组织铺平道路。

1.1. 包管理器选型:为何 pnpm 是现代大型项目的首选

在开始之前,我们首先要选择一个包管理器。虽然 npmyarn 同样流行,但在 2025 年,对于追求极致性能和磁盘空间效率的专业团队而言,`pnpm` 已成为不二之选。

极致的磁盘空间效率
pnpm 采用内容寻址存储,相同版本的依赖在磁盘上仅存一份副本,多项目共享,大幅节省硬盘空间。

闪电般的安装速度
本地缓存命中时,pnpm 通过硬链接而非复制文件完成安装,速度显著快于传统拷贝。

严格的依赖管理
非扁平化 node_modules 杜绝“幽灵依赖”,只能访问 package.json 中显式声明的包,项目更健壮。

如果尚未安装 pnpm,可用 npm 全局安装:

1
npm install -g pnpm

1.2. 初始化项目:从一个最纯净的 Vite 模板开始

在过去,搭建项目可能需要手动配置 Webpack 等复杂的工具。幸运的是,我们现在有了 Vite——一个快如闪电的现代前端构建工具。它能让我们在几秒钟内启动一个项目。

我们的策略: 从最简化的模板开始。为什么?因为这就像学习解剖学,从一个干净的骨架开始,我们能更清晰地看到后续添加的每一个“器官”(如路由、状态管理、UI 库)是如何与主体结合,并理解其存在的价值。

现在,打开你的终端,导航到希望创建项目的目录,然后运行:

1
pnpm create vite

Vite 会像一位友好的向导,引导你完成几个选择:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
◇  Project name:
│ prorise-vue3-guide // 你的项目名称

◇ Select a framework:
│ Vue // 我们选择 Vue 框架

◇ Select a variant:
│ TypeScript // 我们选择 TypeScript 变体

◇ Scaffolding project in D:\web\prorise-vue3-guide...

└ Done. Now run:

cd prorise-vue3-guide
pnpm install
pnpm run dev

按照提示,进入项目目录,安装依赖,并启动它:

1
2
3
cd prorise-vue3-guide
pnpm install
pnpm run dev

浏览器会自动打开 http://localhost:5173/。看到那个旋转的 Vue Logo 了吗?恭喜你,你的项目地基已成功铺设!现在,让我们深入内部,看看 Vite 为我们准备了什么。


1.3. 项目初探:解剖 Vue 应用的“生命周期”

在我们添加任何新东西之前,最重要的一步是理解现有的一切是如何协同工作的。让我们来追踪一下,从你打开浏览器到看到页面,这背后发生了怎样的“故事”。

这是我们项目的初始结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# prorise-vue3-guide/
├── public/
│ └── vite.svg # 公共静态资源
├── src/
│ ├── assets/
│ │ └── vue.svg # 源码中的资源
│ ├── components/
│ │ └── HelloWorld.vue # 一个示例组件
│ ├── App.vue # 根组件
│ ├── main.ts # 一切的起点
│ ├── style.css # 全局样式
│ └── vite-env.d.ts # Vite 环境变量的 TS 类型声明
├── .gitignore
├── index.html # 应用的“外壳”
├── package.json # 项目的“身份证”
├── tsconfig.json # TypeScript 的“说明书”
└── vite.config.ts # Vite 的“控制面板”

故事的开始:index.html

这不仅仅是一个普通的 HTML 文件,它是我们整个单页应用(SPA)的“舞台”。

1
2
3
4
5
6
7
8
9
10
11
<!doctype html>
<html lang="en">
<head>
<!-- ... meta tags ... -->
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app"></div> <!-- 关键:这是我们 Vue 应用将要“入住”的地方 -->
<script type="module" src="/src/main.ts"></script> <!-- 关键:Vite 会从这里开始加载我们的代码 -->
</body>
</html>

当浏览器加载这个文件时,它会发现一个 idappdiv 和一个指向 /src/main.tsscript 标签。这正是我们故事的下一站。

第一站:src/main.ts - 应用的启动引擎

这个 ts 文件是整个 Vue 应用的入口(Entry Point)。它的职责非常专一和重要:

1
2
3
4
5
6
7
8
9
10
11
12
// 从 'vue' 包中导入 createApp 函数
import { createApp } from 'vue'

// 导入我们的根组件 App.vue
import App from './App.vue'

// 导入全局样式
import './style.css'

// 1. 创建应用实例:createApp(App) 会将 App 组件作为整个应用的根。
// 2. 挂载应用:.mount('#app') 告诉 Vue 实例,去 index.html 中找到 id = "app" 的元素,然后把整个应用渲染到那个位置。
createApp(App).mount('#app')

你可以把 main.ts 想象成汽车的点火钥匙。它启动了 App 这个“引擎”,并把它“安装”在 index.html 这个“车身”上。

第二站:src/App.vue - 万物之根的“根组件”

App.vue 是所有组件的“祖先”。未来我们所有的页面、弹窗、按钮,最终都会被嵌套在它内部。

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
<script setup lang="ts">
// 这是 Vue 3 的组合式 API 写法,非常简洁
import HelloWorld from './components/HelloWorld.vue' // 导入子组件
</script>

<template>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" /> <!-- 在模板中使用子组件 -->
</template>

<style scoped>
/* scoped 属性表示这里的样式只对当前组件生效,不会污染其他组件 */
.logo {
height: 6em;
padding: 1.5em;
/* ... */
}
</style>

这个文件完美展示了 Vue 单文件组件(SFC)的魅力:

  • <script setup>: 存放组件的逻辑和数据(JavaScript/TypeScript)。
  • <template>: 存放组件的结构(HTML)。
  • <style>: 存放组件的样式(CSS)。

它导入并使用了 HelloWorld.vue 组件,这构成了我们看到的欢迎页面。现在,我们对项目的初始状态和运行流程有了清晰的认知。是时候为这个“毛坯房”进行“精装修”了。


1.4. CSS 工程化基石:集成 SCSS

虽然 Vite 模板自带了 CSS 支持,但在任何一个真实项目中,直接编写原生 CSS 会很快遇到瓶颈:没有变量、无法嵌套、代码难以复用。为了解决这些问题,我们需要引入 CSS 预处理器,而 SCSS (Sassy CSS) 是当前社区中最成熟、最受欢迎的选择。它为我们提供了变量、嵌套、混入 (Mixin) 等强大的编程能力,是 CSS 工程化的第一步。

1.4.1. 安装 SASS 编译器

Vite 对 SCSS 的支持是开箱即用的,我们唯一需要做的就是安装 SASS 的编译器。

1
pnpm add -D sass

是的,就这么简单!Vite 在检测到你的项目中有 sass 这个开发依赖后,会自动用它来处理 .scss.sass 文件。

1.4.2. 创建并引入全局样式文件

一个好的实践是建立一个全局的 SCSS 文件,用来存放项目的主题色、通用间距、字体定义等变量,以及一些全局的样式重置 (CSS Reset)。

  1. 创建文件: 在 src/ 目录下创建一个新的 assets 文件夹,并在其中新建 styles/main.scss

    1
    2
    3
    4
    5
    src/
    ├── assets/
    │ └── styles/
    │ └── main.scss
    └── ...
  2. 编写 SCSS: 打开 src/assets/styles/main.scss,让我们来体验一下 SCSS 的魔力。

    文件路径: src/assets/styles/main.scss (新建)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    .card {
    background: #fff;
    padding: 2rem;
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);

    h1 {
    font-size: 2em;
    margin-bottom: 1.5rem;
    color: #2c3e50; // 这里暂时硬编码
    }
    }
  3. 全局引入: 最后,我们需要在应用的入口 main.ts 中引入这个文件,让它对整个项目生效。

    文件路径: src/main.ts (修改)

    1
    2
    3
    4
    5
    6
    7
    import { createApp } from 'vue'
    import App from './App.vue'

    // 将之前的 CSS 引入替换为我们的主 SCSS 文件
    import './assets/styles/main.scss'

    createApp(App).mount('#app')

1.4.3. 在 Vue 组件中使用 SCSS

现在,我们可以在单个 Vue 组件中编写带作用域的 SCSS 样式了。

文件路径: src/App.vue (修改)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script setup lang="ts">
import { ref } from 'vue';

const count = ref(0);
</script>

<template>
<div class="card">
<h1>Hello, Prorise!</h1>
</div>
</template>

<!-- 关键改动:将 lang="css" 改为 lang="scss" -->
<style lang="scss" scoped>
// 注意,我们的main.scss其实是全局scss文件,这个样式仅作测试,后续是要删改的,这可不是最佳规范,仅做测试
@import '../src/assets/styles/main.scss';
</style>

重启你的开发服务器 (pnpm run dev),你会看到一个样式更精致的卡片。我们已经成功地将 SCSS 集成到了工作流中,为编写可维护、可扩展的样式代码打下了坚实的基础。


1.5. 提升开发效率:路径别名与 API 自动导入

随着项目变大,我们的代码中会出现两种常见的“坏味道”,它们虽然不影响功能,却严重拖慢开发节奏、降低代码可读性:

  1. 深层相对路径import MyButton from '../../../components/MyButton.vue'
  2. 重复的 API 导入import { ref, computed, onMounted } from 'vue'

本章节将介绍两大神器来彻底解决这两个问题:路径别名API 自动导入。它们能让你的 import 语句变得干净、智能且高效。


1.5.1. 解决路径噩梦:配置路径别名 (@)

路径别名让我们可以用一个简短的符号(通常是 @)来代替冗长的相对路径,直达项目的核心目录(如 src)。

目标效果:
import MyButton from '../../../components/MyButton.vue'
变为 ->
import MyButton from '@/components/MyButton.vue'

这需要两步配置:一步告诉 TypeScript,另一步告诉 Vite

第一步:告知 TypeScript

tsconfig.json 是 TS 编译器的配置文件。在 Vite 创建的项目中,对 src 目录生效的是 tsconfig.app.json。因此,我们必须在 tsconfig.app.json 中添加别名配置

文件路径: tsconfig.app.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
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,

/* 新增的别名配置 */
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
]
}

注意:请确保你的根 tsconfig.json 文件保持初始状态,不要在其中添加 compilerOptions。它应该只包含 filesreferences 字段。

第二步:告知 Vite

现在 TS 知道别名的存在了,但 Vite 在打包运行时还不知道。我们需要修改 vite.config.ts

文件路径: vite.config.ts (修改)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// (新增) 引入 node:url 模块
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
],
// (新增) resolve 配置项
resolve: {
alias: {
// 设置别名
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})

代码解读:
fileURLToPath(new URL('./src', import.meta.url)) 这行代码的作用是获取 vite.config.ts 文件所在目录下的 src 文件夹的绝对路径。这样,Vite 就知道所有以 @/ 开头的导入都应映射到项目的 src 目录。

关键一步:重启 TS 服务

修改 tsconfig 文件后,VSCode 可能不会立即生效。你需要 重启 TypeScript 服务 来加载新配置。

  1. 在 VSCode 中,按下 Ctrl+Shift+P (Windows/Linux) 或 Cmd+Shift+P (Mac)。
  2. 在弹出的命令框中输入并选择 TypeScript: Restart TS Server

完成后,路径别名就可以正常工作,并带有智能提示了。


1.5.2. 终结 import 烦恼:配置 API 自动导入

我们刚刚解决了路径问题,现在来解决另一个烦恼:重复的 import 语句。unplugin-auto-import 这款神器可以为我们自动导入常用的 API,让代码回归纯粹的逻辑。

目标效果:

1
2
3
4
5
6
7
8
<script setup lang="ts">
// 不需要再写 import { ref, onMounted } from 'vue'
const count = ref(0)

onMounted(() => {
console.log('Component mounted!')
})
</script>

第一步:安装插件

1
2
# 使用 pnpm
pnpm add -D unplugin-auto-import

第二步:配置 Vite

我们需要在 vite.config.ts 中引入并使用这个插件。它会与我们之前配置的路径别名共存。

文件路径: 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
29
30
31
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// (新增) 引入 AutoImport
import AutoImport from 'unplugin-auto-import/vite'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
// (新增) AutoImport 配置
AutoImport({
// 定义需要自动导入的模块
imports: [
'vue',
'vue-router',
// 'pinia', // 如果你使用了 Pinia
// '@vueuse/core', // 如果你使用了 VueUse
],
// (关键) 为自动生成的类型声明文件指定路径
// 这能让 TypeScript 知道这些全局变量的类型
dts: 'src/types/auto-imports.d.ts',
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})

配置解读:

  • imports: 一个预设数组,告诉插件需要为哪些库生成自动导入。'vue''vue-router' 几乎是必选项。
  • dts: 这是最关键的一步。插件会扫描 imports 指定的库,将其所有导出的 API 生成一个 TypeScript 类型声明文件(.d.ts)。这个文件告诉了 TypeScript,像 ref, computed 这些变量是全局可用的,从而避免了类型错误和提供了代码提示。

第三步:让 TypeScript 识别自动导入的类型

Vite 启动后,会在 src/types/ 目录下自动生成 auto-imports.d.ts 文件。但我们还需要告诉 TypeScript 去“读取”这个文件。

修改 tsconfig.app.json,将这个自动生成的文件路径添加到 include 数组中。

文件路径: tsconfig.app.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
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,

/* 新增的别名配置 */
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"src/types/**/*.d.ts" // (新增) 确保 TS 能扫描到自动生成的类型文件
]
}

如果你的 dts 路径配置不同,请相应修改 include 中的路径。src/**/*.d.ts 实际上已经包含了 src/types/auto-imports.d.ts,但显式地添加一条更具体的路径 src/types/**/*.d.ts 是一个好习惯,可以确保万无一失。

最后,再次 重启 TS 服务 (Ctrl+Shift+P -> TypeScript: Restart TS Server),让所有配置生效。现在,你可以尽情享受无需手动 import Vue API 的清爽编码体验了!


1.6. 自动化代码纪律:铸造团队协作的统一语言

重要信息: 如果你是初学者,我不建议你继续阅读后续的 1.6 ~ 1.7 章节,因为这和打包部署有关,没有到打包部署的阶段直接阅读这部分的笔记会导致不清晰,所以我再次事先提醒

至此,我们的项目已经拥有了坚实的结构基础。但一个真正的工业级项目,不仅要能构建,更要 易于维护便于协作。想象一下,在一个团队中,张三习惯用双引号,李四坚持用单引号;王五提交的代码忘记移除调试信息,导致生产环境出现意外的日志。这些微小的不一致性累积起来,会极大地侵蚀代码库的可读性和项目的稳定性。

我们需要一套自动化的“纪律系统”,它不依赖于任何人的记忆或自觉,而是在代码层面就建立起一套统一的、不可逾越的规范。本节,我们将从零开始,分三步构建这套体系:

  1. 制定规则: 通过 ESLintPrettier 定义代码的质量与风格标准。
  2. 编辑器集成: 配置 VS Code,实现保存文件时自动应用这些规则,提升开发体验。
  3. 流程强制: 使用 Huskylint-staged 在代码提交前强制执行检查,打造最终的质量防线。

1.6.1. ESLint 的现代化配置:解构 eslint.config.js

ESLint 的配置方式已经迎来了一场革命,告别了传统的 .eslintrc 文件,全面转向了名为 eslint.config.js 的现代化配置文件。这种新范式基于标准的 JavaScript 模块,赋予了我们前所未有的灵活性和清晰度。它不再是一个简单的 JSON 或 YAML 文件,而是一个 配置对象的数组,我们可以像编写普通代码一样,用导入、函数、逻辑判断来动态地构建我们的规则集。

第一步:安装核心依赖

在开始配置之前,我们首先需要将所有必要的“专家”请入我们的项目。

1
pnpm add -D eslint @eslint/js typescript-eslint eslint-plugin-vue eslint-config-prettier globals

这行命令为我们安装了:

  • eslint & @eslint/js: ESLint 本体及其官方核心规则集。
  • typescript-eslint: 用于理解和校验 TypeScript 语法的“翻译官”和规则集,是 TS 项目的必备。
  • eslint-plugin-vue: 专为 .vue 单文件组件量身打造的规则插件。
  • eslint-config-prettier: 用于关闭所有与 Prettier 冲突的 ESLint 规则的“调解员”。
  • globals: 帮助我们预设浏览器、Node.js 等环境的全局变量,避免 undefined variable 错误。

第二步:创建并解构 eslint.config.js

现在,我们在项目根目录创建 eslint.config.js。我们将逐块分析这个配置文件的构成,让你不仅知其然,更知其所以然。

文件路径: eslint.config.js (新建)

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// 1. 导入所有需要的插件和配置模块
import globals from 'globals';
import eslintJs from '@eslint/js';
import tseslint from 'typescript-eslint';
import eslintPluginVue from 'eslint-plugin-vue';
import eslintConfigPrettier from 'eslint-config-prettier';

// 2. 导出一个配置数组
export default tseslint.config(
// -------- 配置块 1: 全局忽略规则 --------
/**
* 作用: 告诉 ESLint 永远不要去检查某些文件或目录。这对于排除构建产物 (dist)、
* 第三方依赖 (node_modules) 或项目自身的配置文件至关重要,能极大地提升 lint 速度并避免不必要的错误。
* 语法: ignores 是一个字符串数组,每个字符串都是一个 glob 模式。
*/
{
ignores: [
'dist',
'node_modules',
'*.config.js',
'public'
],
},

// -------- 配置块 2: 全局语言选项和规则 --------
/**
* 作用: 这是一个基础配置块,它不通过 files 属性限定范围,因此会应用于所有被检查的文件。
* 我们在这里设置了通用的 ECMAScript 版本、模块类型以及浏览器/Node.js 全局变量。
* 语法:
* - languageOptions: 一个对象,用于配置解析器 (parser)、解析器选项 (parserOptions) 和全局变量 (globals)。
* - rules: 一个对象,用于定义具体的代码检查规则。键是规则名,值是规则的严重性 ("off"/0, "warn"/1, "error"/2),后面可能跟着规则的选项。
*/
{
languageOptions: {
// 设置全局通用的解析器选项
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
// 定义全局变量
globals: {
...globals.browser,
...globals.es2021,
},
},
// 应用于所有文件的基础规则
rules: {
'no-console': 'warn', // 对 console.log 等给出警告 - 意味着在代码中使用 console.log、console.warn、console.error 等控制台输出语句时,ESLint 会显示警告提示,提醒开发者在生产环境中应该移除这些调试语句
},
},

// -------- 配置块 3: 应用 ESLint 和 TypeScript 的推荐规则 --------
/**
* 作用: 这是新配置模式下最便捷的用法。我们不再使用 extends 字符串,而是直接将导入的预设配置对象展开到数组中。
* 这使得配置的来源一目了然。
* 语法: eslintJs.configs.recommended 和 ...tseslint.configs.recommended 都是预先打包好的配置对象数组,
* 我们直接将它们混入到我们的主配置数组中。
*/
eslintJs.configs.recommended,
...tseslint.configs.recommended,

// -------- 配置块 4: 针对 Vue 文件的专属配置 --------
{
// 明确指定此配置块只对 .vue 文件生效
files: ['**/*.vue'],
// 应用 Vue 插件的推荐规则
extends: [
...eslintPluginVue.configs['flat/recommended'],
],
// 为 Vue 文件指定语言选项
languageOptions: {
parserOptions: {
// Vue 的 <script> 部分需要 TypeScript 解析器
parser: tseslint.parser,
},
},
rules: {
// 自定义或覆盖 Vue 相关的规则
'vue/multi-word-component-names': 'off', // 暂时关闭组件名必须多词的规则
'vue/html-self-closing': ['error', {
html: { void: 'always', normal: 'never', component: 'always' },
}],
},
},

// -------- 配置块 5: 禁用与 Prettier 冲突的规则 --------
/**
* 作用: ESLint 的某些规则(如 max-len)与 Prettier 的格式化功能是重叠的。
* eslint-config-prettier 这个配置对象的作用就是关闭所有可能与 Prettier 发生冲突的 ESLint 规则。
* 核心原则: 把它放在配置数组的最后一位。这样可以确保它有最高的优先级,
* 能够覆盖掉前面所有配置块中定义的任何冲突规则,从而将格式化的职责完全、纯粹地交给 Prettier。
* 这一项必须是数组的最后一项,以确保它能覆盖所有前面的规则!
*/
eslintConfigPrettier,
);


1.6.2. 开发体验的飞跃:与 VS Code 深度集成

理想的开发流程是,开发者只需专注于业务逻辑,规范的执行应是无感的、自动的。我们将通过配置 VS Code 来实现 保存即修复 的“心流”体验。

准备工作: 如果你发现跟随本文进行配置没有实现预期效果,请尝试 重启 VS Code。通常,修改 ESLint/Prettier 配置或 settings.json 后都需要重启来使其生效。

第一步:安装 VS Code 插件

在 VS Code 的扩展市场(Ctrl+Shift+XCmd+Shift+X)中,搜索并安装两个核心插件:

  1. ESLint: 由 Microsoft 出品,它会读取我们的 eslint.config.js,在编辑器中实时标记出不符合规范的代码。
  2. Prettier - Code formatter: 最流行的 Prettier 插件,负责执行代码格式化。

第二步:配置 settings.json

这是实现自动化的关键。按 Ctrl+Shift+P (或 Cmd+Shift+P),输入 settings,选择 “Preferences: Open User Settings (JSON)”,打开你的 settings.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
{
// ------------------- 核心配置 START -------------------

// 1. 设置 Prettier 为默认格式化器
// 这确保了当我们手动或自动格式化时,调用的是 Prettier
"editor.defaultFormatter": "esbenp.prettier-vscode",

// 2. 开启“保存时格式化”
// 每次按下 Ctrl+S,都会触发默认格式化器 (Prettier)
"editor.formatOnSave": true,

// 3. 开启“保存时运行 ESLint 修复”
// 在保存时,会先执行 ESLint 的自动修复能力(修复带扳手图标的规则)
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},

// ------------------- 核心配置 END -------------------

// (可选但推荐) 为不同语言指定默认格式化器,避免与其他插件冲突
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

现在,魔法发生了!

当你编写代码并保存时,VS Code 会执行一个协同动作:

  1. ESLint 首先介入:根据 eslint.config.js,自动修复所有可修复的语法和质量问题。
  2. Prettier 紧随其后:对 ESLint 修复后的代码,进行最终的、彻底的风格格式化。

这个流程完美地解决了 ESLint 和 Prettier 的协同问题,让你的代码在保存的瞬间就达到了团队规范的最高标准。


1.7. 终极质量关卡:Husky 与自动化提交检查

IDE 级别的配置能极大地提升个人开发效率,但它依赖于开发者的自觉和环境统一。一个健壮的工程体系,必须在代码流入版本库的最后一道关卡——git commit——设立“哨兵”。无论开发者的本地环境如何,这个“哨兵”都会强制执行所有代码检查,确保入库的每一行代码都纯净无瑕。

我们将通过一个无缝的工作流,集成 Husky (v9+) 与 lint-staged,并最终引入 commitlint,来构建这道自动化的质量防线。

第一步:安装与初始化

我们首先将所有需要的工具一次性安装到项目中。

1
pnpm add -D husky lint-staged @commitlint/cli @commitlint/config-conventional

安装完成后,我们通过 husky init 命令来初始化 Husky。

1
npx husky init

这个命令会自动在你的 package.json 中添加一个 prepare 脚本,并创建一个 .husky/ 目录以及一个示例的 pre-commit 钩子文件。prepare 脚本能确保团队其他成员在 pnpm install 后,Husky 也会被自动激活。


第二步:配置 pre-commit 钩子与 lint-staged

现在,我们来配置 pre-commit 钩子,让它在每次提交前,通过 lint-staged 自动检查并修复暂存区的代码。

首先,在 package.json 文件中添加 lint-staged 的配置,精确地告诉它对哪些文件执行什么命令。

文件路径: package.json (添加)

1
2
3
4
5
6
7
8
9
{
// ...其他配置...
"lint-staged": {
"*.{vue,ts,tsx,js,jsx}": [
"eslint --fix",
"prettier --write"
]
}
}

接着,编辑由 husky init 生成的 .husky/pre-commit 文件,将其内容修改为执行 lint-staged

文件路径: .husky/pre-commit (修改)

1
npx lint-staged

至此,代码的提交前自动检查与修复流程已经配置完毕。


第三步:配置 commit-msg 钩子与 commitlint

最后,我们来规范提交信息。一个清晰的提交历史对于项目维护和版本发布至关重要。

首先,在项目根目录创建 commitlint.config.js 文件,定义我们团队需要遵循的提交类型规范。

文件路径: commitlint.config.js (新建)

1
2
3
4
// commitlint.config.js
export default {
extends: ["@commitlint/config-conventional"],
};

然后,我们需要手动创建 commit-msg 钩子文件。在 .husky/ 目录下创建一个名为 commit-msg 的文件,并填入以下内容。

文件路径: .husky/commit-msg (新建)

1
npx --no -- commitlint --edit ${1}

这个脚本会在 git commit 时被触发,调用 commitlint 来校验用户输入的提交信息。
全自动化的质量防线已构建完成!
现在,当任何团队成员尝试提交代码时:

  1. pre-commit 钩子触发,lint-staged 会自动对暂存区的文件运行 ESLint 修复和 Prettier 格式化。如果存在无法自动修复的错误,提交将被 中止
  2. 通过代码检查后,用户输入的 commit message 会被 commit-msg 钩子拦截。
  3. commitlint 会检查该信息是否符合 commitlint.config.js 中定义的规范。如果不符合,提交同样会被 中止,并给出友好的提示。

这套体系,为我们的项目建立了一道坚不可摧的自动化质量防线。至此,第一章关于环境搭建与规范化的内容,已经达到了工业级的完备程度,并完全适配了最新的工具链版本。


1.8. 架构师的蓝图:构建可维护、可扩展应用的指导原则

注意: 本章节的目的是讲解 Vue 项目的最佳实践,如果你是一个新手,可以略过此章节,在后续的章节对于 Vue3 有了一定了解之后可以回来观看此章节,该章节会涉及到我们后续讲解的所有知识,对于他进行一个结构化的组件划分,以及每一个知识点的最佳实践

在我们开始编写第一行业务代码之前,最后一步,也是至关重要的一步,是为我们的“应用城市”绘制一份清晰的 架构蓝图。一个项目的成败,往往在最初的结构设计和原则确立时就已注定。一个清晰、可预测的结构,能让开发者在项目的任何阶段都迅速定位,极大地提升开发效率和项目的可维护性。

本节将阐述我们贯穿整个指南的核心开发哲学与最佳实践,它将成为我们构建高质量应用的“宪法”。

1.8.1. 结构即思想:功能内聚的目录组织

传统的按文件类型(components, views, store)组织项目的方式在小型项目中尚可,但在大型应用中会导致“功能分散”的维护噩梦——修改一个功能,你可能需要在多个文件夹之间来回跳转。

我们将拥抱一种更现代、更具扩展性的 “功能内聚”(Feature-based) 结构。虽然我们仍会保留顶层的分类目录,但在业务复杂区域,我们会优先考虑按功能模块来组织代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# prorise-vue3-guide/
├── src/
│ ├── apis/ # (全局) API 请求模块
│ ├── assets/ # (全局) 静态资源
│ ├── components/
│ │ └── common/ # (全局) 无业务逻辑的基础组件 (BaseButton.vue)
│ ├── features/ # ✨ 核心业务功能区
│ │ ├── auth/ # - 认证功能
│ │ │ ├── components/ # - LoginModal.vue
│ │ │ ├── composables/ # - useAuth.ts
│ │ │ └── store/ # - authStore.ts
│ │ └── user-profile/ # - 用户资料功能
│ │ ├── components/ # - UserProfileCard.vue
│ │ ├── views/ # - ProfilePage.vue
│ │ └── types/ # - user.d.ts
│ ├── router/ # (全局) 路由配置
│ ├── store/ # (全局) Pinia 状态管理
│ ├── types/ # (全局) TypeScript 类型定义
│ └── utils/ # (全局) 通用工具函数
└── ...

核心思想: 将实现同一个业务功能所需的所有代码(组件、状态、逻辑、类型)都放在一个文件夹内。这使得功能本身变得 高内聚、低耦合,易于理解、维护、甚至是迁移。

我们一般都会在新的项目用指令快捷搭建,适用于 Windows 的 PowerShell 命令如下:


1.8.2 一键生成项目结构 PowerShell 脚本

这个脚本将为您完成以下自动化任务:

  1. 创建所有必需的目录,包括嵌套的 features 目录。

  2. 处理 Vite 默认文件

    • src/App.vue 移动到 src/views/App.vue
    • src/components/HelloWorld.vue 移动到 src/components/common/HelloWorld.vue
  3. 自动修复代码引用

    • 更新 src/main.ts 中对 App.vue 的引用路径。
    • 更新新的 src/views/App.vue 中对 HelloWorld.vue 的引用路径。
  4. 提供清晰的执行反馈。

如何使用

  1. 创建脚本文件: 在你刚创建的 Vite 项目的 根目录 (与 package.json 同级),创建一个新文件,命名为 init-structure.ps1

  2. 复制粘贴代码: 将下面的完整脚本代码复制并粘贴到 init-structure.ps1 文件中。

  3. 运行脚本:

在项目根目录打开 PowerShell 终端(在 VS Code 中,可以通过 Terminal > New Terminal,并确保是 PowerShell)。

由于安全策略,PowerShell 默认可能不允许执行脚本。你需要先为当前会话 临时 放宽执行策略。运行以下命令:

1
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force

现在,执行我们的脚本:

1
.\init-structure.ps1
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# -----------------------------------------------------------------------------
# Prorise Vue 3 Guide - Project Structure Initializer
#
# This script sets up the recommended feature-based directory structure
# for the Vue 3 + TypeScript guide. It creates directories and intelligently
# moves initial Vite template files to their new locations.
#
# Usage:
# 1. Save this file as `init-structure.ps1` in your project root.
# 2. Open PowerShell in the project root.
# 3. Run: Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force
# 4. Run: .\init-structure.ps1
# -----------------------------------------------------------------------------

# Start with a clean view
Clear-Host

# --- Configuration ---
$ForegroundColor = "Green"
Write-Host "🚀 Initializing Prorise Vue 3 Guide project structure..." -ForegroundColor $ForegroundColor
Write-Host "---------------------------------------------------------" -ForegroundColor $ForegroundColor

# Check if the 'src' directory exists
if (-not (Test-Path -Path ".\src")) {
Write-Host "❌ Error: 'src' directory not found. Please run this script from the root of your Vite project." -ForegroundColor Red
exit
}

# --- 1. Define and Create Directory Structure ---
Write-Host "[1/3] Creating directory structure..." -ForegroundColor Cyan

$directories = @(
"src/apis",
"src/assets",
"src/components/common",
"src/features/auth/components",
"src/features/auth/composables",
"src/features/auth/store",
"src/features/user-profile/components",
"src/features/user-profile/views",
"src/features/user-profile/types",
"src/router",
"src/store",
"src/types",
"src/utils",
"src/views"
)

foreach ($dir in $directories) {
if (-not (Test-Path -Path $dir)) {
New-Item -Path $dir -ItemType Directory -Force | Out-Null
Write-Host " [CREATED] $dir"
} else {
Write-Host " [EXISTS] $dir" -ForegroundColor Yellow
}
}

# --- 2. Move Vite's Default Files ---
Write-Host "[2/3] Reorganizing default Vite files..." -ForegroundColor Cyan

# Move App.vue
$appSource = "src/App.vue"
$appDest = "src/views/App.vue"
if (Test-Path $appSource) {
Move-Item -Path $appSource -Destination $appDest
Write-Host " [MOVED] $appSource -> $appDest"
}

# Move HelloWorld.vue
$componentSource = "src/components/HelloWorld.vue"
$componentDest = "src/components/common/HelloWorld.vue"
if (Test-Path $componentSource) {
Move-Item -Path $componentSource -Destination $componentDest
Write-Host " [MOVED] $componentSource -> $componentDest"
}

# Remove the now-empty original src/components directory
$oldComponentsDir = "src/components"
if ((Test-Path $oldComponentsDir) -and (Get-ChildItem -Path $oldComponentsDir).Count -eq 0) {
Remove-Item -Path $oldComponentsDir -Recurse
Write-Host " [REMOVED] Empty directory $oldComponentsDir"
}

# --- 3. Update Import Paths in Code ---
Write-Host "[3/3] Automatically updating import paths..." -ForegroundColor Cyan

# Update main.ts
$mainTsPath = "src/main.ts"
if (Test-Path $mainTsPath) {
(Get-Content $mainTsPath) -replace "'./App.vue'", "'@/views/App.vue'" | Set-Content $mainTsPath
Write-Host " [UPDATED] Import path in $mainTsPath"
}

# Update new App.vue
$appVuePath = "src/views/App.vue"
if (Test-Path $appVuePath) {
(Get-Content $appVuePath) -replace "'./components/HelloWorld.vue'", "'@/components/common/HelloWorld.vue'" | Set-Content $appVuePath
Write-Host " [UPDATED] Import path in $appVuePath"
}

# --- Completion ---
Write-Host "---------------------------------------------------------" -ForegroundColor $ForegroundColor
Write-Host "✅ All done! Your project structure is now set up." -ForegroundColor $ForegroundColor
Write-Host " You can now start building your application following the guide's architecture." -ForegroundColor $ForegroundColor

1.8.3. 组件设计的艺术:单一职责与清晰契约

组件是应用的基石。我们将遵循以下原则来设计组件:

  • 小而美: 每个组件只做一件事,并把它做好(单一职责原则)。一个臃肿的“万能组件”是维护的灾难。
  • 清晰的内外之别:
    • 输入 (Props): 组件的数据来源应尽可能通过 props 传入。我们将在 TypeScript 的加持下,为 props 建立严格的类型契约。
    • 输出 (Emits): 组件不应直接修改外部状态。当内部状态变化需要通知外部时,必须通过 emits 发出事件。这保证了 单向数据流 的可预测性。
  • 内容分发 (Slots): 当一个组件需要承载不确定的、复杂的 UI 结构时,不要试图用无数的 props 去控制它。slots(插槽)是实现 UI 组合与控制反转的优雅之道。

1.8.4. 逻辑复用的范式:拥抱组合式函数 (Composables)

在 Vue 3 的世界里,组合式函数 (Composables) 是逻辑复用的黄金标准。我们将彻底摒弃 Mixins,因为它存在来源不明、命名冲突等问题。

我们的原则是:任何跨组件的、有状态的逻辑,都应该被抽离成一个 Composable。一个优秀的 Composable 应该是:

  • 一个纯粹的函数: 输入(参数)和输出(返回一个包含 refcomputed、方法的对象)都极其明确。
  • 自管理: 它应该自己处理好内部的生命周期和副作用(如 onMounted 中添加事件监听,onUnmounted 中自动清理)。
  • 可测试: 脱离了组件的上下文,一个 Composable 可以像普通工具函数一样被轻松地进行单元测试。

1.8.5. 状态管理的策略:局部与全局的权衡

并非所有的状态都需要被放入全局的 Pinia Store 中。滥用全局状态会导致应用变得难以理解和调试。

  • 组件内部状态: 默认情况下,状态应首先归属于最直接需要它的组件。使用 refreactive 足以应对大多数场景。
  • 跨层级状态 (provide/inject): 当一个状态需要在一个组件子树中共享(例如,一个复杂表单的根组件向其所有子孙组件提供校验状态),provide/inject 是比“属性透传”更优雅的解决方案。
  • 全局应用级状态 (Pinia): 只有当一个状态需要被应用中多个 无直接父子关系 的组件共享时(例如,用户信息、主题设置、购物车),才应该将其提升到 Pinia Store 中。

1.8.6. 性能优化的意识:贯穿开发始终

性能不是项目上线前的“灵丹妙药”,而是一种需要贯穿于整个开发过程的 意识和习惯

  • 响应式数据的精细化: 理解 ref vs reactive, shallowRef vs ref 的区别,为不同的数据结构选择最高效的响应式方案。
  • 渲染的智慧: 善用 v-memov-once 来缓存那些不常变化的模板片段。在 v-for 循环中,永远提供一个稳定且唯一的 key
  • 按需加载: 路由组件、大型第三方库、非首屏的重量级组件,都应使用 动态导入 (import()) 来实现懒加载,这是优化应用初始加载速度最有效的手段。

1.8.7. 质量与安全的底线:自动化与最佳实践

  • 静态检查先行: 我们在 1.61.7 节建立的 ESLint + Prettier + Husky 自动化流程,是我们代码质量的第一道,也是最重要的一道防线。
  • TypeScript 全覆盖: 我们将坚持为所有变量、函数参数、返回值、Props、Emits 提供明确的类型。TypeScript 不是负担,而是我们对抗复杂性、提升代码健壮性的最强盟友。
  • 测试驱动: 我们会为核心的 Composable 和 Store 编写单元测试 (Vitest),并为关键的用户流程编写端到端测试 (Cypress)。测试是信心的来源。
  • 安全意识: 时刻警惕 XSS 攻击(永远不要使用 v-html 渲染未经处理的用户内容)、CSRF 等常见 Web 漏洞。

这份蓝图,是我们从“代码实现者”迈向“系统构建者”的思维转变。它所倡导的原则——功能内聚、职责单一、契约清晰、按需加载、质量先行——将是我们接下来每一章实践的指导思想。

有了这块坚如磐-石的地基和这份清晰的蓝图,我们终于可以满怀信心地,开始铸造 Vue 应用的核心——响应式系统。