vue项目从webpack3.x升级vue-cli4

2,054 阅读5分钟

背景:

这个项目已经有好几年的岁月沉淀了, 使用的是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对应的目录中; 将对应的引入全局替换一下, 注意别替换错了.

先看看新旧前后目录结构对比:

新旧目录结构对比.png

/public里放上旧的html以及外部富文本组件(即/static);
/src/assets里放上旧的/common + /assets(旧/common里放的是字体和css,/assets放图片);
/store为/vuex的前身, 不用vuex当文件夹名, 总觉得这样同名不大好;

01 依赖

迁移完项目之后, 就要把使用到的依赖一一装上, 你要看下旧的依赖里有哪些是否还有有用, 那些没用的就别一股脑都搬过来啦.

我当时是把主要的依赖包安装好之后, 注释掉main.js中诸如组件库这类不会影响项目的引用, 之后直接跑一下, 然后不出意外的一堆红艳艳. 处理完这些之后就是把main.js中的引用逐个打开, 然后根据报错处理.

这里主要就是安装依赖, 升级降低依赖版本, 使其可以相互兼容, 不要打架, 根据报错提示处理即可

根据里面的报错去装, 然后大体遇上了比较深刻的几个问题如下(很多可能感觉是小问题就没有记录):

  1. 原先的babel依赖是长这样的babel-..., 而在vcli4中是这样的@babel/...(当然不是全部都这样换,这个就要看你用的是哪种, 可以分别对应查一下).

  2. 注意各个关联依赖之间的版本是否匹配, 有些版本不兼容的会报错, 尽量让他们一家人整整齐齐的吧.

  3. 遇到报错this.getOptions...啥啥的;

    • 我这边是因为less-loader8以上的版本要使用webpack5, 我把他降低版本到7.3.0即可.
  4. Autoprefixer applies control comment to whole block, not to next rules.

    • 这个是Autoprefixer注释出的问题, 参考这篇Autoprefixer报错解决方案;
    • 简单来说就是第二个Autoprefixer注释被忽略了, Autoprefixer注释应该被用于整个代码块, 而不是下一条规则. 也就是/* autoprefixer: off */ 在一个代码块中不可以再作用于/* autoprefixer: on */;
    • 我删掉了所有的第二个全局注释(感觉也有可能是版本问题吧, 不过我没有试出哪个版本是刚好的, 就这样简单粗暴的处理了).
  5. Error evaluating function rgba: color functions take numbers as parameters

    • css的rgba里只设置了三个色值, 改成rgb即可, 原因未知, 猜测是某个css插件导致的;
    • 还有css分离打包, 引用顺序冲突, 调换顺序, 或者是忽略配置即可.
  6. 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文件

    1. 里面一个书写规范的问题: justify-content: start;改为flex-start;

    2. less文件里的calc计算异常问题:

      类似calc(100vw - 66px);会编译成calc(-xxvw), 原因是less-loader把它编译掉了, 改个写法, 用~""把属性包裹起来, 就可以避免被编译: calc(~"100vw - 66px");

    3. 打包后生产环境的基础样式不会生效

      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"
      }
      
    4. 最后, 就是公共样式不生效或者是互相覆盖的问题, 盯了半天代码, 我把在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等等. 还遗留了些许问题没有琢磨透, 后面再去琢磨琢磨看.

在这之前小小的整理过一次项目结结构, 后续的休整方向主要应该是偏向路由和公用模块的内容这一块, 现在的路由层级嵌套很深, 也有很多冗余代码, 暂时的想法是先剔除掉旧的那些么得用处的代码, 之后再对公用方法这一块做整理, 整个项目清晰点之后, 再去搞路由这一块, 最后再做一个整体的优化. 这样看来, 后续应该还有两小一大的动作. 暂时就这样吧, 留存记录.

我还是个小菜鸟, 上面如果有错误地方的话还望不吝赐教, 十分感谢!