全栈手账 订阅 RSS

前端工程化

前端工程化手册:讲解 Vite 构建配置、多环境变量、代码规范(ESLint/Prettier/Git Hooks)、代码分割与按需加载、打包体积优化与目录结构,帮助把能跑的页面变成可维护的项目。

读了 20 分钟 更新于 11 月 3 日

一、工程化解决什么问题

手写 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 最小可用配置

javascript
// 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
ini
# .env.productionVITE_API_BASE=https://api.example.comVITE_APP_TITLE=管理后台VITE_UPLOAD_LIMIT=10

3.2 变量使用规则

规则 说明
必须以 VITE_ 前缀开头 否则不会注入客户端
只放非敏感信息 所有 VITE_* 都会被打包进产物,任何人都能在 JS 里看到
通过 import.meta.env 访问 不是 process.env
类型声明 在 env.d.ts 中声明,获得补全
typescript
// env.d.tsinterface ImportMetaEnv {  readonly VITE_API_BASE: string  readonly VITE_APP_TITLE: string}
javascript
const base = import.meta.env.VITE_API_BASEconst isProd = import.meta.env.PROD

⚠️ 最常见的严重错误:把密钥、Token、第三方 Secret 写进 VITE_*。这些值会以明文出现在打包产物中,等于公开。

3.3 构建时注入 vs 运行时配置

如果同一份产物要部署到多个环境(如私有化部署),构建时注入就不够用了,需要运行时配置:

html
<!-- public/config.js:部署后可直接修改,无需重新构建 --><script>  window.__APP_CONFIG__ = {    apiBase: '/api',    title: '管理后台',  }</script>
javascript
// 使用时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(找问题)

javascript
// 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(管风格)

json
{  "semi": false,  "singleQuote": true,  "printWidth": 100,  "trailingComma": "es5",  "arrowParens": "always"}

分工原则:ESLint 管「代码是否有问题」(如未使用变量),Prettier 管「格式是否好看」(如分号、缩进)。两者职责不重叠,不要用 ESLint 做格式化。

4.4 Git Hooks(提交前自动校验)

bash
npm i -D husky lint-stagednpx husky init
json
// package.json{  "lint-staged": {    "*.{js,ts,vue}": ["eslint --fix", "prettier --write"],    "*.{css,scss,md}": ["prettier --write"]  }}
bash
# .husky/pre-commitnpx lint-staged

收益:只检查本次改动的文件,速度快;不合规的代码进不了仓库。

4.5 TypeScript 类型检查

json
// package.json{  "scripts": {    "typecheck": "vue-tsc --noEmit",    "lint": "eslint . --max-warnings 0",    "build": "npm run typecheck && vite build"  }}

把 typecheck 挂在 build 前面:类型错误不通过就不产出产物,比上线后才发现便宜得多。


五、代码分割与按需加载

5.1 路由懒加载(最有效)

javascript
// ❌ 全部同步引入:首屏加载所有页面代码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 组件级懒加载(重组件)

javascript
// 图表、富文本编辑器等大依赖,用 defineAsyncComponentimport { defineAsyncComponent } from 'vue'const ChartPanel = defineAsyncComponent(() => import('./ChartPanel.vue'))

5.3 第三方库按需引入

javascript
// ❌ 全量引入 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 先量化,再优化

bash
npm i -D rollup-plugin-visualizer
javascript
// 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%
javascript
// 移除生产环境 consoleexport default defineConfig({  esbuild: {    drop: ['console', 'debugger'],   // 生产构建时生效  },})
nginx
# 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 锁定版本

bash
# 必须提交 lock 文件package-lock.json / pnpm-lock.yaml / yarn.lock

CI 中用 npm ci 而不是 npm install:ci 严格按 lock 文件安装,保证各环境一致。

7.4 依赖健康检查

bash
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 静态资源部署到子路径

javascript
// vite.config.jsexport default defineConfig({  base: '/admin/',   // 部署在 example.com/admin/ 时必设})

9.2 history 路由的 Nginx 配置

nginx
location / {    root /path/to/dist;    try_files $uri $uri/ /index.html;   # 关键:找不到文件时回落 index.html}

漏了这行 → 刷新子路由页面直接 404。

9.3 缓存策略

nginx
# 带 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

十二、总结

前端工程化的本质是用工具消除重复劳动与人为失误,重点只有四件事:

  1. 构建:Vite 负责开发体验(快)与产物质量(分包、压缩),配置重点在 base、alias、proxy、manualChunks。
  2. 环境:多环境靠 .env 文件,但运行时配置才是同一产物多环境部署的正解;VITE_* 里绝不能放密钥。
  3. 质量:ESLint 管问题、Prettier 管格式、hooks 卡提交、CI 做兜底——四层缺一层都会退化。
  4. 性能:先做体积分析再优化,懒加载与按需引入通常能解决 80% 的问题。

最后提醒一句:工程化配置写完就放到脚手架里。每个新项目重新配一遍,才是最大的浪费。


完

前端、后端、部署、客服都是我一个人。写这里的东西都是真踩过的坑,不是抄来的教程。