背景:
这个项目已经有好几年的岁月沉淀了, 使用的是webpack3.6.0 + vue2.5.2 + ivew2.x
, 增改配置累, 不熟悉, 并且 webpack4
新增了很多特性和优化, 有更简洁的配置, 还可以自定义分割代码块等, 提升了项目的构建速度和编译速度, 减少了包体积等等, 原先的配置文件直接不要, 在vue.config.js
配置.咋看都是升级一下会更划算,而且我也没尝试过,想要试试也占据了大部分原因.
为啥要整:
- webpack配置文件繁琐(主要是不太熟), 以及高版本有了更多的优化空间和内容.
- 无法使用es6部分特性以及往后的新特性, 例如: 可选链操作等
- 使用的组件库太老旧了, 有许多功能都没有实现,还得自己去写逻辑(我绝不是为了偷懒), 并且混合使用了elementUI
- 后面想要部署一些别的东西, 如果强行用webpack, 我可能需要加班(一代人要有一代人的坚持)
这次动了哪些
webpack 3.x
-->vue cli 4.x(webpack4)
vue 2.5
-->vue 2.6
iview 2.x
-->iview 4.x
- 相关依赖升级和兼容处理等
升级流程(webpack -> vue cli)
00 创建一个新的vue项目
vue create xxx
然后把目录文件迁移到/src对应的目录中; 将对应的引入全局替换一下, 注意别替换错了.
先看看新旧前后目录结构对比:
/public里放上旧的html以及外部富文本组件(即/static);
/src/assets里放上旧的/common + /assets(旧/common里放的是字体和css,/assets放图片);
/store为/vuex的前身, 不用vuex当文件夹名, 总觉得这样同名不大好;
01 依赖
迁移完项目之后, 就要把使用到的依赖一一装上, 你要看下旧的依赖里有哪些是否还有有用, 那些没用的就别一股脑都搬过来啦.
我当时是把主要的依赖包安装好之后, 注释掉main.js
中诸如组件库这类不会影响项目的引用, 之后直接跑一下, 然后不出意外的一堆红艳艳. 处理完这些之后就是把main.js
中的引用逐个打开, 然后根据报错处理.
这里主要就是安装依赖, 升级降低依赖版本, 使其可以相互兼容, 不要打架, 根据报错提示处理即可
根据里面的报错去装, 然后大体遇上了比较深刻的几个问题如下(很多可能感觉是小问题就没有记录):
-
原先的babel依赖是长这样的
babel-...
, 而在vcli4中是这样的@babel/...
(当然不是全部都这样换,这个就要看你用的是哪种, 可以分别对应查一下). -
注意各个关联依赖之间的版本是否匹配, 有些版本不兼容的会报错, 尽量让他们一家人整整齐齐的吧.
-
遇到报错
this.getOptions...
啥啥的;- 我这边是因为
less-loader8
以上的版本要使用webpack5, 我把他降低版本到7.3.0
即可.
- 我这边是因为
-
Autoprefixer applies control comment to whole block, not to next rules.
- 这个是
Autoprefixer
注释出的问题, 参考这篇Autoprefixer报错解决方案; - 简单来说就是第二个
Autoprefixer
注释被忽略了,Autoprefixer
注释应该被用于整个代码块, 而不是下一条规则. 也就是/* autoprefixer: off */
在一个代码块中不可以再作用于/* autoprefixer: on */
; - 我删掉了所有的第二个全局注释(感觉也有可能是版本问题吧, 不过我没有试出哪个版本是刚好的, 就这样简单粗暴的处理了).
- 这个是
-
Error evaluating function rgba: color functions take numbers as parameters
- css的rgba里只设置了三个色值, 改成rgb即可, 原因未知, 猜测是某个css插件导致的;
- 还有css分离打包, 引用顺序冲突, 调换顺序, 或者是忽略配置即可.
-
TypeError: Cannot read property 'vue' of undefined
配置vue-loader的时候报错, 将vue-loader升级到最新包即可.
问题: 升级之后, 出现了findIndex找不到, 找了好久也没有找到解决办法, 就不在配置文件里设置了. 💡 猜测: - 其他依赖版本冲突 - 可能是vue-cli这个版本已经内置了,这边重复引入导致 - 也可能是我配置文件写错了(看了好几遍, 感觉没错, 不愿相信)
02 升级组件库和各种样式问题
iview2
不兼容更高版本的vue, 所以这件事还是得做.
升级组件库主要还是参考了官方的升级 4.x 指南;
-
文档说的很详细, 基本上就是换包, 切换引入, 安装
iview-loader
依赖, 以及其他额外需要处理的:- icon标签的类名都变了,需要一一改过去.
- 还有项目里有i标签有人用了
iview
的class名, 需要一个个改(这里最吐血)
附带在
vue.config.js
配置ivew-loader
:chainWebpack: config=>{ config.module .rule('vue') .test(/\.vue$/) .use('iview-loader') .loader('iview-loader') .tap(args => { return { prefix: false } }).end() }
-
之后就是引入全局的
less
文件-
里面一个书写规范的问题:
justify-content: start;
改为flex-start;
-
less
文件里的calc
计算异常问题:类似
calc(100vw - 66px);
会编译成calc(-xxvw)
, 原因是less-loader把它编译掉了, 改个写法, 用~""
把属性包裹起来, 就可以避免被编译:calc(~"100vw - 66px");
-
打包后生产环境的基础样式不会生效
在
package.json
中有一个配置sideEffects
会把认为带有副作用的东西处理掉, 比如css; 我不知道是它憨憨还是我憨憨, 于是我把css
,vue
,less
, 分别配置上去, 告诉它, 这些类型的文件我罩着!然后发现
iview
的样式在生产环境也不生效, 我累了, 我认输, 就干脆在生产环境里直接用cdn引入iview
; 然后这个渣渣还不向其他的一样, 非要先抛出iview
然后再抛出ViewUI
才生效:{ vue: 'Vue', vuex: 'Vuex', 'vue-router': 'VueRouter', axios: 'axios', "view-design": 'iview', // iview4要先抛出 iview , 再抛出 ViewUI "iview": "ViewUI" }
-
最后, 就是公共样式不生效或者是互相覆盖的问题, 盯了半天代码, 我把在
main
中引入的顺序改一下, 终于看起来正常的跑起来了, 我心甚慰.
-
-
还有一个ivew的form表单升级之后的校验比旧版的严格, 原先的不规范写法都要重新一个个找出来写规范点:
- 例如: FromItem组件里写了个prop, 然后这个prop里的内容并不在表单里, 可能是复制的时候忘记删了, 或者是别的原因, 导致表单会校验失败, 需要删掉或者改为正确的;
- 等等(主要是不记得了, 有些有记录还记得, 有些全忘了)
03 打包优化
🚨 搞优化的时候给我搞崩了一次, 差点心态崩了, 回退了好多代码, 欲哭无泪
-
生产环境中的
cdn
引入就不说啦, webpack有个externals
引入外部资源的, 贴一下代码(配置都在vue.config.js
文件中):const cdnMap = { js: [ CDN_URL + 'vue@2.6.14.min.js', CDN_URL + 'vue-router@3.2.min.js', CDN_URL + 'vuex@3.4.0.min.js', CDN_URL + 'axios@0.24.0.min.js', CDN_URL + 'iview@4.7.0.min.js', ] } const externals = { vue: 'Vue', vuex: 'Vuex', 'vue-router': 'VueRouter', axios: 'axios', "view-design": 'iview', // iview4要先抛出 iview , 再抛出 ViewUI "iview": "ViewUI" } module.exports = { chainWebpack: config =>{ if(IS_PRO){ config.externals(externals); config.plugin('html').tap(args => { args[0].cdn = cdnMap return args }); } } }
index.html <% if(process.env.NODE_ENV==='production' ){ %> <!-- 使用CDN加载的js --> <% htmlWebpackPlugin.options.cdn.js.forEach( function(item) { if(item) { %> <script type="text/javascript" src="<%= item %>"></script> <% }}) %> <% } %>
-
添加打包分析
npm install webpack-bundle-analyzer --save-dev
module.exports = { chainWebpack: config=>{ config.plugin('webpack-bundle-analyzer') .use(bundleAnalyzer.BundleAnalyzerPlugin, process.env.npm_config_report) } }
-
压缩JavaScript
npm install uglifyjs-webpack-plugin --save-dev
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin'); ... module.exports = { configureWebpack: config => { config.plugins.push( new UglifyjsWebpackPlugin({ uglifyOptions: { compress: { drop_debugger: true, drop_console: true, //生产环境自动删除console pure_funcs: ["console.log"] //移除console }, warnings: false, }, sourceMap: false, parallel: true, //使用多进程并行运行来提高构建速度 }) ) } }
-
按需拆分chunk, 减少模块重复依赖, 合并公用模块
CommonsChunkPlugin
是用来避免chunk之间的重复依赖, 不过从 webpack v4 开始,移除了CommonsChunkPlugin
,取而代之的是optimization.splitChunks
(这段话是官网抄来的, 看官方文档吧, 还是很详细的);module.exports = { configureWebpack: config => { config.plugins.push( new webpack.optimize.SplitChunksPlugin({ chunks: "async", minSize: 20000, // 允许新拆出 chunk 的最小体积,也是异步 chunk 公共模块的强制拆分体积 maxAsyncRequests: 30, // 按需加载时的最大并行请求数 maxInitialRequests: 30, // 入口点的最大并行请求数 automaticNameDelimiter: '~', cacheGroups: { // 缓存组 vendors: { name: `chunk-vendors`, test: /[\\/]node_modules[\\/]/, priority: 10, // 缓存组权重,数字越大优先级越高 chunks: 'initial' // 只处理初始 chunk }, common: { name: `chunk-common`, minChunks: 3, // common 组的模块必须至少被 3 个 chunk 共用 priority: 0, chunks: 'initial', // 只针对同步 chunk reuseExistingChunk: true // 复用已被拆出的依赖模块,而不是包含在该组中一起生成 } }, }) ) } }
-
开启GZIP压缩
npm install compression-webpack-plugin --save-dev
const CompressionWebpackPlugin = require("compression-webpack-plugin"); const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i; ... module.exports = { configureWebpack: config => { config.plugins.push( new CompressionWebpackPlugin({ asset: '[path].gz[query]', algorithm: 'gzip', test: productionGzipExtensions, threshold: 10240, // 对超过10K的进行压缩 minRatio: 0.8 // 压缩比例 }) ) } }
总结
一个有了几年历史的项目, 各种写法, 各种风格, 在追求能跑就行的原则上, 还是挺纠结要不要去搞它, 不过看着后面的大版本也会动大刀子, 那就顺势而为了;
总体来说最后结局还算圆满, 后期基本上都是一些琐碎的细节修改, 大部分是因为组件库升级的原因, 也可以看出原先组件库的各种用法和写法都不够严谨, 也借此改了一些历史遗留的bug等等. 还遗留了些许问题没有琢磨透, 后面再去琢磨琢磨看.
在这之前小小的整理过一次项目结结构, 后续的休整方向主要应该是偏向路由和公用模块的内容这一块, 现在的路由层级嵌套很深, 也有很多冗余代码, 暂时的想法是先剔除掉旧的那些么得用处的代码, 之后再对公用方法这一块做整理, 整个项目清晰点之后, 再去搞路由这一块, 最后再做一个整体的优化. 这样看来, 后续应该还有两小一大的动作. 暂时就这样吧, 留存记录.
我还是个小菜鸟, 上面如果有错误地方的话还望不吝赐教, 十分感谢!