开工前说点实在的做前端这几年Webpack 时代我忍了太久——配置复杂、冷启动慢、热更新卡顿每次新项目光搭环境就要折腾大半天。后来切到 Vite那种秒开的开发体验确实让人回不去但真正把 Vite 用明白、用出工程化水平并不是开箱即用那么简单。很多朋友项目能跑起来可一碰到多环境部署、团队协作规范、构建体积失控这些问题就开始手忙脚乱。这篇指南想聊的就是这三件事环境变量怎么配才能既灵活又安全代码规范怎么落地才能不靠人肉 review 也能守住底线性能优化怎么做才能让开发体验和线上产物都拿得出手。适合刚接触 Vite 的初级开发者也适合已经在用但想系统梳理工程化配置的进阶同学。我会把自己实际项目里踩过的坑、验证过的方案、最终沉淀下来的配置直接拿出来你可以照着抄也可以在这个基础上按自己的场景改。1. 环境变量配置别再把环境判断写在业务代码里1.1 先理解 Vite 环境变量的工作逻辑Vite 的环境变量机制底层是依赖 Node 的process.env加上自身的 mode 概念。默认情况下项目根目录下的.env、.env.development、.env.production这些文件会被 Vite 加载文件里以VITE_开头的变量会通过import.meta.env暴露给客户端代码使用。这个机制的关键点在于不是所有.env里的变量都会进客户端。换句话说VITE_前缀就像一道过滤网只有带这个前缀的变量才会被打进业务代码里。为什么要这么设计因为有些变量是给构建工具本身用的——比如NODE_ENV、BASE_URL或者某些只在构建期需要的配置。如果统统暴露到客户端等于把内部信息直接挂在页面上安全性没保障代码体积也会变臃肿。实际开发中我见过有同学把所有配置都塞进.env然后在代码里直接import.meta.env.SOME_KEY。项目小的时候没问题等公司规模上来、多环境越来越多这种方式会迅速失控变量命名混乱、环境文件互相覆盖、上线后改了配置还要重新构建。正确的思路是分三层管理第一层放所有环境公共的默认值.env第二层放不同环境各自的差异化配置.env.development、.env.production等第三层放本地私有配置.env.local必须加进.gitignore。1.2 多环境配置的完整实操先说我目前比较推荐的一套组织方式。项目根目录下这些文件按需创建.env # 所有环境共享的默认值 .env.development # 开发环境 .env.production # 生产环境 .env.staging # 预发布环境如果有的话 .env.test # 测试环境 .env.local # 本地私有配置不进版本库每个文件的格式要严格遵守 KEYVALUE 的写法左侧是键名右侧是值不要加引号Vite 默认把值当作字符串处理如果你想当数字或布尔值用需要自己在代码里转换注释用#开头。一个典型的.env.production长这样# 生产环境标识 VITE_APP_ENVproduction # 接口网关地址 VITE_API_BASE_URLhttps://api.example.com # 站点基础路径配合 base 配置使用 VITE_BASE_PATH/ # 埋点开关 VITE_ENABLE_TRACKINGtrue这里有个容易踩的坑.env和.env.production同时存在时两个文件都会被加载但相同键名的变量以更具体的文件即带环境后缀的文件为准。这有点像 CSS 里类的覆盖关系——公共文件提供默认值环境文件只写差异项。比如.env里定义VITE_API_BASE_URL/api.env.production里覆盖为https://api.example.com开发环境就不需要额外配置。package.json 里的 scripts 也要配合上{ scripts: { dev: vite, build: vite build, build:staging: vite build --mode staging, build:test: vite build --mode test, preview: vite preview } }注意vite build默认走的是 production 模式即使你把它改成别的模式构建行为的默认值还是偏向生产环境。比如npm run build -- --mode staging会加载.env.staging但 build 默认行为和NODE_ENVproduction的逻辑不会变除非你在 vite.config.ts 里再手动调整。1.3 在代码里优雅地使用环境变量业务代码里通过import.meta.env访问这些变量。我最常做的几个封装// src/utils/env.ts export const APP_ENV import.meta.env.VITE_APP_ENV ?? development export const isProd APP_ENV production export const isStaging APP_ENV staging export const isDev import.meta.env.DEV export const API_BASE_URL import.meta.env.VITE_API_BASE_URL ?? /api这样一来业务代码里不要再直接散落一地的import.meta.env.XXX全部通过工具函数走一遍。好处有几个一是变量来自哪里一目了然二是万一哪天后端换域名只需要改环境文件不用动代码三是配合 TypeScript 的类型声明能获得完整的类型提示和拼写检查。TypeScript 的项目记得在src/vite-env.d.ts里显式声明环境变量类型/// reference typesvite/client / interface ImportMetaEnv { readonly VITE_APP_ENV: string readonly VITE_API_BASE_URL: string readonly VITE_BASE_PATH: string readonly VITE_ENABLE_TRACKING: string } interface ImportMeta { readonly env: ImportMetaEnv }1.4 服务端配置与构建期注入还有一个场景很多人容易忽略vite.config.ts本身也是 Node 环境它读取环境变量的方式和客户端不同不能直接用import.meta.env要使用 Vite 提供的loadEnv函数。比如我想根据环境动态修改base配置import { defineConfig, loadEnv } from vite export default defineConfig(({ mode }) { const env loadEnv(mode, process.cwd(), ) return { base: env.VITE_BASE_PATH || /, // 其他配置... } })这里loadEnv的第三个参数传了表示把所有环境变量都加载进来不做前缀过滤默认只加载VITE_开头的。这种写法在需要把后端服务某些参数比如 CDN 路径、部署域名传给构建配置时特别有用。注意defineConfig传函数的形式才能拿到mode如果你写的是defineConfig({...})对象形式是拿不到当前模式的。这是刚上手时最容易犯的低级错误报错还没法直观看到原因排查起来很费劲。2. 代码规范从靠自觉到靠机制2.1 规范工具链的选型思路代码规范这件事很多团队停留在装了 ESLint 就算规范的阶段实际上远不止于此。我理解的完整规范体系至少包含四层代码风格格式化、代码质量Lint 规则、提交规范Commit Message、提交前检查Git Hooks。四者缺一不可分别对应写代码时、写完代码后、提交前、提交时四个环节。Vite 官方脚手架create-vite现在默认集成了 ESLint但如果你拿 Vue/React 那种完整模板会发现规范配置还是偏基础。要在团队里真正落地我建议在 Vite 项目里组装一套组合拳ESLint做代码质量检查 Prettier做代码格式化 Husky做 Git 钩子 lint-staged只检查暂存文件 commitlint校验提交信息。工具选型上为什么要这样组合ESLint 和 Prettier 经常被人拿来对比实际上它们定位完全不同。ESLint 的核心能力是发现问题比如 no-unused-vars未使用变量、no-console是否允许 console 输出、eqeqeq强制全等比较这些属于代码质量问题Prettier 的职责只有一个——把代码格式统一比如缩进用几个空格、单行长度多少、字符串用单引号还是双引号。两者互补而不是替代关系。如果只装 Prettier变量明明没用也不会被提示如果只装 ESLint团队的格式差异还是会让人疯狂。2.2 基于 ESLint 扁平化配置的实操新版 ESLintv9 及以上全面推了扁平化配置flat config旧的.eslintrc形式陆续被淘汰。Vite 项目加上 typescript-eslint 之后配置方式跟以前差异不小很多人直接从老项目复制配置过来发现完全不生效多半就是栽在这里。我建议直接用 ESLint 官方推荐的配置组织方式新建eslint.config.js注意 Vite 的 Node 环境识别.js没问题如果你项目启用了 type: module那后缀保持一致即可import js from eslint/js import globals from globals import tseslint from typescript-eslint import vue from eslint-plugin-vue // 如果项目用 Vue import reactHooks from eslint-plugin-react-hooks // 如果项目用 React export default [ { ignores: [dist, node_modules, coverage] }, js.configs.recommended, ...tseslint.configs.recommended, ...vue.configs[flat/recommended], // 按项目框架选择 { files: [**/*.{ts,vue}], languageOptions: { ecmaVersion: latest, globals: globals.browser }, rules: { no-console: process.env.NODE_ENV production ? warn : off, typescript-eslint/no-unused-vars: [error, { argsIgnorePattern: ^_ }], vue/multi-word-component-names: off } } ]这套配置里ignores必须放在数组最前面靠前的位置它表示哪些目录直接跳过检查。dist和node_modules是基础项你要是忘了加检查速度会慢到你怀疑人生。我习惯在自定义 rules 里带上几组规则适配 Vite 前后端分离的实际开发场景no-console生产环境告警但不直接报错因为有时线上排查问题需要临时打点typescript-eslint/no-unused-vars警告未使用变量参数名以_开头的忽略prefer-const让该用 const 的地方别用 let减少可变状态typescript-eslint/no-explicit-any给出告警而不是直接禁用any毕竟现实业务里有时候确实需要any兜底但用多了就该警觉这些规则可以按团队口味调整不建议一上来就把规则开到最严否则同事们的抵触情绪会很大规范落地方案容易死在推行阶段。2.3 Prettier 与编辑器联动Prettier 的配置文件.prettierrc.json很轻量我给大家一套经过多个项目验证、相对折中的配置{ semi: false, singleQuote: true, printWidth: 100, tabWidth: 2, trailingComma: none, arrowParens: always, endOfLine: lf }这里我特意把semi关掉不写分号single单引号。不是这俩配置一定最好而是团队里必须有人拍板定死一套。格式之争没有对错只有统一与否。printWidth设 100 是因为现在宽屏显示器比较普遍80 太窄容易频繁折行但 120 以上又会让代码横向太长影响阅读100 是个比较舒服的中间值。Prettier 要和编辑器联动才能真正发挥价值。VS Code 里做两件事第一安装 Prettier 扩展然后在设置里把默认格式化器设为 Prettier第二打开保存自动格式化Format On Save。很多同学改了.prettierrc.json但不生效多半是编辑器里的默认格式化器没有切过来或项目里装了多个格式化插件比如 Beautify、JS-CSS-HTML Formatter存在冲突。判断标准很简单把一个文件里的引号改成双引号保存后如果变回单引号就是 Prettier 接管成功如果纹丝不动说明格式化流程根本没跑起来。2.4 Husky lint-staged commitlint 搭建提交防线代码规范最后一道闸门在 Git 提交这个环节。我的配置链路是Husky 注册 Git 钩子在 pre-commit 阶段运行 lint-staged 检查暂存文件在 commit-msg 阶段运行 commitlint 检查提交信息格式。先装依赖npm i -D husky lint-staged commitlint/cli commitlint/config-conventional npx husky inithusky init会自动在.husky/目录下生成 pre-commit 钩子文件。然后改 pre-commit 的内容npx lint-staged再创建.husky/commit-msgnpx --no-install commitlint --edit $1lint-staged 的配置放在 package.json 里即可{ lint-staged: { *.{js,ts,vue,jsx,tsx}: [eslint --fix, prettier --write], *.{css,scss,json,md}: [prettier --write] } }commitlint 配置// commitlint.config.cjs module.exports { extends: [commitlint/config-conventional], rules: { type-enum: [2, always, [feat, fix, docs, style, refactor, test, chore]] } }这套链路跑起来之后效果是这样的你git commit -m add login pagepre-commit 先把暂存的 JS/TS 文件跑一遍 ESLint 自动修复和 Prettier 格式化不合格的文件的修复结果会重新暂存如果还有修不掉的问题提交直接失败。通过之后 commit-msg 检查提交信息add login page这种不符合type(scope): description格式的提交会被打回正确的写法是feat(login): add login page。这里有几个实操心得想重点说第一lint-staged 只处理暂存区的文件不会全量扫描整个项目所以速度非常快不会让提交变卡。但这意味着只跑暂存文件、修复后重新 add 是 lint-staged 自己处理的你不需要手动再 git add 一遍。第二Husky 钩子在某些 CI 环境或者部分开发者机器上可能不生效常见原因是.git/hooks目录被某些工具清掉了重新npx husky install可解决。第三如果你们团队有些人实在受不了强制规则可以在 pre-commit 里给--force留个后门但我强烈不建议这么做。一旦开了先例规范墙就会逐渐坍塌。3. 性能优化开发体验与生产产物两手抓3.1 依赖预构建加速冷启动Vite 快核心就一个字预。开发环境下它用 esbuild 把你 node_modules 里用到的依赖预先打包成 ESM 格式缓存在node_modules/.vite/deps目录里。浏览器请求时直接走这套预构建产物不用像 Webpack 那样每个模块都要即时解析转换所以冷启动能做到毫秒到秒级。但预构建有个隐性问题optimizeDeps.include默认只处理静态 import 的依赖。如果你项目里有动态 import 的依赖或者某个依赖只有运行时才能确定路径Vite 可能开始时报new dependency optimized然后触发一次重新加载。第一次遇到这情况你可能觉得没什么只是页面闪了一下但依赖多的时候会反复重载开发体验直线下降。我的解决方案是在 vite.config.ts 里显式声明这些容易被漏掉的依赖export default defineConfig({ optimizeDeps: { include: [ lodash-es, dayjs, vueuse/core ] } })还有一类问题更容易被忽视当依赖版本更新哪怕只是小版本升级esbuild 重建的时候如果缓存目录里的内容失效Vite 会强制重跑预构建。这时候你会发现首次访问变慢。解决方法是明确告知 Vite 哪些依赖是稳定的哪些需要每次校验——通过optimizeDeps.entries指定入口文件路径让 Vite 只扫描这些入口缩小预构建范围。实测下来一个几十个路由的项目配置好 optimizeDeps 之后冷启动时间能稳定在 1 秒以内热更新响应速度基本是即时的。相比之下Webpack 同体量项目冷启动十几秒、热更新偶尔要等一下的情况体验差距是数量级的。3.2 代码分割别把所有东西打成一个包Vite 生产构建默认会做一些代码分割比如按动态 import 拆 chunk但默认策略比较保守。很多项目不做额外配置直接vite build产出一个巨大的 index chunk首屏下载几 MB 的 JS 是常事。我的做法是双管齐下一是业务代码层面按路由懒加载二是构建配置层面按依赖分组拆 vendor。先看路由懒加载这属于最基础也最有效的优化手段// 改造前一次性把页面组件全部打包 import Home from /views/Home.vue import About from /views/About.vue // 改造后按路由拆分访问时再加载 const Home () import(/views/Home.vue) const About () import(/views/About.vue)Vue Router 或 React Router 配合这种写法每个路由页面会单独成一个 chunk用户访问首屏时只下载当前页面需要的代码。我见过一个后台管理系统不做懒加载时首屏 JS 接近 3MB做完路由懒加载直接降到 500KB 左右体感差异很明显。FCP 时间从 3 秒多降到 1 秒多这种优化不花一分钱服务器成本性价比极高。再来看 vendor 拆分。用 Vite 的build.rollupOptions.output.manualChunks可以把体积大、更新频率低的第三方库拆成独立 chunk让浏览器充分利用缓存——这些库不变用户下次访问就直接命中缓存不需要重新下载build: { rollupOptions: { output: { manualChunks(id) { if (id.includes(node_modules)) { if (id.includes(vue) || id.includes(vue-router)) { return vue-vendor } if (id.includes(lodash) || id.includes(dayjs)) { return utils-vendor } if (id.includes(echarts)) { return echarts } return vendor } } } } }manualChunks 的id参数是模块的绝对路径通过路径匹配把相关依赖聚合到同一个 chunk。这里有一个平衡点拆太细会导致请求数过多HTTP/1.1 并发有限虽然 HTTP/2 下这不是大问题拆太粗又起不到缓存优势。我的建议是将产出体积超过 100KB 的库单独拆中小型依赖合并成一个 vendor chunk别把每个库都拆成一个文件。3.3 构建产物压缩与 CDN 可达性优化构建阶段还有一个容易被忽略的优化方向——产物压缩。Vite 内置了 esbuild 压缩器build.minify默认就是 esbuild速度快、压缩率也不错。但对极致体积有要求的项目可以换成terser或接入vitejs/plugin-legacy兼容旧浏览器和 gzip/brotli 预压缩插件。gzip/brotli 压缩这块很多前端同学不了解其实原理很简单服务器在传输静态资源时对 JS/CSS 做 gzip 或 brotli 压缩浏览器收到后再解压。Nginx 可以动态压缩但动态压缩消耗服务器 CPU而且每次请求都要重新压缩。更好的方案是在构建时直接生成.gz和.br文件Nginx 配置成优先返回预压缩文件。像vite-plugin-compression这个插件就能干这事import viteCompression from vite-plugin-compression export default defineConfig({ plugins: [ viteCompression({ algorithm: gzip, threshold: 10240 // 10KB 以下的文件不压缩收益不大 }), viteCompression({ algorithm: brotliCompress, threshold: 10240 }) ] })注意 brotli 压缩率通常比 gzip 再高 10%~20%但浏览器兼容性要求 HTTPS 环境 较新浏览器。我的策略是同时产出 gzip 和 brotli 文件由服务器根据请求头Accept-Encoding决定返回哪个。另外像 UI 组件库按需引入、图片使用 WebP 或 AVIF 格式、SVG 走雪碧图这类常规前端优化在 Vite 项目里同样适用插件生态里都有对应的方案这块就看实际项目需求再按需引入别为了优化而优化徒增维护成本。3.4 用分析工具给构建产物体检优化如果没有量化数据就只能是玄学。我强烈建议在项目里引入体积分析工具。像rollup-plugin-visualizer或vite-bundle-visualizer都能在构建完成后生成一份产物体积的可视化报告——每个 chunk 有多大、里面包含哪些模块、哪些依赖是可以被剔除的一目了然。安装和引入方式import { visualizer } from rollup-plugin-visualizer export default defineConfig({ plugins: [ visualizer({ open: true, gzipSize: true, brotliSize: true }) ] })跑一次npm run build浏览器会自动打开那份报告。我拿到报告后的标准动作是这样的先看最大的几个 chunk 是谁然后逐个确认——这个依赖真的必要吗能不能换更轻量的替代品能不能按需引入能不能拆成动态加载曾经有个项目报告里显示某图表库占了 400KB但实际上整个项目只用到了其中折线图和柱状图两个模块改成按需引入后体积瞬间砍掉 70%。体积优化不是一刀切的越小越好核心是让用户感知到快。首屏要小是因为首屏直接影响首次加载时间非首屏路由的代码晚点加载无伤大雅。所以优化优先级应该是首屏代码大小 关键路径资源数量 缓存命中率 总体积。4. 常见问题与排查实录4.1 速查表高频问题的一线排查经验问题现象可能原因解决方案环境变量在代码里是 undefined变量没有VITE_前缀或文件命名不对检查.env文件名与 scripts 里的--mode是否匹配变量名加VITE_前缀vite.config.ts 里读取不到环境变量使用了对象形式的 defineConfig拿不到 mode改为函数形式defineConfig(({ mode }) {...})用loadEnv加载修改 .env 文件后变量没变Vite 默认缓存了 env 结果重启 dev server少数情况需要删node_modules/.vite缓存ESLint 规则全部失效ESLint 版本过低或不支持扁平配置升级到 ESLint v9用eslint.config.js替换.eslintrcPrettier 格式化不生效编辑器默认格式化器不是 Prettier或有多个插件冲突设置里将默认格式化器改为 Prettier禁用或卸载其他格式化插件Husky 钩子执行不了.git/hooks 失效或 husky 未正确安装删掉.husky/_目录后重新npx husky install构建速度越来越慢依赖体积膨胀manualChunks 过于分散用 visualizer 定位大依赖考虑按需引入或换轻量库合并过细的 chunk开发环境热更新偶尔失效optimizeDeps 漏配某些动态依赖在optimizeDeps.include里显式声明重启 dev server这张表是我在多个项目里反复踩坑后总结出来的多数问题都和环境变量机制、ESLint 扁平化迁移、依赖预构建缓存这三类有关排查时可以按这个优先级走。4.2 三个我印象最深的实战案例先聊聊环境变量泄露的问题。有一次同事在代码里直接写了import.meta.env.VITE_API_SECRET用来做请求签名本地调试一切正常上线后却发现这个 secret 被暴露在浏览器里。原理不复杂所有VITE_前缀的变量都会被打包到客户端代码里本质上就无法保密。解决方式是把这个 secret 挪到服务端代理层前端请求统一走/api路径由 Node 网关在服务端注入鉴权信息。这个教训让团队定了一条铁律任何密钥类信息禁止进.env文件必须走服务端环境变量或密钥管理系统。然后是 ESLint 扁平化迁移的坑。某次从老项目迁移配置从.eslintrc.js复制了一大段 rules 过来结果 ESLint 只报了几个基础错误自定义规则全部失效。排查到最后发现是扁平配置的语法变了——不再是module.exports { extends: [...] }这种对象形式而是导出数组每个元素可以是配置对象、文件匹配模式、插件引用。官方迁移工具能自动转换大部分配置但自定义规则必须手动去新语法里逐条核查。这事之后我养成了习惯每改完 ESLint 配置先在终端跑一条npx eslint src确认规则生效再谈其他。最后是那次构建内存溢出的排查。项目做到中后期依赖越来越多某个版本升级之后vite build动不动报JavaScript heap out of memory。一开始以为是代码问题后来定位到是构建期转译源码时内存占用峰值太高。临时方案是把 Node 内存上限调高NODE_OPTIONS--max-old-space-size4096治本方案是减少构建期的工作量——把某些只用于开发环境的 polyfill 挪到动态加载、给大规模项目划分成多个子应用分开构建。内存调参是止痛药架构调整才是根治这中间的取舍要看项目现实情况。4.3 团队落地规范的两个沟通建议技术方案再完善落地时最大的阻力往往是人。推代码规范这件事我有两个实际经验想分享第一别搞突击式推行——挑一个版本迭代的间隙选一个大家相对空闲的时间窗口统一升级依赖、统一配置、统一跑一次格式化把整个仓库的代码全部规整一遍再开始约束新提交比从某天开始突然卡提交要平滑得多。第二把规范的收益直观展示出来。比如 lint-staged 能让提交前检查在几百毫秒内完成不拖慢任何人的开发节奏Prettier 格式化之后代码 diff 会变小代码 review 的负担降下来——这些切身利益讲透了同事的配合度会完全不同。写在最后的个人感受我从 Webpack 迁移到 Vite 已经两年多最大的体感变化是构建工具不再是一个让人焦虑的黑盒。Vite 的工程化配置虽然也有一堆细节但它设计得足够直观配合 esbuild 和 Rollup 这套底层组合开发体验和构建质量都站在了现阶段工具链的第一梯队。环境变量、代码规范、性能优化这三块说穿了都不需要高深的理论关键是把机制理解透、把配置落到项目的真实需求上。环境变量的核心是哪些变量能进客户端、哪些必须留在服务端代码规范的核心是让工具链替人守住底线而不是靠人肉 review性能优化的核心是盯住首屏体验和缓存命中率而不是盲目追求体积最小。把这几点想明白配置写起来就有了自己的判断力不用再到处复制粘贴别人的模板。希望这篇分享能让你少走几步弯路如果你也有自己的一套工程化配置心得多交流实践中才能沉淀出最适合自己团队的那套方案。