最终鼓起勇气把公司五年的webpack老项目改成了vite

11,316 阅读8分钟
一.【 升级原因 】

  工程现状:

    1. 项目较早 + 代码量大 + 插件类库版本低 + 各个节点优化空间逐渐变小

    2. 为接下来Pro工程瘦身做准备;

  造成的困扰:

    PS:在以上问题的基础上,webpack已经变得非常不好用了

    开发环境服务的不稳定性,严重影响开发效率和心情;

    打包后输出文件过大,导致运行环境速度慢、系统性能低;

  改造目标:

    1. webpack 升级 为 vite;

    2. node版本调整;

    3. sass 版本升级;

    

二.【 改造思路 】

  版本库描述:

    1. 整体保持vue@2.5.10核心基础库不动

    2. 使用vite@2.8.0替换webpack@4.16.0

    3. NodeJs版本v14.19.1升级到v18.7.0(后因版本过高,降回原版本v14.19.1)

    4. Npm版本v6.14.16升级到v8.15.0

    5. 升级node-sass@7.0.3到node-sass@7.0.3(后因版本过高,降回原版本v4.7.2)

    6. 使用sass@1.55.0

    7. 升级sass-loader@13.1.0到sass-loader@7.0.3

    8. 引用vite-plugin-vue2@1.9.3使vue2支持vite

  思路描述:

    按需调整和配置,修改编译环境和运行环境的错误,最终经过测试阶段,代码推送线上

  分支保全方案:

    origin/dev    dev环境源代码

    origin/propre2  pre环境源代码

    origin/pro    线上环境源代码

    origin/config_vite_nyz_221108  vite配置测试分支

    origin/config_webpack_backup_nyz_221109  webpack配置备份分支(拉取自pro分支)

三.【 升级报告 】
类目webpack(升级前)vite(升级后)
本地服务启动时长230 秒3 秒
修改后热更新时长23 秒1 秒
本地服务性能更新慢 & 服务经常崩溃更新快 & 服务稳定
执行打包任务时长300秒55 秒
打包后输出文件大小37.0 MB19.8 MB
打包后输出文件个数1372 个787 个
依赖包优化后个数103 个82 个
运行环境兼容性验证----
四.【 执行过程 】
  1. 初始化一个vite项目
// 注意:这里vite的版本采用2.8.0的,最新的版本创建后续会出现问题 
// 要选择vanilla原生项目 , 因为选择vue会默认走vue3,造成与当前版本不匹配
 npm init vite@2.8.0
  1. 将原项目的src文件夹和其他用到的文件/文件夹移入新项目
  1. 合并package.json的依赖项,script命令抛弃webpack,保留vite的两条指令
"scripts": {
    "serve": "cross-env NODE_ENV=development vite",
    "build": "cross-env NODE_ENV=production vite build"
  },
  1. 删除外层初始化的main.js,移入或保留原项目的main.js到src下
  1. index.html修改入口引入方式

    <script type="module" src="src/main.js"></script>
    
  1. main.js修改根的挂载方式

    // // 原代码
    // new Vue({
    //     el: '#app',
    //     router,
    //     store,
    //     render: h => h(App)
    // })
    
    // 目标代码
    new Vue({
      router,
      store,
      render: h => h(App)
    }).$mount('#app')
    
  1. 使用cnpm快速下载相关依赖包

    cnpm install
    
  1. NodeJs版本不一致,导致[node-sass]找不到并报错,直接拉取latest版本

    cnpm install --save-dev sass sass-loader node-sass
    
  1. 启动本地服务报错:require is not defined

    原因:vite默认使用es6标准的 import 的导入方式,不支持require引入

    // 下载
    cnpm install vite-plugin-require-transform
    
    // vite.config.js 引入
    import requireTransform from 'vite-plugin-require-transform'
    // vite.config.js 使用
    defineConfig({
      plugins: [
        createVuePlugin(),
        requireTransform({
          fileRegex: /.vue$|.js$|.json$|.ts$|.tsx$/
        })
      ]
    })
    
  1. 服务编译的时候,头部注释信息报错 , 如下图,

    原因:语法编译不通过

    解决:涉及文件较多,全部找出来注掉就可以了

  1. 编译报错:Failed to fetch dynamically imported module

    原因:vue文件引入错误,构建过程无法补全文件后缀并读取

    解决:把vue和js相关引入代码全部加上后缀 ( 涉及代码较多,找出来全部改掉 )

  1. 样式的deep语法报错,找出来全部替换掉

    /deep/ 
    替换为 
    ::v-deep
    
  1. 编译报错:process is not defined

    // vite.config.js
    define: {
      'process.env': {} 
    }
    
  1. 项目中使用的环境变量报错

    解决:webpack升级vite后,需要创建环境变量文件并使用vite的环境变量

    process.env // webpack
    import.meta.env // vite
    
  1. 编译报错:Module “path” has been externalized for brower compatibility and cannot be accesed in client code

    原因:path模块无法在客户端代码中使用 , webpack自动帮我们处理了node内置模块 , vite没有

    解决:安装 path-browserify 代替 path 模块

    cnpm install path-browserify
    
    import path from 'path' 
    改为 
    import path from 'path-browserify'
    
  1. require兼容配置,某些三方包中的require语法,在开发环境正常,打包环境报错

    // vite.config.js
    build:{
         commonjsOptions: {
             transformMixedEsModules: true
         }
    }
    
  1. 打包报错:[commonjs] Identifier '_vite_plugin_require_transform_Component' has already

    原因:错误所指的意思是echarts 库中声明的变量已存在,还有再声明的必要吗?

    解决:实则该变量并没有重复声明,所以改动CDN引入的方案,并删掉页面import引入语句

    注意:CDN要引入对应的版本

    jsdelivr相关库文件的CDN地址:www.jsdelivr.com/package/npm…

    // echart主入口和单独引入的功能 改为cdn引入
    // index.html 引入以下代码
    <script src="https://cdn.jsdelivr.net/npm/echarts@3.8.5/dist/echarts.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/echarts@3.8.5/map/js/china.js"></script>
    
  1. 打包报错:'default' is not exported by node_modules/_core-js@2.6.12@core-js

    解决:rollup本身是不支持CommonJS的,使用了这个插件,就可以解析CommonJS模块了

    rollup.js相关配置资料:rollupjs.org/guide/en/#o…

    cnpm install @rollup/plugin-commonjs -D
    
    // vite.config.js
    import commonjs from '@rollup/plugin-commonjs';
    plugins: [
      commonjs()
    ]
    
  1. 打包报错:Error: 'default' is not exported by node_modules/_throttle-debounce@1.1.0

    原因:该代码中的防抖节流方法没有默认抛出

    解决:使用自定义封装方法或工具库平替即可

  1. 打包成功后部署至测试环境:Uncaught TypeError: Illegal invocation,报错如下图

    原因:查看报错位置,可能是因为代码编译转换过程产生的错误

    解决:故调整库版本和引入方式

    1. es6-promise 引入方式调整
    
    // require引入方式去掉
    require('es6-promise').polyfill() 
    
    改为
    
    // index.html 引入
    <script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.min.js"></script
    
    2. @rollup/plugin-commonjs 该库版本降级调整
    
    @23.0.2   降至    @13.0.2
    
  1. 测试环境引入文件404,如图

    原因:实际上这个文件是存在的,但它的文件名携带%,导致get的时候被浏览器转译丢失

    查找:因为 style标签中的 rel="stylesheet/scss" 导致 [/] 转为[%2F] 从而报错

    解决:干掉报错部分的这段原代码,重新编译

  2. 路径别名配置平替webpack开发环境和打包环境用到的@

    // vite.config.js
    resolve: { // 路径相关配置项
        alias: [ // 文件别名配置
            {
              find: '@',
              replacement: path.resolve(__dirname, 'src')
            }
        ]
    },
    
  1. 忽略扩展名配置,本项目文件后缀已手动补全,此项配置用作兜底方案

    官方提示:注意,不建议忽略自定义导入类型的扩展名 (例如: .vue) ,因为它会影响 IDE 和类型支持

    // vite.config.js
    resolve: {
          extensions: [ // 导入时想要省略的扩展名列表。
            '.vue',
            '.js'
          ],
    },
    
  1. 为保证项目在浏览器的兼容性,打包目标选择最低

    // vite.config.js
     build: { // 打包相关配置
          target: 'es2015'
    }
    
  2. package.json文件中无用的库全部去掉,可以优化项目体积和编译速度

  3. 可选优化升级:配置Gzip压缩

    cnpm install vite-plugin-compression -D
    
    // vite.config.js
    import viteCompression from 'vite-plugin-compression'
    
    plugins: [
        viteCompression({
            verbose: true,
            disable: false,
            threshold: 10240, // 阈值
            algorithm: 'gzip',
            ext: '.gz',
        })
    ]
    
  4. 可选优化升级:提升vite项目的浏览器兼容性

    cnpm install @vitejs/plugin-legacy -D
    
    // vite.config.js
    import legacyPlugin from '@vitejs/plugin-legacy'
    
    plugins: [
        legacyPlugin({
            targets: ['chrome 52'], // 需要兼容的目标列表,可以设置多个
            additionalLegacyPolyfills: ['regenerator-runtime/runtime'] // 面向IE11时需要此插件
            renderLegacyChunks: true,
            polyfills: [
              'es.symbol',
              'es.array.filter',
              'es.promise',
              'es.promise.finally',
              'es/map',
              'es/set',
              'es.array.for-each',
              'es.object.define-properties',
              'es.object.define-property',
              'es.object.get-own-property-descriptor',
              'es.object.get-own-property-descriptors',
              'es.object.keys',
              'es.object.to-string',
              'web.dom-collections.for-each',
              'esnext.global-this',
              'esnext.string.match-all'
            ]
        })
    ]
    
  5. 最终目录结构

    PS:

    1. 删掉原项目webpack相关配置:* /build** & */config

    2. 新增:/public

    3. 原项目的 /static 文件夹下内容移至 /public 下

    4. 根目录新建:vite.config.js & .env.development & .env.production

  6. 测试发现:城市地图模块的动态导入数据运行和打包报错

    原因:vite运行环境和编译环境都会抛出错误,因为此写法不被支持

    // 报错的代码: import(``)
    function showProvince(eName, param) {
            import(`@/utils/map/province/${eName}.json`).then(res => {
              const data = JSON.parse(JSON.stringify(res))
              that.$nextTick(() => {
                echarts.registerMap(param, data)
                initEcharts(param)
              })
            })
          }
    

    解决:采用请求的方式动态获取城市地图的轻量数据,并在nginx配置支持json文件的get获取

    function showProvince(eName, param) {
            axios.get(`/map/province/${eName}.json`).then(res => {
              if (res.status === 200 && res.data) {
                const data = JSON.parse(JSON.stringify(res.data))
                that.$nextTick(() => {
                  echarts.registerMap(param, data)
                  initEcharts(param)
                })
              }
            })
          }
    
  1. 测试项目依赖包的install和build发现:node-sass下载会报错,打包会任务循环无法

    解决方案:因为node和node-sass版本不匹配

    node降至v14.19.1

    node-sass降至@4.7.2

  2. 对于vite打包的项目进行兼容性测试(重要)

    (1)通过CanIUse查看API各个浏览器兼容性

    地址参考:caniuse.com/es6-module-…

    图片参考:

 (2)核心关注使用率较高的几款PC端浏览器

  图片参考: 

 

 (3)根据官方给出的Api兼容表,验证浏览器的兼容性

  参考内容:可以使用比较成熟的几款云端虚拟机自动化测试工具,这里用的是BrowserStack

  参考地址:www.browserstack.com/

  测试步骤1:选取浏览器和版本,这里对照上面的表格针对性测试几款浏览器的边界值版本

  测试步骤2:拿到虚拟机跑出来的页面访问结果

  测试步骤3:查看结果,白页为未访问到项目,最终与CanIUse给出的兼容表一致

 (4)根据网站header入库的 navigator.userAgent 字段分析用户浏览器使用分布

  字段参考:(容易区分的部分高亮)

    IE6.0:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)

    IE7.0:Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)

    IE8.0:Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)

    IE9.0:Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1)

    IE10:Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.3; WOW64; Trident/6.0)      IE11:Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko

    Chrome:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36            (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36

    firefox:Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0

    Opera:Opera/9.80 (Windows NT 5.1; U; zh-cn) Presto/2.9.168 Version/11.50

    360浏览器:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; 360se)

  1. 测试打包流程,发现vite2和@vitejs/plugin-legacy 库的@2版本不匹配

    解决方案:降级@vitejs/plugin-legacy这个包@2版本到@1.8.2,即可

    // vite.config.js 
    import legacyPlugin from '@vitejs/plugin-legacy' // IE和旧版chrome兼容 
    legacyPlugin({ 
        targets : [ 'ie >= 11' ] , 
        additionalLegacyPolyfills : [ 'regenerator-runtime/runtime' ] 
    })
    
  1. 兼容性测试结果

    主流(支持):Chrome、Firefox、Safari、Edge

    国内(支持):QQ浏览器、搜狗浏览器、猎豹浏览器、360浏览器

image.png