从 VueCLI 迁移到Rsbuild:性能直线上升🚀

我为什么要迁移到Rsbuild?

我们部门开发的产品构建工具都是@vue/cli-service4.5以下的版本,且使用babel处理低版本浏览器兼容性问题,项目又很庞大,每次构建都需要5分钟左右。这月项目不是很忙,决定对本部门产品构建工具进行升级!

在决定升级后对比了Vite和Rsbuild:

  1. 部门的项目兼容IE10、11 切换到Vite后很难处理
  2. Rsbuild 兼容了大部分webpack配置和插件,使用SWC兼容低版本浏览器,编译速度快。

迁移第一步:删除所有vuecli和babel相关插件

NodeV12切换为NodeV18Node.js >= 16都可以)

js 复制代码
nvm use 18

删除node_modulespackage.lock.json以及babel.config.js

js 复制代码
rimraf node_modules package.lock.json babel.config.js

删除package.json中所有devDependencies依赖(也可以先只删除vuecli和babel相关依赖,根据自己项目而定)。 我的项目构建比较早,随着版本迭代引入了很多不必要的插件,全部删除后瞬间清爽了很多。

js 复制代码
"devDependencies": {
    "@babel/polyfill": "^7.12.1",
    "@babel/core": "^7.18.2",
    "@vue/cli-plugin-babel": "~4.5.17",
    "@vue/cli-plugin-eslint": "~4.5.17",
    "@vue/cli-plugin-router": "~4.5.17",
    "@vue/cli-plugin-vuex": "~4.5.17",
    "@vue/cli-service": "~4.5.17",
    "babel-eslint": "^10.1.0",
    "babel-plugin-import": "^1.13.5",
    "copy-webpack-plugin": "^5.1.1",
    "eslint": "^7.32.0",
    "eslint-config-prettier": "^7.0.0",
     ...
    "less": "^3.13.1",
    "less-loader": "^6.2.0",
    "prettier": "^2.6.2",
    ....
  }

迁移第二步:使用Rsbuild构建新项目

因为考虑到项目文件有点多(单路由文件超180),直接修改package.json再重新拉依赖会有很多报错,所以还是决定新开一个项目,再把依赖、源代码慢慢挪过来。

js 复制代码
npx create-rsbuild --dir rsbuild-jsas --template vue2-js

这样构建出一个比较干净的项目后,把自己项目的package.json中的依赖复制过来,执行npm install。然后启动项目查看是否报错。

迁移第三步:源码迁移

成功构建好项目后,先迁移公共组件代码,vuexrouteraxios等,运行没有报错后再进行业务代码迁移。这一步大部分项目运行时会报错。

/deep/修改为::v-deep

vue升级后不再支持/deep/样式穿透,统一改为::v-deep

修改导入扩展名

当我们导入.vue文件时,不需要具体到文件的后缀系统就能识别到文件,这是因为vuecli构建的项目自动帮我们添加了.vue后缀。但是Rsbuild构建的项目需要我们手动添加,在rsbuild.config.mjs文件中添加代码。
注意:resolve.extensions今添加常用的文件即可,配置的过多会导致性能问题

js 复制代码
...
  resolve: {
    ...
    extensions: ['.js', '.jsx', '.vue']
  },
  ...

环境变量

Vue CLI 默认会将 VUE_APP_ 开头的环境变量注入到 client 代码中,而 Rsbuild 默认会注入 PUBLIC_ 开头的环境变量。为了兼容 Vue CLI 的行为,你可以手动调用 Rsbuild 提供的 loadEnv 方法来读取 VUE_APP_ 开头的环境变量,并通过 source.define 配置项注入到 client 代码中。

js 复制代码
import { defineConfig, loadEnv } from '@rsbuild/core';

const { publicVars } = loadEnv({ prefixes: ['VUE_APP_'] });

export default defineConfig({
  source: {
    define: publicVars,
  },
});

处理源码中的NodeJs模块

以前在源码中使用NodeJs模块,打包后在浏览器环境下依然正常运行,是因为webpack帮我们做了处理,自动替换为浏览器相关API,但是Rsbuild需要额外引入依赖Node Polyfill 插件,并添加如下配置

js 复制代码
import { pluginNodePolyfill } from "@rsbuild/plugin-node-polyfill";

export default defineConfig({
  plugins: [pluginNodePolyfill()],
});

我项目使用NodeJs模块的地方不多,所以我直接把相关代码直接用浏览器兼容的API重写了,就没安装这个包。

指定HTML模版

Vue CLI 默认使用 public/index.html 文件作为 HTML 模板。在 Rsbuild 中,你可以通过 html.template 来指定 HTML 模板:

js 复制代码
export default defineConfig({
  html: {
    template: './public/index.html',
  },
});

按需引入组件

之前项目使用了ant-design-vue的一个组件,在Rsbuild中怎么可以按需引入这个组件呢?可以参考这个配置:

js 复制代码
export default defineConfig({
  source: {
    transformImport: [
      { libraryName: "ant-design-vue", libraryDirectory: "es", style: true },
    ],
  },
});

全局注册less变量

js 复制代码
  plugins: [
       ...
    pluginLess({
      lessLoaderOptions: {
        lessOptions: {
          // additionalData: `@import "@/styles/less/variables.less`
          globalVars: {
            'primary-color': '#0096f7',
            'boder-color': '#dfdfdf',
            'warning-color': '#ffcc00'
          }
        }
      }
    }),
  ],

自定义拆包策略

Rsbuild内置了一些拆包策略,想要一些自定义配置可以使用chunkSplit.override配置。不推荐使用## chunkSplit.strategy: custom,除非你非常清楚你要每个包应该怎么拆分。
chunkSplit.override会根据split-by-experience模式下自定义策略。 这里我把echarts单独拆分。

js 复制代码
  performance: {
    removeConsole: true,
    chunkSplit: {
      override: {
        cacheGroups: {
          echarts: {
            test: /node_modules[\\/]echarts[\\/]/,
            name: 'lib-echarts',
            chunks: 'all'
          },
        }
      }
    }
    // 与构建性能、运行时性能有关的选项
  },

兼容IE浏览器需要做哪些处理

项目要求兼容IE11,node_modules下很多包交付的代码并不支持IE,所以我们需要额外编辑node_modules下的依赖(默认是不编译的)。

这样会编译node_modules下除了core-js以外的所有依赖。

js 复制代码
  source: {
    ...
    include: [{ not: /[\\/]core-js[\\/]/ }],  //swc编译所有的js文件 排除node_moduled下的core.js 二次编译会导致运行时问题
  },

最后别忘了修改.browserslistrc

js 复制代码
[production]
> 0.5%
not dead
IE 11



[development]
chrome >= 87
edge >= 88
firefox >= 78
safari >= 14

js文件中导出jsx代码编译报错怎么解决

我在js文件中写了jsx代码,编译时报错,此时不得不安装babel插件了。

js 复制代码
import { pluginBabel } from '@rsbuild/plugin-babel';
  plugins: [
    pluginBabel({
      include: /\.TooltipTool\.js$/,
      // 建议 exclude node_modules 目录以提升构建性能
      exclude: /[\\/]node_modules[\\/]/,
    }),
    ...
  ],

总结

以上就是项目迁移的整个过程,附上迁移前后性能提升对比。
项目启动时间由4分29秒缩减为35.8秒,提升7.5倍。

打包时间由8分40秒缩减至1分1.78秒,速度提升了约8.42倍。

首页优化: 进入系统首页原来共发起了552项请求,共36.8MB加载时间26.16秒。Rsbuild打包后,则发起了106项请求,共13.4MB,加载时间为4.24秒。

最后,附上完整配置以供大家参考。

js 复制代码
const path = require('path');
import { defineConfig, loadEnv } from '@rsbuild/core';
import { pluginVue2 } from '@rsbuild/plugin-vue2';
import { pluginLess } from '@rsbuild/plugin-less';
import { pluginVue2Jsx } from '@rsbuild/plugin-vue2-jsx';
import { pluginBabel } from '@rsbuild/plugin-babel';
import { pluginBasicSsl } from '@rsbuild/plugin-basic-ssl';
import { RsdoctorRspackPlugin } from '@rsdoctor/rspack-plugin';

const { publicVars: vueEnvs } = loadEnv({ prefixes: ['VUE_APP_'] });
const resolve = dir => {
  return path.join(__dirname, dir);
};
export default defineConfig({
  plugins: [
    pluginBabel({
      include: /\.TooltipTool\.js$/,
      // 建议 exclude node_modules 目录以提升构建性能
      exclude: /[\\/]node_modules[\\/]/,
    }),
    pluginVue2({
      splitChunks: {
        vue: true,
        router: true
      }
    }),
    pluginVue2Jsx(),
    pluginLess({
      lessLoaderOptions: {
        lessOptions: {
          // additionalData: `@import "@/styles/less/variables.less`
          globalVars: {
            'primary-color': '#0096f7',
            'boder-color': '#dfdfdf',
            'warning-color': '#ffcc00'
          }
        }
      }
    }),
    pluginBasicSsl()
  ],
  server: {
    port: 9529,
    // 跨域代理
    proxy: {
      '/api': {
        target: 'https://10.0.100.235',
        secure: false,
        // target: 'https://10.0.100.101',
        changeOrigin: true,
        //重点在下面,下面这个把本地调试403问题解决掉了
        onProxyReq: proxyReq => {
          proxyReq.removeHeader('referer'); //移除请求头
          proxyReq.removeHeader('origin'); //移除请求头
        }
      }
    },
    // 移除请求报错全屏显示错误
    client: {
      overlay: false
    }
  },
  resolve: {
    alias: {
      '@': './src'
    },
    extensions: ['.js', '.jsx', '.vue','.less', '.css']
  },
  // 与源代码解析、编译方式相关的选项
  source: {
    entry: {
      index: './src/main.js'
    },

    define: {
      ...vueEnvs
    },
    include: [{ not: /[\\/]core-js[\\/]/ }],  //swc编译所有的js文件 排除node_moduled下的core.js 二次编译会导致运行时问题
  },
  // 与构建产物有关的选项
  output: {
    legalComments:  'none',
    polyfill: 'entry',
    copy: [
      {
        from: resolve('./static'),
        to: resolve('./dist/static')
      }
    ],
    distPath: {
      root: 'dist'
    },  
    sourceMap: {
      js: process.env.NODE_ENV == 'development' ? 'cheap-module-source-map' : false,
      css: true ,
    },
  },

  tools: {
    bundlerChain: (chain, { CHAIN_ID }) => {
      if(process.env.RSDOCTOR){
        chain.plugin('Rsdoctor').use(RsdoctorRspackPlugin, [
          {
            // 插件选项
            disableClientServer: false,
            features: ['lite', 'loader', 'plugins', 'bundle'],
          }
        ]);
      }
    }
  },
  // 与 HTML 生成有关的选项
  html: {
    template: './public/index.html'
  },
  performance: {
    removeConsole: true,
    chunkSplit: {
      override: {
        cacheGroups: {
          echarts: {
            test: /node_modules[\\/]echarts[\\/]/,
            name: 'lib-echarts',
            chunks: 'all'
          },
        }
      }
    }
  }
});
相关推荐
拾光拾趣录1 天前
JavaScript压缩原理与手写实现
前端·javascript·前端工程化
Dolphin_海豚2 天前
一文理清 node.js 模块查找策略
javascript·后端·前端工程化
拾光拾趣录4 天前
组件封装的⼀些基本准则
前端·前端工程化
拾光拾趣录6 天前
前端代码保护:防止网页调试
前端·前端工程化
陳有味_ChenUvi7 天前
使用 pnpm 优雅搭建 Monorepo 仓库
前端·npm·前端工程化
若梦plus8 天前
基于Rust的前端工具链重构
前端·前端工程化
止观止8 天前
深入探索 pnpm:高效磁盘利用与灵活的包管理解决方案
前端·pnpm·前端工程化·包管理器
拾光拾趣录11 天前
从0到1:搭建企业级前端基础建设
前端·前端工程化
拾光拾趣录12 天前
Webpack 打包中的 Hash 生成机制
前端·webpack·前端工程化