React从webpack迁移到rsbuild 纪实

Why

随着团队项目规模越来越大之后,继从babel-loader迁移到esbuild之后发现打包、热重载性能随着时间迭代之后又慢慢开始成为性能瓶颈,所以决定用新的打包工具去解决这个问题。esbuild 自然成为了不二之选。

What esbuild 是什么

esbuild 是基于espack 的一款打包工具,借助于rust的性能加成在前端应用打包领域取得了非比寻常的进步,以下是他相较于其他打包工具的对比:

Benchmark results from build-tools-performance.

相较于webpackespack 有以下几个特性:

  • rust的性能加持: Webpack的性能经常被其竞争对手挑战,特别是在大型项目中。Rspack使用Rust语言解决了这个问题,Rust语言专门设计来优先考虑性能,在速度和内存管理方面都在benchmarks的前列。Rust还提供了许多编译器保护措施,以避免其他原生语言(如c++)的常见陷阱。
  • 高度并行的架构: webpack 受限于 JavaScript 对多线程的羸弱支持,导致其很难进行高度的并行化计算,而得益于 Rust 语言的并行化的良好支持, Rspack 采用了高度并行化的架构,如模块图生成,代码生成等阶段,都是采用多线程并行执行,这使得其编译性能随着 CPU 核心数的增长而增长,充分挖掘 CPU 的多核优势。
  • 内置大部分的功能: 事实上 webpack 本身的性能足够高效,但是因为 webpack 本身内置了较少的功能,这使得我们在使用 webpack 做现代 Web App 开发时,通常需要配合很多的 plugin 和 loader 进行使用,而这些 loader 和 plugin 往往是性能的瓶颈,而 Rspack 虽然支持 loader 和 plugin,但是保证绝大部分常用功能都内置在 Rspack 内,从而减小 JS plugin | loader 导致的低性能和通信开销问题。
  • 增量编译: 尽管 Rspack 的全量编译足够高效,但是当项目庞大时,全量的编译仍然难以满足 HMR 的性能要求,因此在 HMR 阶段,我们采用的是更为高效的增量编译策略,从而保证,无论你的项目多大,其 HMR 的开销总是控制在合理范围内。

How 我们怎么做的

webpack项目迁移到rsbuild并不难, 甚至非常简单,我们是存量项目迁移 typescript less 是基础语言,因此通过以下几步我们就完成了关键迁移:

  • 如果直接一步步集成各种plugin跟构建工具,踩的坑肯定很多,所以我们是这么做的 先用npm create rsbuild@latest 创建了一个全新的项目
  • 然后把对应依赖的包跟rsbuild config 从上一个新项目复制到自己项目中
    • 一般是 rsbuild.config.ts
    • @rsbuild/core @rsbuild/plugin-react 这两核心包
  • 下载依赖,这一步要把自己项目内的css预处理语言对应的插件添加上我们项目是less因此添加了@rsbuild/plugin-less
  • 配置html
  • 配置代理
  • 兼容其他webpac配置
    • alias
    • define
    • entry等等

然后分享一个坑是某个文件less引用了一个图片 使用的是相对路径地址,结果导致打包工具解析不到正确的路径,从而在整个打包进程一直卡在某个地方卡着,也没错误提示,最后解决方法就是使用别名路径配置了地址,就解决了。

定位问题的时候,这个地方我们是用排除法去定位的,就是首先把引用的文件全注释掉,然后一一放开 发现哪个文件放开的时候 打包有问题就去排查这个文件,不断重复此过程就能定位到最终问题发生的文件。

最后放个大体配置文件, 注意不要直接复制

typescript 复制代码
export default defineConfig({
  html: {
    template: './src/index.html',
    inject: 'body',
    templateParameters: {
      removeConsole: true,
    }
  },
  source: {
    entry: {
      index: './src/index.tsx',
    },
    define: {
      
    },
  },
  resolve: {
    alias: {
      '@': '/src',
    },
  },
  output: {
    copy: [{}],
    cssModules: {},
  },
  dev: {
    progressBar: true,
  },
  plugins: [
    pluginReact({
      fastRefresh: true,
    }),
    pluginLess({
      lessLoaderOptions: {
        lessOptions: {
          javascriptEnabled: true,
        },
      },
    }),
  ],
  server: {
    port: 3000,
    proxy: {
      [PROXY_URL]: {
        target: `https://${headers.Host}`,
        secure: false,
        changeOrigin: true,
        pathRewrite: {[`^${PROXY_URL}`]: ''},
        headers,
      }
    },
  },
  tools: {

  },
});

嗯 大体就是这样了。完结。