跳到主要内容

认识

2024年11月08日
柏拉文
越努力,越幸运

一、认识


Rspack 是一个基于 Rust 编写的高性能 JavaScript 打包工具, 它提供对 Webpack 生态良好的兼容性,能够无缝替换 Webpack, 并提供闪电般的构建速度。

二、特点


三、问题


3.1 RsPack Vs Vite?

Vite 提供了很好的开发者体验,但 Vite 在生产构建中依赖了 Rollup ,因此与其他基于 JavaScript 的工具链一样,面临着生产环境的构建性能问题。

另外,Rollup 相较于 Webpack 做了一些平衡取舍,在这里同样适用。比如,Rollup 缺失了 Webpack 对于拆包的灵活性,即缺失了 optimization.splitChunks 提供的很多功能。

3.2 RsPack Vs Rollup?

RspackRollup 虽然都是打包工具,但是两者的侧重领域不同,Rollup 更适合用于打包库,而 Rspack 适合用于打包应用。因此 Rspack 对打包应用进行了很多优化,如 HMRBundle splitting 等功能,而 Rollup 则比 Rspack 的编译产物对库更为友好,如更好的 ESM 产物支持。 社区上也有很多的工具在 Rollup 基础上进行了一定的封装,提供了对应用打包更友好的支持,如 vitewmr, 目前 RspackRollup 有更好的生产环境构建性能。

3.3 RsPack Vs Esbuild?

我们在内部进行过大规模地将 esbuild 作为通用的 Web App 构建工具的实践,但是遇到如下一些问题:

  1. 缺乏对 JavaScript HMR 和增量编译的良好支持,这导致大型项目中的 HMR 性能较差。

  2. 拆包策略也非常原始,难以满足业务复杂多变的拆包优化需求。

3.4 将 Webpack 升级为 RsPack 之后会有什么问题吗?

问题一、相同路径、相同名称 Chunk 处理方式不同: 将 Webpack 升级为 RsPack 之后, 针对相同路径、相同名称的 Chunk 处理方式不一样。Webpack 遇到相同的 Chunk 会覆盖之前的。RsPack 遇到相同的 Chunk, 会直接使用已经加载的 Chunk。这样会导致之前在 Webpack 构建下, 一个页面加载了一份正常的 Chunk, 基于 MicroApp 恰巧加载了一份相同路径、名称的 Chunk, 但是他们的构建方式是不一样的、逻辑也不一样。由于 Webpack 后面的 Chunk 会覆盖前面的 Chunk, 所有 Webpack 不会又问题。但是在 RsPack 构建下, 本来应该使用的是后面的 Chunk, 但是因为 Chunk 的复用逻辑, 导致 MicroApp 使用的是前面的 Chunk, 导致 MicroApp 渲染异常。以下是 RsPack Chunk 处理逻辑:

      var installedChunks = {"platform": 0,};
__webpack_require__.O.j = function(chunkId) { return installedChunks[chunkId] === 0; };
// install a JSONP callback for chunk loading
var webpackJsonpCallback = function(parentChunkLoadingFunction, data) {
var chunkIds = data[0];
var moreModules = data[1];
var runtime = data[2];
// add "moreModules" to the modules object,
// then flag all "chunkIds" as loaded and fire callback
var moduleId, chunkId, i = 0;
if (chunkIds.some(function(id) { return installedChunks[id] !== 0; })) {
for (moduleId in moreModules) {
if (__webpack_require__.o(moreModules, moduleId)) {
__webpack_require__.m[moduleId] = moreModules[moduleId];
}
}
if (runtime) var result = runtime(__webpack_require__);
}
if (parentChunkLoadingFunction) parentChunkLoadingFunction(data);
for (; i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if (
__webpack_require__.o(installedChunks, chunkId) &&
installedChunks[chunkId]
) {
installedChunks[chunkId][0]();
}
installedChunks[chunkId] = 0;
}
return __webpack_require__.O(result);
};

问题二、EsModule 语法中不允许有 CommonJS 导出: 我们在之前的 Tsx 中, 有过一些不正确的模块语法。比如上面通过 import 导入模块, 下面通过 module.exports 导出。EsModuleCommonJS 混合在一起, 这在 Webpack 中没有问题, 但是 RsPack 会严格校验。

3.5 Vite、Rollup、Webpack、RsPack 各自优势、特点?

Vite: 采用双引擎架构, 开发阶段使用 Esbuild + no-bundle 服务,生产环境用 Rollup 编译构建。Vite在开发阶段, Vite 项目的启动可以分为两步。第一步是 依赖预构建, 借助 Esbuild 超快的编译速度来将不同规范的代码转换为 ES Module 格式, 方便浏览器直接加载, 此外, 预构建还会将第三方库分散的文件合并打包到一起, 减少 HTTP 请求数量; 第二步是 Dev Server 的启动, 基于浏览器原生 ESModule 的支持实现了 no-bundle 服务,实现开发阶段的 Dev Server, 进行模块的按需加载, 可以直接在浏览器中运行源码, 无需事先打包。当浏览器请求一个模块时, Vite 会即时地编译和执行对应的代码, 会经历一系列的编译流程, 然后 Vite 会将编译结果响应给浏览器。Vite 生产环境借助 Rollup, 从 AST 解析的功能开始, 完成代码的词法分析(tokenize)和语义分析(parse), 实现模块依赖图和作用域链的搭建, 并完成 Tree Shaking、循环依赖检测及 Bundle 代码生成。 在 HMR 方面, Vite 的热更新则只会针对改动的模块进行更新,提高了更新速度。当开发者修改了一个模块的代码, Vite 可以在几毫秒内完成热更新, 将更新后的模块发送到浏览器中, 让开发者能够更快地看到代码修改后的效果。

Rollup 是一款基于 ES Module 模块规范实现的 JavaScript 打包工具。通过静态分析构建模块依赖图, 并进行 Tree Shaking, 剔除未使用的代码。Rollup 可以直接处理 Es Modules , 对于 CommonJs 需要通过插件来转换。Rollup 本身不提供开发服务器、HMR 等功能,主要定位于生产构建。

Webpack: 无论开发环境还是生产环境, Webpack 会扫描整个项目的所有文件, 将所有模块进行静态分析, 分析它们之间的依赖关系, 形成依赖树, 然后一次性编译生成文件, 生成最终代码前, 根据模块中出现的特性依赖, 补充相应运行时代码, 比如立即表达式 IIFEWebpack runtime 运行时代码, 生成最终产物。Webpack 实现了一套自己的 CommonJS 规范, 在 Webpack 中, 每个模块都被包装在一个函数中, 这个函数接受一个对象, 这个对象有 exportsrequiremodule 等属性, 通过这种方式实现了模块的隔离, 每个模块都有自己的作用域, 不会污染全局作用域。在 HMR 方面, Webpack 的热更新需要整个模块链重新打包和替换,对于大型项目可能会有延迟。在热更新过程中, Webpack 会检测到模块的变化,然后重新编译整个模块链,最后将更新后的模块替换到浏览器中。这个过程相对复杂,可能会导致一定的延迟。另外, Webpack 5.x 内置了更加完善、更智能、更强大的 TreeShaking代码分割作用域提升 等。Webpack 5.x 新特性: 1. 模块联邦(Module Federation, 模块联邦是 Webpack 5 中最引人注目的新特性之一, 它允许不同独立构建的应用在运行时共享彼此的模块,而无需事先打包到同一个 bundle 里; 2. 持久化缓存(Persistent Caching, Webpack 5 引入了持久化缓存功能,将中间构建结果保存到磁盘上,这样在二次构建时可以复用已有结果; 3. 更高效的 Tree Shaking 与代码优化: 使生成的产物更精简,提升浏览器缓存效果; 4. 删除 Node.js 核心模块的自动 Polyfill, 以前 Webpack 会自动为浏览器端补充 Node.js 核心模块(例如 Bufferprocesscrypto 等)的 polyfill, Webpack 5 默认不再提供这些 polyfill, 要求开发者根据项目需要手动添加或调整代码逻辑; 5. 内置资源模块(Asset Modules, 在 Webpack 4 及之前版本中,处理静态资源(如图片、字体等)通常依赖于第三方 Loader(如 file-loaderurl-loaderraw-loader 等), Webpack 5 内置了资源模块,开发者可以直接在代码中 import 资源, 无需额外安装 Loader

RsPack 是一个基于 Rust 编写的高性能 JavaScript 打包工具, 它提供对 Webpack 生态良好的兼容性,能够无缝替换 Webpack, 并提供闪电般的构建速度。1. Rust 语言优势: Rspack 使用 Rust 语言编写, 得益于 Rust 的高性能编译器支持, Rust 编译生成的 Native Code 通常比 JavaScript 性能更为高效; 2. 高度并行的架构: Webpack 受限于 JavaScript 对多线程的羸弱支持,导致其很难进行高度的并行化计算,而得益于 Rust 语言的并行化的良好支持, Rspack 采用了高度并行化的架构,如模块图生成,代码生成等阶段,都是采用多线程并行执行,这使得其编译性能随着 CPU 核心数的增长而增长,充分挖掘 CPU 的多核优势; 3. 内置大部分的功能: 事实上 Webpack 本身的性能足够高效,但是因为 Webpack 本身内置了较少的功能,这使得我们在使用 Webpack 做现代 Web App 开发时,通常需要配合很多的 pluginloader 进行使用,而这些 loaderplugin 往往是性能的瓶颈,而 Rspack 虽然支持 loaderplugin,但是保证绝大部分常用功能都内置在 Rspack 内,从而减小 JS plugin | loader 导致的低性能和通信开销问题; 4. 增量编译: 尽管 Rspack 的全量编译足够高效,但是当项目庞大时,全量的编译仍然难以满足 HMR 的性能要求,因此在 HMR 阶段,我们采用的是更为高效的增量编译策略,从而保证,无论你的项目多大,其 HMR 的开销总是控制在合理范围内。但是, RsPackWebpack 对构建产物体积的优化方面, TreeShaking代码分割作用域提升 做的不是那么完善。