本文档从宏观角度详细讲解 Vite 项目的构建流程,帮助你理解从执行构建命令到生成最终产物的完整过程。
目录#
- 1. 构建流程概览
- 2. 命令执行与配置加载
- 3. 依赖预构建(开发模式)
- 4. 模块解析与加载
- 5. 代码转换与编译
- 6. 代码分割策略
- 7. 资源处理
- 8. 代码优化与压缩
- 9. 产物生成与输出
- 10. 本项目构建实例
1. 构建流程概览#
1.1 整体流程图#
npm run build
↓
┌──────────────────────────────────────┐
│ 阶段 1: 配置加载与解析 │
│ - 读取 vite.config.js │
│ - 加载环境变量 │
│ - 合并默认配置 │
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ 阶段 2: 模块图构建 │
│ - 从入口文件开始 │
│ - 解析所有模块依赖 │
│ - 构建完整的依赖图谱 │
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ 阶段 3: 代码转换 │
│ - Vue 文件编译 │
│ - TypeScript 转换 │
│ - JSX/TSX 转换 │
│ - CSS 预处理 │
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ 阶段 4: 代码分割 │
│ - 分析模块依赖关系 │
│ - 按配置分割代码 │
│ - 生成多个 chunk │
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ 阶段 5: 代码优化 │
│ - Tree Shaking │
│ - 代码压缩 │
│ - 移除无用代码 │
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ 阶段 6: 资源处理 │
│ - 图片优化 │
│ - 字体处理 │
│ - 静态资源复制 │
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ 阶段 7: 产物生成 │
│ - 生成 JS/CSS 文件 │
│ - 生成 HTML 文件 │
│ - 生成 Sourcemap │
│ - 生成压缩文件 │
└──────────────────────────────────────┘
↓
构建完成1.2 核心概念#
构建模式
- 开发模式(dev):快速启动,按需编译,HMR 热更新
- 生产模式(build):完整构建,代码优化,生成产物
构建工具
- 开发阶段:使用 esbuild 进行依赖预构建(速度快)
- 生产构建:使用 Rollup 进行打包(产物优化好)
为什么使用两种工具?
- esbuild:Go 语言编写,速度极快,适合开发环境
- Rollup:成熟的打包工具,Tree Shaking 效果好,插件生态完善
2. 命令执行与配置加载#
2.1 构建命令执行#
当你执行 npm run build 时,实际执行的是:
vite build这个命令会触发 Vite 的构建流程,主要步骤如下:
解析命令行参数
- 读取
--mode、--base、--outDir等参数 - 确定构建模式(默认为 production)
- 读取
查找配置文件
- 按顺序查找:
vite.config.js、vite.config.mjs、vite.config.ts - 支持多种配置文件格式
- 按顺序查找:
加载配置文件
- 执行配置文件导出的函数
- 传入
command: 'build'和mode: 'production'
2.2 环境变量加载#
Vite 会自动加载项目根目录下的环境变量文件:
.env # 所有模式下都会加载
.env.local # 所有模式下都会加载,但会被 git 忽略
.env.production # 仅在生产模式下加载
.env.production.local # 仅在生产模式下加载,但会被 git 忽略加载优先级(从高到低):
.env.production.local.env.production.env.local.env
环境变量规则:
- 只有以
VITE_开头的变量才会暴露给客户端代码 - 其他变量仅在构建配置中可用
2.3 配置合并#
Vite 会按以下顺序合并配置:
1. Vite 默认配置
↓
2. 用户配置文件 (vite.config.js)
↓
3. 插件注入的配置
↓
4. 命令行参数本项目配置示例:
// vite.config.mjs
export default defineConfig(({ command, mode }) => {
// 加载环境变量
const env = loadEnv(mode, process.cwd());
// 读取版本号
const version = readVersion();
return {
// 基础配置
base: "/",
// 插件配置
plugins: createVitePlugins(env, command === "build"),
// 路径别名
resolve: {
alias: {
"@": "/src",
components: "/src/components",
// ...
},
},
// 构建配置
build: {
outDir: "dist",
sourcemap: "hidden",
// ...
},
};
});3. 依赖预构建(开发模式)#
注意:依赖预构建主要用于开发模式(
vite dev),而非生产构建。生产构建时,Rollup 会直接处理所有依赖,不依赖预构建的缓存。本章节帮助你理解 Vite 开发服务器的工作原理。
3.1 为什么需要依赖预构建?#
问题背景:
- 许多 npm 包使用 CommonJS 或 UMD 格式
- 有些包有数百个内部模块(如 lodash-es)
- 浏览器原生 ESM 不支持裸模块导入(如
import { debounce } from 'lodash-es')
预构建的作用(开发模式下):
- 格式转换:将 CommonJS/UMD 转换为 ESM
- 性能优化:将多个模块合并为单个模块,减少 HTTP 请求
- 路径解析:将裸模块导入转换为浏览器可识别的路径
3.2 预构建流程#
1. 扫描依赖
- 从入口文件开始扫描
- 识别所有 import 语句
- 收集需要预构建的依赖
2. 使用 esbuild 构建
- 将依赖打包为单个文件
- 转换为 ESM 格式
- 生成到 node_modules/.vite 目录
3. 缓存依赖
- 基于 package.json 和配置生成哈希
- 依赖未变化时直接使用缓存
- 大幅提升二次构建速度3.3 预构建配置#
// vite.config.js
export default {
optimizeDeps: {
// 强制预构建的依赖
include: ["vue", "vue-router"],
// 排除预构建的依赖
exclude: ["your-local-package"],
// esbuild 选项
esbuildOptions: {
target: "esnext",
},
},
};4. 模块解析与加载#
4.1 入口文件解析#
构建从 index.html 开始:
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<div id="app"></div>
<!-- Vite 从这里开始解析 -->
<script type="module" src="/src/main.js"></script>
</body>
</html>解析过程:
- 读取
index.html文件 - 找到
<script type="module">标签 - 将
/src/main.js作为入口模块 - 开始构建模块依赖图
4.2 模块依赖图构建#
Vite 会递归解析所有模块,构建完整的依赖关系图:
main.js
├─ vue (node_modules)
├─ App.vue
│ ├─ vue
│ ├─ HelloWorld.vue
│ └─ style.css
├─ router/index.js
│ ├─ vue-router (node_modules)
│ └─ views/*.vue
└─ store/index.js
├─ vuex (node_modules)
└─ modules/*.js构建步骤:
解析导入语句
- 分析每个模块的
import语句 - 识别导入的模块路径
- 分析每个模块的
路径解析
- 相对路径:
./App.vue→ 解析为绝对路径 - 别名路径:
@/components/Foo.vue→ 根据配置解析 - 裸模块:
vue→ 解析到 node_modules
- 相对路径:
递归加载
- 加载每个依赖模块
- 继续解析该模块的依赖
- 直到所有模块都被处理
4.3 路径别名解析#
配置示例:
resolve: {
alias: {
'@': '/src',
'components': '/src/components',
'styles': '/src/styles',
'utils': '/src/utils'
}
}解析示例:
@/App.vue→/src/App.vuecomponents/Button.vue→/src/components/Button.vueutils/format.js→/src/utils/format.js
5. 代码转换与编译#
5.1 Vue 文件编译#
Vue 单文件组件(SFC)需要被编译为 JavaScript:
原始 Vue 文件:
<template>
<div class="hello">{{ msg }}</div>
</template>
<style scoped>
.hello {
color: red;
}
</style>编译过程:
- 模板编译:将
<template>编译为渲染函数 - 脚本处理:处理 `
