ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

vue.config.js配置全解:从开发调试到构建优化的核心技能

vue.config.js配置全解:从开发调试到构建优化的核心技能 我最早意识到vue.config.js是“绕不过去的坎”是在一个项目上线前最后一晚本地跑得飞快的 Vue 项目一部署到测试服务器就白屏控制台报了一堆静态资源 404。接着是后端同事说接口跨域了再然后是首屏加载直接把我电脑风扇转满。三个问题全指向同一个文件而当时我的vue.config.js还几乎是个空壳。后来做过的 Vue 项目越多越发现这个文件才是 Vue CLI 工程真正的“中枢神经”。它看起来只是导出一个对象实际控制着开发服务器、构建产物、Loader 规则、插件注入、多环境变量几乎决定了你的项目在上线前是省心还是糟心。这篇文章我想从实际踩坑的角度把这几年在vue.config.js里沉淀下来的配置思路、原理解释和可复用的配置片段一次讲透。1. 为什么说 vue.config.js 是 Vue CLI 项目的中枢配置很多人第一次见到vue.config.js是在脚手架生成的项目根目录里。它可选、可空、甚至删掉项目也能跑于是不少同学就默认它是“不急着学的东西”。但这个认知在工程规模变大后会立刻反噬。1.1 它到底在配置什么层面Vue CLI 3 之后的核心设计思路是“零配置上手渐进式自定义”。脚手架帮你把 Webpack、DevServer、PostCSS、ESLint 这些底层工具全部封装好了对外只暴露一个统一入口。vue.config.js就是这个入口它在vue/cli-service启动时被加载经过内部处理后再合并成最终的 Webpack 完整配置。它实际控制的层级可以分成四层DevServer 层本地开发服务器的端口、代理、热更新、host 配置全在这里控制。Build 输出层产物目录、静态资源路径、文件名 hash、sourcemap 策略决定打包出来的 dist 长什么样。Loader 与规则层哪些文件走什么 loader 处理、哪些依赖需要被转译、如何修改已有的 webpack 规则。插件层往构建流程里注入自定义 Webpack 插件比如体积分析、代码压缩、CDN 加速。这四层几乎覆盖了一个前端工程从开发到上线的全部链路。所以配置不熟的项目日常开发不会有感觉因为 DevServer 的默认值基本够用但一到部署、一到性能优化、一到多环境切换就开始连环踩坑。1.2 加载时机与基本形态vue.config.js运行在 Node.js 环境中用的是 CommonJS 语法而不是 ES Module// vue.config.js const { defineConfig } require(vue/cli-service) module.exports defineConfig({ transpileDependencies: true, lintOnSave: false, devServer: { port: 8080, }, })这里需要注意凡是业务代码里能用import的语法在这个文件里都不能直接用除非你用 CJS 的require变通。我见过有人好奇地在里面写export default结果 CLI 直接报“Unexpected token export”。这个文件本质是一个 Node 脚本不是在浏览器里跑的模块。另外还有一个很实用的判断标准这个文件里绝对不能写任何业务逻辑。它是构建期的配置文件不是运行时代码不要尝试在这里面放接口地址、业务枚举、权限表之类的数据。跨环境变量有专门机制后面讲.env文件归它管的只有构建行为本身。注意改完 vue.config.js 里的大部分配置尤其是 devServer 相关必须手动重启 npm run serve热更新不会自动加载。这一点很多人反复踩坑改了半天页面没变化以为是配置写错了其实纯粹是没重启。1.3 默认值为什么要清楚很多人配置报错是因为不知道默认值是什么。比如publicPath默认是/意味着构建产物的所有资源引用都是以根路径开头比如outputDir默认是dist比如assetsDir默认是空字符串静态资源直接平铺在 dist 根目录。我整理了一份高频配置项清单方便你对照排查表vue.config.js 核心配置项速查配置项默认值作用常见适配场景publicPath/打包资源的基础路径部署在子路径或 CDN 时必须改outputDirdist构建产物输出目录后端有固定目录要求时改assetsDir静态资源子目录想让 js/css/img 分目录存放时改indexindex.html入口 HTML 文件名后端模板接管时需要改productionSourceMaptrue生产环境是否产出 sourcemap有安全/体积要求时设 falselintOnSavetrue保存时是否执行 eslint 检查觉得卡顿或想交给 CI 时设 falsetranspileDependencies[]指定 node_modules 中需要被 babel 转译的依赖安装的包是 ES 版本时用devServer内置默认开发服务器配置端口/代理/热更新等把这张表记在心里很多配置问题根本不用搜索直接查自己的场景属于哪一行就行。2. devServer 代理配置本地联调的第一道拦路虎前后端分离开发时本地页面请求后端接口几乎必然遇到跨域。因为你的开发服务器跑在localhost:8080后端接口地址是http://192.168.1.10:9090两边端口不同、host 也可能不同浏览器同源策略直接就把请求拦了。解决方案有两个方向后端开启 CORS或者前端做代理。在真实的团队协作里后端的 CORS 通常只放行特定域名本地开发环境的 host 千奇百怪所以最稳妥的做法就是让 DevServer 做转发代理浏览器看到的所有请求都发往同一个源跨域问题从根上消失。2.1 一段靠谱的 proxy 配置以下是我项目中经常使用的代理配置// vue.config.js module.exports { devServer: { port: 8080, host: 0.0.0.0, proxy: { /api: { target: http://192.168.1.10:9090, changeOrigin: true, ws: true, pathRewrite: { ^/api: }, // 后端接口没有统一前缀时需要重写路径 }, }, }, }字段拆开解释一下target后端真实地址。可以是内网 IP、局域网域名也可以是线上环境的临时地址。changeOrigin: true让代理转发时把请求头里的 Host 字段改成 target 的域名。很多后端接口会根据 Host 做校验或路由不加这个字段会得到诡异的 404。pathRewrite路径重写。这里的意思是把请求路径开头的/api去掉再转发给后端。比如前端请求/api/user/list后端实际收到的是/user/list。这组配置是我踩了无数次坑以后固定下来的前端请求全带/api前缀后端服务不一定有这个前缀所以必须重写。如果你的后端所有接口本身就带/api那 pathRewrite 这一行可以删掉。2.2 axios baseURL 与 pathRewrite 的配合细节很多同学在 axios 里写baseURL: /api又在代理里把/api重写掉了结果请求发出去变成/user/list代理一看“没有/api开头的路径”直接不转发最后请求落在 DevServer 自己身上返回一个 index.html。正确的配合方式是二选一方案 Aaxios baseURL /api代理里pathRewrite: { ^/api: }后端不需要感知/api前缀。方案 Baxios baseURL 或具体接口路径代理里不写pathRewrite后端接口本身就带/api。我个人推荐方案 B因为生产环境如果也用 Nginx 做同源反向代理后端大概率是有统一前缀的开发环境保持一致的路径规则可以减少环境切换时“路径对不上”的坑。2.3 配置不生效的三大原因代理配置看起来简单但经常有人配了以后还是没有效果。我遇到过的原因基本逃不出这三个第一没有重启 dev server。代理配置在 devServer 启动时就固定了保存文件不会重新加载必须重启。第二请求路径根本没经过代理。如果你的前端项目部署后通过 Nginx 访问而你在浏览器访问的地址是 Nginx 的地址代理根本不参与。代理只作用于本地 DevServer 启动时的开发环境。第三代理前缀与 axios baseURL 不一致。请求发出去了但实际路径不是以/api开头代理规则匹配不上。建议配置完先打开浏览器 Network 面板看请求 URL 的 Path 部分确认它走到了预期前缀。注意changeOrigin 在大多数场景下都建议设为 true。尤其是请求公司内网统一登录系统SSO或网关的时候Host 不对会被直接拒绝或跳转错误。3. publicPath部署白屏和 404 的第一嫌疑这一节必须单独拿出来说因为publicPath 的配置错误是线上事故出现频率最高的一种。它控制的是构建产物中所有静态资源JS、CSS、图片、字体的引用路径默认值是/。3.1 根路径和相对路径的区别默认的/代表所有资源引用从域名根路径开始script src/js/app.abc123.js/script如果你的应用部署在域名根路径https://example.com/下没问题资源能正常加载。但如果部署在子路径下比如团队用 Nginx 将前端应用挂在https://example.com/web/下那浏览器会去请求https://example.com/js/app.abc123.js而真实文件位于https://example.com/web/js/自然 404应用白屏。解决方式是改成相对路径module.exports { publicPath: ./, }这样构建产物里的资源引用会变成script srcjs/app.abc123.js/script页面从哪个子路径打开资源就从哪个子路径下拼接获取灵活性最大。但相对路径也有代价如果你的前端应用做了 history 路由mode: history并且在二级路径下刷新页面Nginx 的 try_files 需要正确回退到 index.html否则子路由下的资源路径同样会错乱。3.2 publicPath 与 vue-router base 的联动这里有一个非常容易忽略的点改了 publicPath路由 base 也要同步改。比如你的应用部署在/web/下publicPath已经设成/web/了但 vue-router 的createWebHistory()默认使用location.pathname作为基准路径你在 Nginx 里必须保证所有/web/前缀下的请求都回退到index.html。如果不想在代码里硬编码/web/可以借助环境变量// 推荐写法避免硬编码 const router createRouter({ history: createWebHistory(process.env.BASE_URL), routes, })Vue CLI 构建时默认会注入process.env.BASE_URL它的值正好来自publicPath。所以保持两者一致路由就不会因为路径嵌套而错乱。3.3 真实部署场景下的 publicPath 组合不同部署方式对应的配置确实不同我在项目里验证过这几类表部署场景与 publicPath 推荐值部署方式publicPath 推荐值说明域名根路径/最简单常规部署Nginx 子路径/web/静态资源全在 /web/ 下独立 CDN 主域名线上 CDN 地址静态资源全走 CDN页面托管在主站本地 file 协议打开./不通过服务器直接打开 dist/index.html适合临时预览我遇到过最极端的场景是静态资源放 CDNCDN 域名是cdn.example.com应用本身跑在www.example.com。这种时候 publicPath 就设成https://cdn.example.com/构建出来的 JS 引用也全部指向 CDN。但如果 CDN 回源配置不到位文件会加载失败所以选这条路之前先确认 CDN 的回源策略。提示publicPath 不是只能写成静态字符串它可以是相对路径、绝对路径、甚至是完整 URL。Vue CLI 会把它直接拼接进资源路径里。所以只要你的部署拓扑明确了这个值就不难确定。4. configureWebpack 与 chainWebpack深度定制 Webpack 的两种姿势Vue CLI 把 Webpack 配置封装了一层但总有默认配置满足不了的需求加一个插件、改几个 Loader 选项、调整压缩参数。这种时候就需要直接操作 Webpack 配置有两条路configureWebpack和chainWebpack。4.1 什么时候用 configureWebpackconfigureWebpack支持接收对象或函数。对象形式适合新增配置项比如加一个插件const BundleAnalyzerPlugin require(webpack-bundle-analyzer).BundleAnalyzerPlugin module.exports { configureWebpack: { plugins: [ new BundleAnalyzerPlugin(), ], }, }函数形式适合需要拿到最终配置后再修改的场景module.exports { configureWebpack: (config) { if (process.env.NODE_ENV production) { config.optimization.minimizer[0].options.terserOptions.compress.drop_console true } return config }, }这里我配置的是生产环境下自动去掉console.log。因为压缩 minifier 在默认配置里是一个数组函数形式可以方便地直接访问并修改它。4.2 什么时候必须用 chainWebpack如果要修改已有的默认规则configureWebpack就不够用了。比如你想给某个 loader 加参数、想把某个规则从默认匹配中剔除、想修改默认的 splitChunks 行为这些操作在对象合并中无法表达必须用chainWebpack的链式调用来精确修改。chainWebpack基于 webpack-chain 库风格是链式 API。我实际用到最多的是给 svg 文件新增一个自定义规则module.exports { chainWebpack: (config) { // 找到默认的 svg 规则排除掉 icons 目录 config.module .rule(svg) .exclude.add(path.resolve(__dirname, src/assets/icons)) .end() // 给 icons 目录下的 svg 新增一个规则使用 svg-sprite-loader config.module .rule(icons) .test(/\.svg$/) .include.add(path.resolve(__dirname, src/assets/icons)) .end() .use(svg-sprite-loader) .loader(svg-sprite-loader) .options({ symbolId: icon-[name] }) }, }这套配置在做图标雪碧图的时候非常常见让大部分 svg 走默认的 file-loader单独把某个目录下的 svg 交给 svg-sprite-loader 处理生成 symbol 集合方便用svguse引用。4.3 基于环境变量动态切配置实际项目里常有这种需求测试环境需要 sourcemap 定位问题生产环境为了安全不要 sourcemap或者测试环境不压缩代码方便报错行号还原。跨环境差异就该用环境变量来驱动而不是在发布前手动改配置。// vue.config.js const isProd process.env.NODE_ENV production module.exports { productionSourceMap: !isProd, css: { sourceMap: !isProd, extract: isProd, }, configureWebpack: (config) { if (!isProd) { config.devtool eval-cheap-module-source-map } }, }现在部署平台都会注入NODE_ENVnpm scripts 里也能通过--mode指定所以配置里通过process.env.NODE_ENV做分支判断是最保险的做法不需要额外引入第三方库。5. 构建性能与产物优化配置里的每一行都在为上线铺路vue.config.js里关于构建优化的配置是收益最明显也最容易被忽略的一块。项目规模一大首次构建 2 分钟、二次构建 40 秒、单文件超过 1MB是很常见的事。下面这三板斧我几乎在每个项目里都会用。5.1 体积分析先行不知道哪里大优化无从谈起很多人的优化是盲目的——抽了个公共库体积没降多少加了个压缩插件构建时间反而涨了。所以我建议第一步永远是先接入体积分析插件const { BundleAnalyzerPlugin } require(webpack-bundle-analyzer) module.exports { chainWebpack: (config) { if (process.env.npm_config_report) { config .plugin(webpack-bundle-analyzer) .use(BundleAnalyzerPlugin, [{ analyzerPort: 8888 }]) } }, }这样当你运行npm run build --report时构建完成会自动打开一个可视化页面你的dist里哪个包占了多大体积、哪个依赖是重复引用的、哪个大库其实可以换懒加载全部一目了然。5.2 第三方库 CDN 化externals 的正确打开方式像 Vue、Vue Router、Axios、Element UI 这类几乎不会变的库完全可以不参与打包而是通过 CDN 引入构建产物体积能小一半还不止。配置方式分两步。第一步在vue.config.js里告诉 Webpack 这些依赖不用打包了module.exports { configureWebpack: { externals: { vue: Vue, vue-router: VueRouter, axios: axios, element-ui: ELEMENT, }, }, }第二步在public/index.html里通过 CDN 标签引入这些库。注意顺序先引 Vue 和 Router再引 Element UI再引你自己的业务代码。这个过程本质上是在说“名字叫 Vue 的全局变量就是 vue 这个包构建时不用管它运行时浏览器会给你。”外链 CDN 有一个容易被忽略的细节CDN 挂掉或网络被墙整个应用直接白屏。所以如果是内网部署更稳的方案是把这些静态文件下载到本地由 Nginx 托管配置里照样用 externals 排除打包但 HTML 里的 src 指向本地路径。5.3 构建提速让 Webpack 少干点重复活构建速度的优化核心思路是“少编译、多缓存”。下面是几个配置维度的经验值transpileDependencies: []默认情况下 CLI 不会对 node_modules 里的文件做 Babel 转译这是正确的。如果你额外安装的包需要转译比如 lib-flexible、vant 的 ESM 版本才需要把它们加进来不要无脑全量转译。开启cache-loaderVue CLI 默认就带了 Babel 的缓存一般不必额外配置。你真正需要关注的是有没有在业务代码里import了过多的大模块。对于大型项目可以尝试把thread-loader加到 babel-loader 之前让多进程并行转译 JS。注意不是所有场景都值得小项目加上反而因为进程调度拖慢速度。给一组可用的 chainWebpack 示例const path require(path) module.exports { chainWebpack: (config) { config.module .rule(js) .test(/\.m?jsx?$/) .exclude.add((filepath) { // 不转译 node_modules 下大部分包只保留指定需要转译的包 return /node_modules/.test(filepath) !/node_modules[\\/](vue-awesome-swiper)[\\/]/.test(filepath) }) .end() }, }exclude函数里可以精细控制哪些 node_modules 里的包需要被处理哪些不用处理这个比全量排除再放行要灵活很多。5.4 sourcemap 策略开发要方便上线要安全sourcemap 是构建产物和源码的映射文件看报错堆栈时很有用但它同时也是暴露源码的工具生成的.map文件体积还不小。一个常见配置组合是module.exports { productionSourceMap: false, // 生产环境不生成 .map 文件 css: { sourceMap: process.env.NODE_ENV ! production, }, }如果团队确实需要线上定位问题可以考虑只在测试环境打开 sourcemap生产环境关闭。大部分公司的做法是生产环境配合一套错误监控平台如 Sentry错误上报时带上 release 版本号再通过 sourcemap 映射回来这是一种更专业的兜底方案。提示关闭 productionSourceMap 后如果后端或运维同事反馈线上报错看不到具体代码位置不要直接打开 map。正确的做法是保留一份构建时的 map 文件到归档系统线上不直接暴露。6. 多环境配置一套代码在不同环境间自由切换一个前端项目通常至少有三个环境开发、测试、生产。不同环境对应的接口地址、CDN 路径、路由模式可能都不同。vue.config.js本身做不了这件事但它和.env文件配合得天衣无缝。6.1 .env 文件的基本规则Vue CLI 支持在项目根目录放.env、.env.development、.env.production等文件文件里的键值对会自动注入到process.env中。但要注意只有以VUE_APP_开头的变量才会被注入到客户端代码中。NODE_ENV和BASE_URL是特殊情况不用加前缀CLI 会自动注入。变量在所有vue.config.js可访问的 Node 环境里也能用。举个例子// .env.development VUE_APP_API_BASE_URL /api VUE_APP_PUBLIC_PATH / // .env.production VUE_APP_API_BASE_URL https://api.example.com VUE_APP_PUBLIC_PATH https://cdn.example.com/然后在vue.config.js里读取并使用module.exports { publicPath: process.env.VUE_APP_PUBLIC_PATH || /, devServer: { proxy: { /api: { target: http://192.168.1.10:9090, changeOrigin: true, pathRewrite: { ^/api: }, }, }, }, }业务代码里用它axios.defaults.baseURL process.env.VUE_APP_API_BASE_URL6.2 自定义 mode不止开发和生产CLI 默认只有development和production两种模式但实际工作中往往需要staging预发布、test测试、mock模拟数据等更多环境。通过--mode参数可以实现自定义// package.json { scripts: { serve: vue-cli-service serve, build: vue-cli-service build, build:test: vue-cli-service build --mode test, build:staging: vue-cli-service build --mode staging } }运行时CLI 会加载.env.test、.env.staging文件。注意--mode只是指定加载哪个.env文件不会自动改NODE_ENV所以你仍然需要在.env.test里手动指定NODE_ENVproduction因为测试环境也要走压缩构建否则 CLI 默认把它当开发模式处理。我见过很多人在这一环节翻车npm run build:test构建出来居然还带着 sourcemap 且未压缩就是因为没有在.env.test里补上NODE_ENVproduction。这个坑报错不一定明显但产物一看就露馅。6.3 从 env 文件到配置生效的完整链路把这套机制串起来看一个典型的多环境配置链路是开发本地执行npm run serveCLI 读取.env.developmentprocess.env.NODE_ENV被设为developmentVUE_APP_API_BASE_URL被注入。同时读取vue.config.jspublicPath、devServer.proxy根据 env 变量的值生成。构建时执行npm run build:testCLI 读取.env.testNODE_ENVproduction代码压缩、关闭 sourcemap接口指向测试地址。正式上线执行npm run build读取.env.production接口、CDN 路径、publicPath 全部切换成生产值。这样一套代码就能在开发、测试、预发、生产之间平滑切换不用在部署时改任何业务代码也不用运维同学临时帮你改配置。7. 组合起来一套可以直接落地的生产级配置章节拆开讲完我最后放一个完整示例。这个配置是我在多个中型后台管理项目中验证过的涵盖了代理、多环境、体积优化、CDN、构建提速你可以直接参考然后按需删减。// vue.config.js const path require(path) const { defineConfig } require(vue/cli-service) const { BundleAnalyzerPlugin } require(webpack-bundle-analyzer) const isProd process.env.NODE_ENV production const isAnalyze process.env.npm_config_report true module.exports defineConfig({ transpileDependencies: true, lintOnSave: false, productionSourceMap: false, publicPath: process.env.VUE_APP_PUBLIC_PATH || /, outputDir: dist, assetsDir: static, indexPath: index.html, devServer: { port: 8080, host: 0.0.0.0, client: { overlay: { warnings: false, errors: true, }, }, proxy: { /api: { target: process.env.VUE_APP_DEV_TARGET || http://192.168.1.10:9090, changeOrigin: true, ws: true, pathRewrite: { ^/api: }, }, }, }, css: { sourceMap: !isProd, extract: isProd, }, configureWebpack: (config) { // 生产环境去掉 console if (isProd) { config.optimization.minimizer[0].options.terserOptions.compress.drop_console true } // 接入体积分析 if (isAnalyze) { config.plugins.push(new BundleAnalyzerPlugin()) } }, chainWebpack: (config) { // 配置别名 config.resolve.alias .set(, path.resolve(__dirname, src)) .set(views, path.resolve(__dirname, src/views)) // 忽略不需要打包的第三方库 config.plugin(ignore).use(require(webpack).IgnorePlugin, [ /^\.\/locale$/, /moment$/, ]) // 修改 html 插件参数按需注入外部 CDN config.plugin(html).tap((args) { args[0].cdn { css: [], js: isProd ? [ https://cdn.example.com/vue/2.7.14/vue.min.js, https://cdn.example.com/vue-router/3.6.5/vue-router.min.js, https://cdn.example.com/axios/1.4.0/axios.min.js, ] : [], } return args }) }, })这个配置是一个比较重的形态实际项目可以根据团队情况砍掉一部分。比如你的部署环境用不到 CDN就可以去掉 html plugin 的 CDN 注入比如项目规模不大就不需要 IgnorePlugin。对应的.env文件生产版// .env.production NODE_ENVproduction VUE_APP_BASE_URLhttps://example.com/ VUE_APP_PUBLIC_PATHhttps://static.example.com/ VUE_APP_DEV_TARGEThttps://api.example.com/这里我把接口地址拆分成了VUE_APP_BASE_URL页面域名和VUE_APP_DEV_TARGET后端接口地址分别是业务代码和 devServer 代理使用的目标互不干扰部署时只要替换这些值即可。上线前建议再核对这些配置项检查项正确姿势错误后果publicPath与部署路径/CDN 地址一致静态资源 404、应用白屏productionSourceMap生产环境设为 false源码泄露、构建产物过大drop_console生产环境开启console 影响部分浏览器性能devServer.proxy与后端实际地址一致联调失败、接口 404.env文件内容不出现硬编码敏感信息密钥泄露、环境变量污染CDN 外链有可用性保障外链挂掉时应用整体不可用我在这套配置上踩过最深的坑是上线前改完 publicPath 后忘记同步 update 路由 base结果应用在子路径下确实能打开首页但一旦进入二级路由刷新页面Nginx 找不到对应的资源路径全部 404。后来养成了习惯凡是动 publicPath必定检查 vue-router 的 history base必定检查 Nginx 的 try_files 配置三处保持一致再发包。如果你现在维护的 Vue CLI 项目还没有系统梳理过vue.config.js我建议你抽个下午把现有配置打开对着上面这几类问题逐项过一遍。这个过程会让很多以前“跑得好好的、但不知道为什么会挂”的线上问题一下子就有了答案。如果项目已经准备迁往 Vite这份配置里的 publicPath、proxy、多环境变量的经验也可以平移过去因为配置的本质是同一套部署逻辑只是壳换了。
返回列表