前端工程化
前端工程化手册:讲解 Vite 构建配置、多环境变量、代码规范(ESLint/Prettier/Git Hooks)、代码分割与按需加载、打包体积优化与目录结构,帮助把能跑的页面变成可维护的项目。
一、工程化解决什么问题
手写 HTML + JS 能做出页面,但项目一旦超过几个文件,就会遇到:
| 问题 | 工程化的解法 |
|---|---|
依赖靠 <script> 堆叠,顺序容易错 |
模块化 + 包管理器 |
| 改了代码要手动刷新、手动压缩 | 开发服务器 + 构建工具 |
| 语法太新,浏览器不认 | 转译(ESBuild / Babel) |
| 图片、字体、CSS 引用路径混乱 | 资源处理管线 |
| 多环境(开发/测试/生产)配置混在一起 | 环境变量注入 |
| 代码风格各异,Review 全在吵架 | Linter + Formatter + 提交钩子 |
| 首页加载几 MB,用户等不及 | 代码分割 + 体积分析 |
一句话:工程化是把「重复的、易错的、可自动化的」事情交给工具。
二、构建工具
2.1 Webpack 与 Vite 的核心差异
| 维度 | Webpack | Vite |
|---|---|---|
| 开发启动 | 先打包全部,再起服务(项目大时慢) | 不打包,浏览器按需请求,秒级启动 |
| 开发热更新 | 重新构建受影响 chunk | 基于 ESM,毫秒级 HMR |
| 生产构建 | 成熟稳定,生态庞大 | 默认 Rollup,产物质量好 |
| 配置复杂度 | 高(loader/plugin 概念多) | 低(约定优于配置) |
| 适用 | 老项目、需要复杂定制 | 新项目首选 |
Vite 快的原理:开发时利用浏览器原生 ESM,只编译当前请求的模块;依赖用 esbuild 预构建(Go 编写,比 JS 实现快一个数量级)。
2.2 Vite 最小可用配置
// vite.config.jsimport { defineConfig } from 'vite'import vue from '@vitejs/plugin-vue'import path from 'path'export default defineConfig({ plugins: [vue()], resolve: { alias: { '@': path.resolve(__dirname, 'src'), // 路径别名,避免 ../../../.. }, }, server: { port: 5173, open: true, proxy: { // 开发期跨域:转发到后端 '/api': { target: 'http://127.0.0.1:8080', changeOrigin: true, // rewrite: (p) => p.replace(/^\/api/, ''), // 后端无 /api 前缀时用 }, }, }, build: { outDir: 'dist', sourcemap: false, // 生产关闭;需排查时可设为 'hidden' chunkSizeWarningLimit: 800, // KB,超过则提示 rollupOptions: { output: { // 手动分包:第三方库单独成 chunk,利于长期缓存 manualChunks: { vue: ['vue', 'vue-router', 'pinia'], ui: ['element-plus'], }, }, }, },})2.3 关键配置项说明
| 配置 | 作用 | 常见坑 |
|---|---|---|
resolve.alias |
路径别名 | 需同步配置 tsconfig.json 的 paths,否则 TS 报错 |
server.proxy |
开发期代理 | changeOrigin: true 必须开,否则后端收到错误 Host |
base |
部署子路径 | 部署到 /admin/ 时必须设 base: '/admin/',否则资源 404 |
build.sourcemap |
源码映射 | 生产开 true 会暴露源码结构,建议 false 或 'hidden' |
build.assetsInlineLimit |
小文件转 base64 | 默认 4KB,过大反而增加 HTML 体积 |
三、多环境与变量管理
3.1 环境文件约定
.env # 所有环境共享.env.development # npm run dev 时加载.env.staging # 预发环境.env.production # npm run build 时加载.env.local # 本地覆盖,加入 .gitignore# .env.productionVITE_API_BASE=https://api.example.comVITE_APP_TITLE=管理后台VITE_UPLOAD_LIMIT=103.2 变量使用规则
| 规则 | 说明 |
|---|---|
必须以 VITE_ 前缀开头 |
否则不会注入客户端 |
| 只放非敏感信息 | 所有 VITE_* 都会被打包进产物,任何人都能在 JS 里看到 |
通过 import.meta.env 访问 |
不是 process.env |
| 类型声明 | 在 env.d.ts 中声明,获得补全 |
// env.d.tsinterface ImportMetaEnv { readonly VITE_API_BASE: string readonly VITE_APP_TITLE: string}const base = import.meta.env.VITE_API_BASEconst isProd = import.meta.env.PROD⚠️ 最常见的严重错误:把密钥、Token、第三方 Secret 写进
VITE_*。这些值会以明文出现在打包产物中,等于公开。
3.3 构建时注入 vs 运行时配置
如果同一份产物要部署到多个环境(如私有化部署),构建时注入就不够用了,需要运行时配置:
<!-- public/config.js:部署后可直接修改,无需重新构建 --><script> window.__APP_CONFIG__ = { apiBase: '/api', title: '管理后台', }</script>// 使用时const base = window.__APP_CONFIG__?.apiBase || import.meta.env.VITE_API_BASE四、代码规范与质量
4.1 三层防线
编辑器实时提示(ESLint 插件) ↓Git 提交时校验(lint-staged + husky) ↓CI 兜底(构建前跑 lint + type check)4.2 ESLint(找问题)
// eslint.config.js(扁平配置,ESLint 9+)import js from '@eslint/js'import vue from 'eslint-plugin-vue'export default [ js.configs.recommended, ...vue.configs['flat/recommended'], { rules: { 'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], 'no-console': ['warn', { allow: ['warn', 'error'] }], 'vue/multi-word-component-names': 'off', }, },]4.3 Prettier(管风格)
{ "semi": false, "singleQuote": true, "printWidth": 100, "trailingComma": "es5", "arrowParens": "always"}分工原则:ESLint 管「代码是否有问题」(如未使用变量),Prettier 管「格式是否好看」(如分号、缩进)。两者职责不重叠,不要用 ESLint 做格式化。
4.4 Git Hooks(提交前自动校验)
npm i -D husky lint-stagednpx husky init// package.json{ "lint-staged": { "*.{js,ts,vue}": ["eslint --fix", "prettier --write"], "*.{css,scss,md}": ["prettier --write"] }}# .husky/pre-commitnpx lint-staged收益:只检查本次改动的文件,速度快;不合规的代码进不了仓库。
4.5 TypeScript 类型检查
// package.json{ "scripts": { "typecheck": "vue-tsc --noEmit", "lint": "eslint . --max-warnings 0", "build": "npm run typecheck && vite build" }}把
typecheck挂在build前面:类型错误不通过就不产出产物,比上线后才发现便宜得多。
五、代码分割与按需加载
5.1 路由懒加载(最有效)
// ❌ 全部同步引入:首屏加载所有页面代码import Home from './views/Home.vue'import UserList from './views/UserList.vue'// ✅ 路由级懒加载:访问时才加载const routes = [ { path: '/', component: () => import('./views/Home.vue') }, { path: '/users', component: () => import('./views/UserList.vue') },]5.2 组件级懒加载(重组件)
// 图表、富文本编辑器等大依赖,用 defineAsyncComponentimport { defineAsyncComponent } from 'vue'const ChartPanel = defineAsyncComponent(() => import('./ChartPanel.vue'))5.3 第三方库按需引入
// ❌ 全量引入 UI 库(几百 KB)import ElementPlus from 'element-plus'app.use(ElementPlus)// ✅ 按需自动引入// vite.config.jsimport AutoImport from 'unplugin-auto-import/vite'import Components from 'unplugin-vue-components/vite'import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'export default defineConfig({ plugins: [ AutoImport({ resolvers: [ElementPlusResolver()] }), Components({ resolvers: [ElementPlusResolver()] }), ],})5.4 分包策略
| 策略 | 好处 |
|---|---|
| 业务代码按路由分 chunk | 改动一个页面只失效一个文件 |
| 第三方库单独成 chunk | 库不变则长期命中缓存 |
| 公共组件抽 common chunk | 避免重复打包 |
六、打包体积优化
6.1 先量化,再优化
npm i -D rollup-plugin-visualizer// vite.config.jsimport { visualizer } from 'rollup-plugin-visualizer'export default defineConfig({ plugins: [visualizer({ open: true, gzipSize: true })],})构建后自动打开体积分析图,先找出占比最大的模块,不要凭感觉优化。
6.2 常见优化手段
| 手段 | 典型收益 |
|---|---|
| 路由懒加载 | 首屏体积降 40%–70% |
| UI 库按需引入 | 降 200–500 KB |
| 用轻量替代库(dayjs 替 moment) | 降约 60 KB |
| 图片转 WebP + 懒加载 | 视图片占比可达数 MB |
| 移除生产环境的 console / debugger | 数 KB,且更安全 |
| 开启 gzip / brotli(Nginx 层) | 传输体积降 60%–80% |
// 移除生产环境 consoleexport default defineConfig({ esbuild: { drop: ['console', 'debugger'], // 生产构建时生效 },})# Nginx 开启 gzipgzip on;gzip_min_length 1k;gzip_comp_level 5;gzip_types text/css application/javascript application/json image/svg+xml;6.3 体积参考线
| 指标 | 参考值 |
|---|---|
| 首屏 JS(gzip 后) | < 200 KB |
| 单个 chunk(gzip 后) | < 300 KB |
| 首屏 LCP | < 2.5s |
七、依赖管理
7.1 npm / pnpm / yarn 选择
| 工具 | 特点 | 适用 |
|---|---|---|
| npm | 内置,兼容性最好 | 默认选择 |
| pnpm | 硬链接共享,磁盘占用小、安装快 | 多项目开发推荐 |
| yarn | 早期优势已不明显 | 存量项目 |
7.2 依赖分类
| 类型 | 命令 | 说明 |
|---|---|---|
| 运行时依赖 | npm i pkg |
会打进产物 |
| 开发依赖 | npm i -D pkg |
只在构建时用 |
误把构建工具装成运行时依赖会导致产物体积异常,也影响生产环境安装。
7.3 锁定版本
# 必须提交 lock 文件package-lock.json / pnpm-lock.yaml / yarn.lockCI 中用 npm ci 而不是 npm install:ci 严格按 lock 文件安装,保证各环境一致。
7.4 依赖健康检查
npm outdated # 查看可升级的包npm audit # 安全漏洞npm ls <pkg> # 查某个包为什么被安装(排查体积来源)八、目录结构规范
src/├── api/ # 接口层:按模块拆分,与后端契约一一对应│ ├── request.js # 请求封装(拦截器、统一错误处理)│ └── user.js├── assets/ # 需要构建处理的静态资源(图片、字体)├── components/ # 全局复用组件│ └── BaseButton/├── composables/ # 组合式函数(可复用逻辑)│ └── usePagination.js├── layouts/ # 布局容器├── router/ # 路由定义├── stores/ # 状态管理├── styles/ # 全局样式、变量├── utils/ # 纯函数工具├── views/ # 页面级组件└── main.js分层原则:
| 层 | 职责 | 禁止 |
|---|---|---|
views |
页面编排 | 直接写 axios 请求(应走 api 层) |
api |
请求定义 | 写业务逻辑 |
components |
通用 UI | 依赖具体页面数据 |
composables |
可复用逻辑 | 直接操作 DOM |
utils |
纯函数 | 有副作用的操作 |
九、构建产物与部署配合
9.1 静态资源部署到子路径
// vite.config.jsexport default defineConfig({ base: '/admin/', // 部署在 example.com/admin/ 时必设})9.2 history 路由的 Nginx 配置
location / { root /path/to/dist; try_files $uri $uri/ /index.html; # 关键:找不到文件时回落 index.html}漏了这行 → 刷新子路由页面直接 404。
9.3 缓存策略
# 带 hash 的静态资源:长期缓存location ~* \.(js|css|png|jpg|woff2)$ { expires 1y; add_header Cache-Control "public, immutable";}# index.html:不缓存,保证能拿到新版本location = /index.html { add_header Cache-Control "no-cache, no-store, must-revalidate";}原理:文件名带 hash,内容变则文件名变 → 可以永久缓存;index.html 不缓存 → 每次拿到最新引用。
十、常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 部署后白屏 | base 配置与实际路径不符 |
检查 base 与 Nginx 的 root/location |
| 刷新子页面 404 | 缺 try_files 回落 |
补 Nginx 配置 |
| 改了代码线上没变 | index.html 被缓存 |
给 index.html 设 no-cache |
| 开发正常、构建后报错 | 环境变量未配置 / 大小写敏感 | 检查 .env.production 与文件名大小写 |
| 首屏加载慢 | 未做懒加载 / 体积过大 | 体积分析 → 懒加载 → 分包 |
| TS 路径别名不生效 | 只配了 Vite 未配 tsconfig | 两处都要配 paths |
| 安装依赖后本地能跑 CI 挂 | 用 install 而非 ci;lock 未提交 |
CI 改 npm ci,提交 lock 文件 |
十一、检查清单
构建配置
-
base与实际部署路径一致 - 开发代理
changeOrigin: true - 路径别名在 Vite 与 tsconfig 中同时配置
- 生产关闭 sourcemap(或用
hidden)
环境变量
- 变量以
VITE_开头 - 无任何密钥/Secret 写入
VITE_* -
.env.local已加入.gitignore - 提供
.env.example模板
代码质量
- ESLint + Prettier 配置完成且职责不重叠
- pre-commit 执行 lint-staged
- CI 中执行 lint 与 typecheck
-
build脚本内联 typecheck
性能
- 路由全部懒加载
- 第三方 UI 库按需引入
- 大依赖(图表/编辑器)异步加载
- 生产移除 console / debugger
- Nginx 开启 gzip/brotli
- 静态资源长缓存 + index.html 不缓存
依赖
- lock 文件已提交
- CI 使用
npm ci - 定期
npm audit与npm outdated
十二、总结
前端工程化的本质是用工具消除重复劳动与人为失误,重点只有四件事:
- 构建:Vite 负责开发体验(快)与产物质量(分包、压缩),配置重点在
base、alias、proxy、manualChunks。 - 环境:多环境靠
.env文件,但运行时配置才是同一产物多环境部署的正解;VITE_*里绝不能放密钥。 - 质量:ESLint 管问题、Prettier 管格式、hooks 卡提交、CI 做兜底——四层缺一层都会退化。
- 性能:先做体积分析再优化,懒加载与按需引入通常能解决 80% 的问题。
最后提醒一句:工程化配置写完就放到脚手架里。每个新项目重新配一遍,才是最大的浪费。
完