# rspack 使用体验过程

## why

首先说下为什么要尝试迁移，主要有几个原因

* webpack 使用非常慢
* rust 学习和尝试
* 纯粹好奇，我的项目升级以后到底能快多少？

构建更快的方案，其实是目前前端卷的最多的一个方向。一方面，基于 rust 重写各种工具，似乎成为一种有趣的挑战，webpack 是非常复杂的工具，这是一个具有挑战性的任务，所以大家愿意尝试。另外也是，更快有这更好的开发体验，同时更快的部署速度，出现 bug 的时候，能够更快部署上线，这对于很大很复杂的项目会更加重要。

## 结论

从 rust pack 的官网，我们可以看到对比数据，大概是 10x 的速度提升。不过，在我们的项目中，我一开始测试，确实大概是有 10x 的提速，从 40s 减少到 4s 。不过后面 wukun 群里提出的那个问题 ts 类型检测的问题，后面开启异步类型校验后时间效果大概如下

|     | webpack |     | rspack |     |
|-----|---------|-----|--------|-----|
| 项目  | 启动耗时    | 更新耗时 | 启动耗时   | 更新耗时 |
| datago | 16s     | 800ms | 4s     | 300ms |
| flink | 40s     | 800ms | 8s     | 1800ms |

大部分情况，rspack 是有一定的提效，但是没有 10x 那么高，大概是 4-5 倍提速，但是在 flink 项目中，更新耗时更长了，这有点奇怪。 当然这是我们目前的使用数据，具体原因还可以继续探索。因为 rspack 官网的对比项目配置也是关闭了 ts 类型检查的，具体原因后面可以继续对比探索。

## 选择 rspack 的原因

### 历史

在很早之前，没有 webpack 的时代，我们是不需要打包工具的。当时的方案，通过 require.js sea.js 之类的工具来动态加载模块。线上通过 cdn 的自动把文件打包合并成一个文件，比如 `a.cdn.com/?a.js,b.js,c.js`这样就可以拿到 a b c 三个文件。 但是在此之前，webpack 的出现还是非常重要的。webpack 的诞生其实挺简单的，当时的谷歌有一个 Java 版本的构建工具 Google Web Toolkit ，有一个叫做 code split 的特性。然后 sokra 很喜欢，但是他不喜欢 Java，当时找到一个类似的构建工具，叫做 webmake ，这个可能想做 web 版本的 make 。 然后，sokra 尝试给这个工具增加了 code split 的支持，然后他发现基本上把这个工具重写了，所以 webpack 诞生了。具体细节可以看看早期的 sokra 的[分享](http://sokra.github.io/slides/webpack#27)。

### 特色

我理解任何一种工具都需要有自己的特色所在，webpack 诞生于对于 code split 特性的实现。 webpack 之外，现在比较流行的是 vite ，vite 的方案其实和最早的方案类似的，只是随着浏览器的发展，现在主流浏览器都已经默认支持 module ，也就是可以直接 import 模块了。 虽然 webpack 已经流行很多年，但项目越来越大，webpack 越来越慢的问题遭到很多人不满。实际上，我们的项目还不算特别大吧，我知道有些项目启动要 2 分钟。flink 在我们的项目中已经算很慢的了，估计这和我们把很多公共依赖提出来有关，这导致 module 数量少了很多。 针对 webpack 慢的问题，主要有两种方案提出

* 基于新的语言，比如 rust、go 重写整套体系
* vite

当然，重写整套体系成本很高，很多人会担心一些兼容问题。这一套方案下有非常多的工具，比较火的是

* esbuild：基于 go 重写
* turbopack：这应该是第一个基于 rust 的实现

turbopack 背后最核心的代码生成方案是 swc 实现的，swc 是 rust 版本的 babel 。在 turbopack 以后，rspack 诞生了，rspack 的特色在于，他基本兼容 webpack 的，大部分 webpack 都是可以在 rspack 上跑的。 可以说，rspack 算是 webpack 的 rust 版本，并且他底层的插件机制保留了 webpack 老的模式。rspack 的插件体系和 webpack 一样基于 tapable 实现。 所以，我感觉选择 rspack 迁移应该是会最简单，并且兼容性应该也是最好的。

## 实现

我们使用都是通过 rspack 这个命令行工具来执行的，然后是 npm 包 rspack/core ，主要做参数透传以及 webpack 生命周期兼容相关逻辑。最终通过 rspack/binding 这个包来把负责启动 rust 包，rust 最终会打包成一个 node 的 [native module](https://napi.rs/docs/deep-dive/native-module)，就是一个动态链接库，这样 rust 所有代码打包成一个文件，可以直接通过 v8 加载到内存，然后可以调用其中的方法。这里是通过 [napi.rs](https://napi.rs/) 来打包的，node 的 native module 发展也经历过非常多次迭代升级，最终到目前的 napi 模式，可以一个文件运行于多个不同的 node 版本了，细节可以参考这里的文章。  ![](https://iling.fun/api/files.get?sig=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ1cGxvYWRzL2MyMzFmODM0LTY3ZGQtNGJjZi1iM2RmLWYxMTQ2NGUxM2Y0MS9jNTU0NmUwOS1iNjMzLTRlZDgtOWNkMC1lNGVlMzZlZTFiODMvaW1hZ2UiLCJ0eXBlIjoiYXR0YWNobWVudCIsImlhdCI6MTc5MDQzODQwMCwiZXhwIjoxNzkwNTI0ODAwfQ.olWOzebUVGWWF04-ik2jPsdTnwNg0DYKYk-DQFD-1nM)  ![image.png](https://iling.fun/api/files.get?sig=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ1cGxvYWRzL2MyMzFmODM0LTY3ZGQtNGJjZi1iM2RmLWYxMTQ2NGUxM2Y0MS8xODFmMDE1OC0wZjMyLTQ3NWMtYjFlOS0zYTM5ZGEwMWNmMDgvaW1hZ2UucG5nIiwidHlwZSI6ImF0dGFjaG1lbnQiLCJpYXQiOjE3OTA0Mzg0MDAsImV4cCI6MTc5MDUyNDgwMH0.Pb7lZGNMsHQMS2TI72OqNlhSbtQuM5d2ugHDYxTBk4c) napi.rs 会基于 rust 的定义，自动生成一个 d.ts 类型文件，然后 require 这个文件，你就可以基于类型文件直接调用 rust 里面实现的方法了。 这里只是简单说了下，具体里面的逻辑非常多，我也没看明白。

## 踩坑过程

rspack 已经内置了非常多的原生支持，比如 TypeScript 、React ，所以它的配置简单得多。我们和默认配置不一样的主要几个点，less-loader，这个直接使用即可。tsconfig-paths ，这个我参考文档加上了 tsconfig.json 配置路径即可，似乎很简单，然后项目挂了。  ![image.png](https://iling.fun/api/files.get?sig=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ1cGxvYWRzL2MyMzFmODM0LTY3ZGQtNGJjZi1iM2RmLWYxMTQ2NGUxM2Y0MS8wODZmYTc3Ny0wODlmLTQ1ZmMtOTE5Yy1iNmIzMDllMWQ0Y2UvaW1hZ2UucG5nIiwidHlwZSI6ImF0dGFjaG1lbnQiLCJpYXQiOjE3OTA0Mzg0MDAsImV4cCI6MTc5MDUyNDgwMH0.R4tKnrttyBBOIGVH7LTNV149PpZBFX4GuUe9a-kWXb4) 关键问题在于，我创建了一个最小的项目，而且使用了同样的配置，那个小的项目就没有问题。不过，这个本来就是为了试试 rust ，遇到问题更好，可以进去 debug 看看。 然后把 rspack 项目拉下来，然后参考开发文档，打了一个 debug 包，使用 debug 包继续调试。debug 包终于看到错误信息了，然后遇到问题直接进去 debug 排查一下就行了。 当然，真实过程还是挺折腾的，一开始 vscode 的 debug 工具有点问题，无法调试，然后使用 lldb 直接调试可以进去，但是里面很多变量无法打印出来。

```bash
\$ rust-lldb -- node ../rspack/packages/rspack-cli/bin/rspack build
```

最终，我直接把 rspack_code 依赖的那个包代码修改了，然后直接 print 出错误日志，最终折腾了很久终于发现是我配置错了，要用绝对地址。 后面调试的问题发现是 codelldb 的一个 [bug](https://github.com/vadimcn/codelldb/discussions/456#discussioncomment-874122) ，不知道为啥一直没修复，通过删除一个文件好了。

```typescript
rm ~/.vscode/extensions/vadimcn.vscode-lldb-1.10.0/lldb/bin/debugserver
```

### 模块丢失

这个问题修复以后，项目可以启动了，启动耗时 4s ，感觉很激动。然后打开页面，额，直接挂了。一开始报错，然后进去调试发现是一个模块找不到了

```typescript
import { AddDataModel, IProps } from './index';
export class EditDataModel extends AddDataModel {}
```

好像所有 index 文件都找不到了。然后构建了一个最小的 demo 发现 rspack 解析路径的是会有一个 bug ，首先会从 paths 配置里面找文件，如果找到了就直接返回。也就是 `index` 和 `./index` 是一样的，因为我们项目中有一个 `types/index`文件，然后所有的 `./index`都指向了这个文件。 不过这个问题修复也很简单，直接改下 [nodejs_resolver](https://github.com/web-infra-dev/nodejs_resolver/pull/199) 就可以了。 但最终，我发现 nodejs_resolver 已经不维护了，他们有了新的方案 oxc_resolver ，然后可以开启新的 resolver ，然后这个问题也解决了。

### 装饰器失效

看起来构建都没有问题了，然后页面访问，现在头有了，其他还是空白的，还没有任何报错。仔细调试发现，在 store 里面 set 了以后，组件没有重新 render 。然后看到生成的代码是这样的

```typescript
class RoleStore extends BaseStore {
  @observable isLoaded = false;
	@action.bound
  setRole() {
    this.isLoaded = true;
  }
}
// 老的生成代码
class RoleStore {
    constructor() {
        this.isLoaded = false;
    }
    setRole() {
        this.isLoaded = true;
    }
}
__decorate([observable], RoleStore.prototype, "isLoaded", void 0);
__decorate([action.bound], RoleStore.prototype, "setRole", null);
// rspack 生成的代码
class RoleStore {
    isLoaded = false;
    setRole() {
        this.isLoaded = true;
    }
}
__decorate([observable], RoleStore.prototype, "isLoaded", void 0);
__decorate([action.bound], RoleStore.prototype, "setRole", null);
```

这样导致 isLoader 的 observable 失效了，所以虽然更新成功了，但是 observable 失效了，render 过程没有调用依赖收集，所以 isLoader 更新的时候， render 不会执行了。

---

**Documents**

- [基于实践谈谈软件架构设计](https://iling.fun/s/blog/doc/5z65lqo5a6e6le16lci6lci6l2v5lu25p625p6e6k66k6h-tsGrQnNSNu)
- [rspack 使用体验过程](https://iling.fun/s/blog/doc/rspack-u8S4Mtjkfz)
- [E2E test with Playwright](https://iling.fun/s/blog/doc/e2e-test-with-playwright-JLFKAvWQ0U)
- [Vim everywhere](https://iling.fun/s/blog/doc/vim-everywhere-v6WZyLzGA1)
- [滴答 FaaS 研发平台小结](https://iling.fun/s/blog/doc/faas-XQxhEO3e5r)
- [Cod 服务编排之路](https://iling.fun/s/blog/doc/cod-0CFsOfiX20)
- [Cod 起源和用武之地](https://iling.fun/s/blog/doc/cod-1aAA2rVJZY)
- [2019 年度总结](https://iling.fun/s/blog/doc/2019-RhbTD2SiCn)
- [凤蝶区块历史及其问题排查记录](https://iling.fun/s/blog/doc/5yek6j225yy65z2x5y6g5yy5yk5yw26zeu6aky5o6s5pl6k6w5b2v-WoaaJ9QHpq)
- [如何用单元测试](https://iling.fun/s/blog/doc/5aac5l2v55so5y2v5ywd5rwl6kv-3zwG1cJvTn)
- [小钱袋 Node 应用开发经验总结](https://iling.fun/s/blog/doc/node-QpFfYCuoE0)
- [简单 JS 算数表达式求值算法的实现探索](https://iling.fun/s/blog/doc/js-FXcWkpW2vw)
- [凤蝶可视化编辑实现原理](https://iling.fun/s/blog/doc/5yek6j225yv6keg5yyw57yw6l6r5a6e546w5y6f55cg-ZsBj0CS1ev)
- [凤蝶服务端渲染经验总结二](https://iling.fun/s/blog/doc/5yek6j225pyn5yqh56uv5riy5pt57up6aqm5oc757ut5lqm-a4Kd59d7Td)
- [凤蝶服务端渲染经验总结](https://iling.fun/s/blog/doc/5yek6j225pyn5yqh56uv5riy5pt57up6aqm5oc757ut-GBhTESymbn)
- [Node.js 中 sourcemap 使用问题总结](https://iling.fun/s/blog/doc/nodejs-sourcemap-duex3wDhWt)
- [如何实现一个 mvvm 组件](https://iling.fun/s/blog/doc/mvvm-8TXAVSXe77)
- [webpack 热加载原理探索](https://iling.fun/s/blog/doc/webpack-7Wjn9z1fUB)
- [再谈 vim](https://iling.fun/s/blog/doc/vim-UBf5u57ko7)
- [How to realize velocity template interpreters](https://iling.fun/s/blog/doc/how-to-realize-velocity-template-interpreters-4s1KEznjYR)
- [joycss](https://iling.fun/s/blog/doc/joycss-bGvBvDGF7k)