简介:一套基于Vue 3与Webpack(非Vite版本)构建的若依UI框架项目实例,面向需要快速搭建后台管理系统、或想深入理解Vue 3工程化配置的前端开发者。资源共2004个文件,以脚本源码(966个)与Markdown说明文档(975个)为主,另有52个配置文件、5个网页入口、2个文本说明,压缩包体积57.1MB。脚本文件承载组件逻辑与页面交互,文档说明承载相关技术细节,配置文件多为依赖和构建规则,网页文件可作入口或测试页。内容体现了Vue 3组合式API、传送门等特性在若依后台项目中的实际运用,以及Webpack下加载器、摇树优化、代码压缩等模块处理流程,便于理解Vue 3新特性与Webpack传统构建的配合方式;目录包含源码目录、构建产物、依赖清单和Webpack配置文件,便于对照学习构建配置与依赖管理。已有675人学习下载,整体结构既适合通读源码,也适合作为后台项目二次开发与工程化优化的参考。
1. 若依Vue3的Webpack版本:不是技术倒退,是团队的现实选择
手头这个若依Vue3的Webpack版本,不是官方默认模板,而是把Vite构建换成webpack 5之后的工程形态。很多人不理解:Vite开发热更新快那么多,为什么还要回头用webpack?我见过太多团队的真实处境——CI里跑了几年的webpack产物脚本、锁在Node 14上的构建镜像、还有只熟悉webpack打包编排的运维,这些没法一夜换掉。与其硬上Vite,不如把若依Vue3稳定跑在webpack上,前端代码和接口范式一点不变。这篇笔记就是给这类团队准备的,我会把这套工程的关键配置、参数和踩坑按落地顺序讲清楚,让你拿到手能跑、跑起来能查、查完能改。
2. 拆清Vite版与Webpack版的5个关键差异,迁移才不容易跑偏
迁移的第一件事不是抄配置,而是先把两套构建体系里名字不同、职责相同的部分对上号。Vite的优势在于开发阶段用原生ESM按需编译,生产才走Rollup打包,所以它的配置文件短、依赖预构建自动做;webpack则是统一打包模型,开发和生产共用一条loader链。若依Vue3的源码里有很多和Vite绑定很深的写法,比如import.meta.env、public目录的静态资源引用、样式里的路径别名,这些在webpack下全部要重新接一遍。
2.1 配置入口:vite.config.js 与 webpack.config.js 里的同一件事
若依Vue3官方模板的构建配置集中在vite.config.js,里面做了路径别名、开发代理、打包分包、Gzip压缩和可视化分析。换成webpack后,这些职责分散到了webpack.config.js的resolve、devServer、optimization、plugins几个区块里,内容更多,但每个部分更直白。我一般会先做一张对照表,把两边配置项手动对齐,避免迁移时漏项。
| Vite 配置项 | Webpack 对应位置 | 迁移注意点 |
|---|---|---|
| resolve.alias | resolve.alias | @指向src,两边写法几乎一样 |
| server.port / open | devServer.port / open | 注意webpack 5的devServer配置项从devServer传入,不是serve |
| server.proxy | devServer.proxy | 路径匹配规则不同,Vite用对象键匹配,webpack支持数组或对象 |
| build.outDir | output.path | webpack里要配合output.clean清理旧产物 |
| build.rollupOptions.input | entry | 若依入口是src/main.js,基本不用动 |
| plugins(unplugin等) | plugins | 插件的webpack写法大部分要换导入路径 |
| build.sourcemap | devtool | Vite的sourcemap在生产默认关闭,webpack默认是关闭的,但devtool写错会严重影响体积 |
对照完你会发现,真正难的不是配置迁移,而是那些Vite自动帮你做掉的事,在webpack里必须显式声明,比如historyApiFallback、Sass的全局变量注入、依赖预构建。
2.2 环境变量:import.meta.env 与 process.env 的替换策略
这是若依Vue3迁移到webpack时最绕不开的一个点。Vite版源码里大量使用import.meta.env.VITE_APP_BASE_API来读取接口前缀,webpack 5虽然支持import.meta语法,但不会像Vite那样自动往import.meta.env里塞环境变量。如果不处理,所有接口请求的baseURL都会变成undefined,登录直接失败。
常见的处理方式有两种。第一种是把源码里所有import.meta.env全局替换成process.env,然后配合DefinePlugin注入变量,改动量大但直观;第二种是我推荐的,用DefinePlugin直接对import.meta.env.xxx做字面替换,源码一行不动。后者在后续升级若依官方代码时能明显减少冲突。
2.3 开发服务器:代理规则与history路由回退
若依Vue3的开发环境里,前端请求统一带/dev-api前缀,由构建服务器转发到后端接口。Vite的server.proxy默认帮你在转发前剥掉前缀,webpack的devServer.proxy也能做同样的事,但有一个隐藏区别:Vite对History路由模式默认支持回退,webpack必须手动开historyApiFallback: true。漏掉这一项,你在浏览器地址栏直接访问http://localhost:xxx/system/user这类子路由时就会白屏或404。
我在迁移时习惯把代理和后端真实地址分开写在配置顶部,方便不同环境切换。后端默认跑在http://localhost:8080,代理目标就写到这个地址,然后按环境变量区分/dev-api和/prod-api。
2.4 样式链路:Sass与PostCSS谁负责做前缀和变量
Vite对CSS的处理近乎零配置:装了sass就能直接用.scss,PostCSS自动加浏览器前缀。webpack里样式的loader顺序是从右往左执行的,常见链条是sass-loader -> postcss-loader -> css-loader -> style-loader(开发)/MiniCssExtractPlugin.loader(生产)。loader顺序错了,最常见的报错是You need to add a postcss loader,或者是变量$xxx未定义。
若依Vue3的全局样式里有一套variables.scss,里面的颜色变量、圆角和阴影值被每个子页面的样式引用。Vite版里通过css.preprocessorOptions.additionalData注入,webpack下要在sass-loader的sassOptions.additionalData里写同样的@import。另外注意看你的sass-loader是配的sass(Dart Sass)还是node-sass,若依的新版本样式默认兼容Dart Sass,node-sass也可以跑,但遇到@use语法时会有兼容问题。
2.5 按需引入与依赖预构建:Vite的免配置项在Webpack里都要显式声明
Vite启动时会预构建node_modules里的依赖,把CommonJS模块转成ESM,所以Element Plus这类库基本开箱即用。webpack没有这个自动步骤,你得在resolve.mainFields里显式告诉它优先用模块的module字段,否则可能加载到一份不适合浏览器打包的CommonJS版本。更常见的是按需引入配置:Element Plus官方文档提供了unplugin-vue-components和unplugin-auto-import的Vite用法,webpack下要改成从unplugin-vue-components/webpack导入注册,插件还是同一个,导入路径不一样。
这一段如果你不想纠结,直接在main.js里全量引入Element Plus也能跑,只是打包体积会多出300KB左右。我会在按需和全量之间优先按需,但如果项目里已经用了大量自定义主题变量,全量引入往往更省心,体积问题用Gzip兜底。
3. 把若依Vue3跑在Webpack上:核心配置文件的落地写法
这一章直接落到webpack.config.js。我以webpack 5为例,因为若依Vue3的代码和依赖链已经不需要webpack 4的老一套了。如果你的环境还是webpack 4,先放弃,升级webpack 5的成本远低于在webpack 4里给Vue3配loader的痛苦。
3.1 基础骨架:入口、输出、路径别名和HTML模板
先搭一个能跑的最小骨架。入口指向若依Vue3的src/main.js,输出格式用[name].[contenthash]做缓存友好命名。这里有个容易忽略的点:若依的index.html在public目录下,webpack的HtmlWebpackPlugin要用这个文件当模板,不然就丢掉了里面的中文标题和favicon引用。
const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') const { DefinePlugin } = require('webpack') const CopyWebpackPlugin = require('copy-webpack-plugin') module.exports = { mode: 'development', entry: path.resolve(__dirname, 'src/main.js'), output: { path: path.resolve(__dirname, 'dist'), publicPath: '/', filename: 'js/[name].[contenthash:8].js', chunkFilename: 'js/[name].[contenthash:8].chunk.js', clean: true }, resolve: { alias: { '@': path.resolve(__dirname, 'src') }, extensions: ['.js', '.vue', '.json'] }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, 'public/index.html'), favicon: path.resolve(__dirname, 'public/favicon.ico') }), new CopyWebpackPlugin({ patterns: [{ from: path.resolve(__dirname, 'public'), to: '', globOptions: { ignore: ['**/index.html'] } }] }) ] }这里的关键参数是publicPath。开发环境写/没问题,如果要部署到二级目录,比如http://域名/admin/下,就必须改成/admin/,并且路由的createWebHistory要同步传base。output.clean是webpack 5自带功能,代替了旧版的CleanWebpackPlugin,每次构建自动清空dist。CopyWebpackPlugin负责把public下的静态文件复制到产物里,但要注意把index.html排除,否则会和HtmlWebpackPlugin生成的模板冲突。
3.2 Vue 3与Babel链路:vue-loader版本与babel-loader的取舍
Vue 3的组件文件靠vue-loader解析,它不是一个独立loader,必须配套VueLoaderPlugin才能工作。版本上,vue-loader@17对应Vue 3,如果你装到了15.x,那对应的是Vue 2,编译出来的组件全是运行时报错。Babel这边没有严格必须上,若依用的是新语法,现代浏览器基本全支持,但为了兼容老客户端的Chrome 69之类,还是建议挂一层babel-loader。
const { VueLoaderPlugin } = require('vue-loader') module.exports = { module: { rules: [ { test: /\.vue$/, use: 'vue-loader' }, { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: [ ['@babel/preset-env', { targets: { chrome: '80', firefox: '78' } }] ] } } } ] }, plugins: [ new VueLoaderPlugin() ] }vue-loader的解析顺序必须放在所有loader前面,因为它会把.vue文件拆成script、template、style三部分,分别交给后续loader处理。VueLoaderPlugin不要漏,少了它启动时直接报pitcher相关错误。babel-loader的exclude: /node_modules/是必须的,否则编译来自element-plus源码,构建时间成倍上涨。
记住一个原则:Babel只处理你自己写的源码和若依的src目录,第三方库用browserslist交给webpack的target逻辑控制。target里的chrome版本不用太低,若依Vue3本身就不支持IE,没必要为了老语法付出双倍打包时间。
3.3 样式与SVG图标:sass-loader、svg-sprite-loader的参数细节
若依的侧边栏菜单、登录页logo用的都是SVG图标,放在src/icons/svg目录下,由一个SvgIcon组件统一渲染。Vite版用vite-plugin-svg-icons生成雪碧图,webpack下更成熟的做法是svg-sprite-loader。
样式链路和图标配置一起写:
const MiniCssExtractPlugin = require('mini-css-extract-plugin') module.exports = { module: { rules: [ { test: /\.scss$/, use: [ process.env.NODE_ENV === 'development' ? 'style-loader' : MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', { loader: 'sass-loader', options: { sassOptions: { additionalData: '@import "@/assets/styles/variables.scss";' } } } ] }, { test: /\.svg$/, include: [path.resolve(__dirname, 'src/icons/svg')], use: [ 'svg-sprite-loader', { loader: 'svgo-loader', options: { plugins: [ { name: 'removeAttrs', params: { attrs: '(fill|stroke)' } } ] } } ] }, { test: /\.(png|jpe?g|gif|webp)$/, type: 'asset/resource', generator: { filename: 'img/[name].[contenthash:8].[ext]' } } ] } }样式loader的链式执行顺序是从右到左:sass-loader先把.scss编译成CSS,postcss-loader处理autoprefixer加前缀,css-loader把CSS里的url()和@import解析成模块依赖,最后style-loader把样式插进HTML。additionalData是每个.scss文件编译前自动注入的代码,若依的variables.scss在这里注入后,所有页面都能直接用那些颜色变量。
SVG那块要注意include必须精确指向src/icons/svg,因为项目里其它地方也可能引用SVG图片,那些应该走下面的asset/resource,走了svg-sprite-loader就会被错误转成雪碧图符号,导致图片显示成一个空白符号。svgo-loader里的removeAttrs会移除图标里的fill和stroke属性,这样图标颜色就能被CSS的stroke或color控制,但不要移除viewBox,否则图标缩放会乱掉。
3.4 环境变量注入:用DefinePlugin替换import.meta.env的最小改动方案
若依Vue3的源码里,接口前缀到处是import.meta.env.VITE_APP_BASE_API,webpack没有这东西。最省事的方案是用DefinePlugin把import.meta.env.xxx直接替换成字面量,源码里怎么写、现在还是怎么写,一行不用动。配合dotenv读取.env文件和对应的环境文件。
const dotenv = require('dotenv') const webpack = require('webpack') const envPath = `.env.${process.env.NODE_ENV || 'development'}` dotenv.config({ path: envPath }) const envMap = {} for (const key in process.env) { if (key.startsWith('VITE_APP_')) { envMap[`import.meta.env.${key}`] = JSON.stringify(process.env[key]) } } module.exports = { plugins: [ new webpack.DefinePlugin({ ...envMap, 'import.meta.env.MODE': JSON.stringify(process.env.NODE_ENV), 'import.meta.env.DEV': JSON.stringify(process.env.NODE_ENV === 'development'), 'import.meta.env.PROD': JSON.stringify(process.env.NODE_ENV === 'production') }) ] }这里唯一的坑是JSON.stringify。DefinePlugin的替换是纯文本级别的,如果你写成envMap[key] = process.env[key],替换出来的代码是const base = /dev-api,直接把/dev-api当成了正则,页面全炸。用JSON.stringify包一层,出来的就是带引号的字符串。import.meta.env.MODE这些也要记得注入,很多第三方组件会读取MODE判断环境,漏了它可能在开发环境打出生产日志。
如果你用了webpack 5的自带文件缓存,DefinePlugin替换的变量变化时缓存会自动失效,不用担心改.env后拿到旧值。
3.5 devServer与生产构建:代理、publicPath和分包策略
开发服务器的核心是把若依的登录、验证码、菜单接口都正确转发到后端。我的习惯是代理统一只配/dev-api和/prod-api两个前缀,后端地址抽到环境变量里。生产构建则把Element Plus、Vue全家桶单独拆包,避免第三方库的更新导致业务代码缓存全部失效。
const CompressionPlugin = require('compression-webpack-plugin') module.exports = { devServer: { port: 80, open: true, hot: true, historyApiFallback: true, proxy: [ { context: ['/dev-api', '/prod-api'], target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/dev-api': '', '^/prod-api': '' } } ] }, optimization: { splitChunks: { chunks: 'all', cacheGroups: { elementPlus: { test: /[\\/]node_modules[\\/]element-plus[\\/]/, name: 'element-plus', priority: 20 }, vueVendor: { test: /[\\/]node_modules[\\/](vue|vue-router|pinia|axios)[\\/]/, name: 'vue-vendor', priority: 10 } } } }, plugins: [ new CompressionPlugin({ test: /\.(js|css|html)$/, algorithm: 'gzip' }) ] }historyApiFallback: true这行是老生常谈,但每次迁移都有人漏。它的作用是让开发服务器把所有路径的请求都回退到index.html,否则你在浏览器里直接访问http://localhost/system/user会404。pathRewrite把/dev-api前缀在转发时剥掉,因为若依后端的Controller接口路径里并没有这个前缀,前缀只是前端用来区分环境的。
splitChunks的chunks: 'all'会把异步加载的组件也纳入分包,cacheGroups里给Element Plus和Vue核心库分别建组。priority数字越大越优先匹配,Element Plus的包要设高点,vueVendor的设低点,两者不会互相把对方吞掉。CompressionPlugin做Gzip,生产环境配合Nginx的gzip_static可以先不用,让Nginx自己压缩也行,但提前生成.gz能省掉服务器的CPU开销。
4. 若依Vue3 Webpack迁移避坑:6个高发问题与排查路径
配置能跑起来只是开始,下面这些坑我几乎每次帮人排查若依Webpack版时都会遇到,按出现频率排序,每条都按现象、原因、解决三段讲清楚。
4.1 代理不生效,所有接口都打到了本地8080
现象:npm run dev启动后,打开页面正常,但登录接口请求地址是http://localhost:8080/api/login,直接跨域失败,控制台报CORS或404。
原因:axios的baseURL读了import.meta.env.VITE_APP_BASE_API,而这个变量没有正确注入,代码里的请求地址变成纯后端地址,代理规则没机会生效。也有一种情况是代理配置写成了/dev-api这个key,但请求发出时前缀已经被baseURL里的变量替换成了别的值。
解决:先在浏览器Network里看请求URL到底带不带/dev-api。不带,是环境变量问题,回到3.4节检查DefinePlugin的注入和.env.development文件;带了但代理没转发,检查proxy数组里的context是否匹配了完整前缀,注意/dev-api不要写成'/dev-api/',斜杠会匹配失败。改完代理规则必须重启npm run dev,webpack的devServer配置改动不触发热更新。
4.2 刷新后白屏,控制台只有404
现象:首页正常,从菜单跳转也正常,但按F5刷新一个子路由页面,整个页面空白,Network里能看到xxx.js加载到了index.html的内容,控制台报语法错误。
原因:vue-router用的是History模式,浏览器直接请求/system/user,webpack开发服务器不知道这个路径应该返回index.html,就返回了404,然后前端拿到404页面当JS解析,直接崩掉。
解决:devServer加historyApiFallback: true。生产环境则要看Nginx配置,location /里必须有try_files $uri $uri/ /index.html;。如果你部署在二级路径,publicPath和后端路由的base都对上了还白屏,大概率是Nginx的try_files里的回退路径写成了/index.html,应该写成/二级目录/index.html。
4.3 Element Plus图标不渲染,全是方框
现象:登录页的用户名、密码输入框前面的图标全是方框,或者菜单上的图标消失,但页面布局正常。
原因:Element Plus的图标是独立组件包@element-plus/icons-vue,必须手动注册成全局组件。若依Vue3在main.js里做了一个批量注册循环,如果你迁移时改了引入路径或用了unplugin-vue-components按需引入,自动注册的代码被跳过了。
解决:确认main.js里有类似for (const [key, component] of Object.entries(ElementPlusIconsVue))的注册逻辑,或者在组件里手动import { User } from '@element-plus/icons-vue'再用。如果用unplugin-vue-components,要检查webpack版插件配置里resolvers: [ElementPlusResolver()]有没有正确生效,这个resolver不止处理组件,还处理图标的自动导入,但两种注册方式不能混用,混了容易把图标组件注册成重复key,一样是方框。
4.4 图标雪碧图不显示,或所有图标缩成一团
现象:侧边栏菜单的小图标全部空白,或者图标挤在一起变形,但DOM里的<svg>元素存在。
原因:svg-sprite-loader的include范围没控制好,把非图标目录的SVG也编译成了雪碧图符号;或者是svgo-loader的配置里把viewBox移除了,SVG失去了宽高比例,全部按默认尺寸挤在一起。
解决:test: /\.svg$/的规则里必须同时写include指向src/icons/svg,再在图片资源的asset/resource规则里加一条exclude: [path.resolve(__dirname, 'src/icons/svg')],确保两类资源各走各的链路。svgo-loader的removeAttrs只移除fill和stroke,不要移除viewBox。排查时打开浏览器开发者工具,看SVG元素上一级use标签的href值,里面是#icon-xxx就说明雪碧图生成成功,是样式问题;如果是空引用,是loader没解析到。
4.5 打包后接口404,静态资源也404
现象:本地npm run build:prod成功,把dist扔到服务器上,index.html能打开,但所有JS、CSS加载404,接口也全部404。
原因:output.publicPath写死了/,但你的站点部署在二级目录,资源请求全指向了根路径;接口那边,生产环境用/prod-api前缀,Nginx没有把该前缀转发到后端,或转发了但pathRewrite没有在服务端做。
解决:二级目录部署时,publicPath改成/你的目录/,同时vue-router的createWebHistory传base参数。接口的404要看Nginx配置,典型写法是location /prod-api/ { proxy_pass http://后端地址/; },注意proxy_pass最后的斜杠,带斜杠会剥掉/prod-api前缀,不带斜杠则保留,若依后端接口不带前缀,所以这里要带斜杠。改完Nginx记得nginx -t测试再reload。
4.6 构建到一半内存溢出,卡死连报错都没有
现象:npm run build执行到60%左右,终端直接报FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory,偶尔连完整体积报告都出不来。
原因:webpack是单线程的,默认Node堆内存上限约1.5GB。若依项目里views目录几十个页面,加上Element Plus全量引入,SourceMap和分包计算同时进行,内存很容易打满。还有一个隐藏推手是devtool在生产环境配了source-map,那是调试才用的,生产完全没必要。
解决:先给构建命令加内存参数,NODE_OPTIONS=--max-old-space-size=4096;再把生产环境的devtool改成false或hidden-source-map,前者不生成SourceMap,后者生成但不泄露到线上。如果项目页面超过100个,给babel-loader加thread-loader或者把cache: { type: 'filesystem' }打开,webpack 5的文件缓存能把二次构建时间砍掉一半,内存峰值也会降下来。这些做完了还溢出,检查splitChunks里是不是把chunks: 'all'和maxSize写成了矛盾配置,那会导致分包递归过深。
5. 开发态与生产态分开验证:若依Vue3 Webpack版的自检清单
配置改完,别急着提交,按下面这套清单过一遍,能拦住90%的隐性问题。开发态和生产态的问题往往不一样,开发态主要看代理和热更新,生产态主要看路径和缓存。
5.1 开发态验证:登录、动态路由、接口前缀逐一过
启动项目后,按顺序检查这几项,每一步都在浏览器开发者工具的Network面板里确认请求URL:
| 检查项 | 预期结果 | 失败排查方向 |
|---|---|---|
| 登录接口 | POSThttp://localhost/dev-api/login,返回验证码图片 | 代理context和pathRewrite |
| 验证码图片 | GET...dev-api/captchaImage,返回200和图片 | 环境变量注入和代理转发 |
| 默认跳转登录页 | 路由跳转正常,图标显示正常 | 路由base、图标注册 |
| 登录成功后跳转首页 | 拉取用户信息、权限菜单接口全部200 | 动态路由模板、接口前缀 |
| 刷新子路由 | 页面不白屏,接口正常 | historyApiFallback |
| 修改vue文件 | 页面热更新,不整页刷新 | 热更新开关、VueLoaderPlugin |
有一个非常隐蔽的点:DevTools控制台如果出现GET http://localhost/favicon.ico 404,不影响功能,但说明HtmlWebpackPlugin里的favicon参数没生效,或者public目录下根本没有对应文件。若依的favicon在登录页标题栏会被加载,缺失会拖慢首次加载速度。
5.2 生产构建验证:产物结构、资源加载和gzip
构建完之后,先看dist目录结构,再本地起一个静态服务验证,不要直接FTP扔服务器。我的检查命令一直是:
npm run build:prod npx serve dist然后用浏览器访问本地静态服务,打开Network看三点:index.html里的JS/CSS引用路径是相对路径还是绝对路径;每个JS文件是否带contenthash,文件名形如app.1a2b3c4d.js;gzip是否生效,请求头里的Accept-Encoding和响应头里的Content-Encoding是否匹配。serve不会自动做gzip,如果看到所有资源都是原始大小,不代表生产有问题,但要确认Nginx侧的gzip开关是 on。
再检查dist/js目录下的chunk数量。理想情况下有一个app主包、一个element-plus包、一个vue-vendor包,加上若干个页面异步chunk。如果所有页面代码挤进了一个500KB以上的大包,回到3.5节看splitChunks的cacheGroups是否真的匹配到了路径规则,test里的正则路径分隔符和Windows没关系,统一按/写。
5.3 长期维护:版本锁、构建脚本和依赖升级习惯
构建脚本建议拆成三个环境,用webpack-merge做基础配置复用。package.json的scripts保持和若依官方一致,业务同事不需要关心底层构建方式:
{ "scripts": { "dev": "webpack serve --mode development", "build:prod": "webpack --mode production", "build:prod:preview": "npm run build:prod && npx serve dist" } }依赖版本一定要锁。若依Vue3的官方Vite版和webpack版的package.json依赖版本不一定完全对齐,特别是vue、element-plus、sass三个,升级时一次只升一个,升完跑一遍完整的5.1节自检清单。sass-loader从13升到14时,底层从node-sass切到了dart-sass,如果项目里用了/做除法运算,会直接报错;vue-loader从16升到17,VueLoaderPlugin的引入路径没变,但内部行为有差别,必须重新构建验证。
还要把package-lock.json或yarn.lock纳入版本库。团队里一旦有人用了npm install,锁文件更新后,其他机器拉下来可能被安装到兼容范围内的新版本,今天能跑,明天npm ci就挂。构建机上加一个npm ci而不是npm install的习惯,能避免大量“我这台没问题啊”的翻车现场。
6. 收尾:值得长期坚持的三个构建习惯与一个翻车教训
保持若依Vue3的Webpack版本长期稳定,我的个人习惯是三条。第一,任何依赖升级都单独提一次,升完立刻跑构建和登录流程,不要攒七八个依赖一起升,出问题你根本不知道是哪一行引起的。第二,webpack配置尽量保持和若依官方Vite版的插桩对齐,官方新增了页面模块,你只需要保证src目录的代码不动,构建层的差异永远停留在配置文件这一薄层。第三,把构建时间纳入提交检查,webpack --mode production超过两分钟就该看缓存配置了,webpack 5的文件缓存cache: { type: 'filesystem' }是白捡的性能,不开等于在给团队浪费生命。
说一个我实际踩过的教训。有一回为了压缩图标体积,我把svgo-loader的配置从移除fill和stroke扩展到了移除viewBox,理由是网上有人这么干说能减小图标体积。结果整个侧边栏的菜单图标全部挤成一团,每个图标都按默认 0x0 的尺寸渲染,页面乱得没法看。排查了半天,最后发现是viewBox被移除后,SVG失去了坐标系。更尴尬的是,图标文件总共才几十KB,省下的那点体积远小于一次排错的时间成本。从那以后我给自己立了个规矩:样式相关的优化,先理解再动手,任何和渲染结果强相关的loader参数,改完立刻看渲染效果,而不是只看构建是否通过。
如果你也在维护若依Vue3的Webpack版本,建议先把SVG的include、sass-loader的additionalData、DefinePlugin的环境变量这三处固定下来,它们稳定了,这个项目就稳了一大半。希望帮到你。
本文还有配套的精品资源,点击获取