因为rolldown-vite比vite打包速度快, 所以必须把rolldown-vite在项目中用起来🤺

背景

采用 Rust 编写的高性能打包器 rolldown-vite,在实际打包中速度通常能比 Vite(基于 Rollup)快一倍左右。Rolldown 之所以比 Rollup 更快,主要有以下几个原因:

  1. 底层语言的差异
  • Rollup 是用 JavaScript 实现的
  • Rolldown 则是用 Rust 实现的 Rollup 兼容打包器。Rust 在执行效率和内存管理方面远胜 JavaScript,因此在相同逻辑下,Rust 版本的打包速度自然更快。
  1. 更强的并行能力
  • Rollup 的构建流程基本是单线程的(受限于 Node.js)
  • Rolldown 基于 Rust,可以利用多线程并行解析和处理依赖,充分发挥 CPU 多核的优势。结果就是:Rollup 需要逐个文件处理,而 Rolldown 可以成批处理文件。
  1. 更高效的 AST 解析
  • 打包过程中需要不断将 JS/TS 转换为 AST(抽象语法树)并进行依赖分析
  • Rolldown 采用了自研的 Rust 工具链 Oxc(涵盖 parser、transformer、resolver、minifier 等组件),相比传统基于 Babel/Terser 的 JS AST 解析,速度快得多。
  1. 缓存与增量编译优化
  • Rolldown 内部实现了更细粒度的缓存机制,可以避免重复解析同一个依赖
  • vite dev 模式下尤为明显:修改一个文件时,Rolldown 只会重新编译受影响的模块,而不是像 Rollup 那样做很多无关的检查。

除此之外,Rolldown 完全兼容 Vite 的配置,迁移成本极低,因此想在项目中应用起来,现在我们进入今天的正题

改造

第一步 修改package.json

添加用rolldown-vite替换vite的声明, 这里有个细节要注意一下,就是rolldown-vite的版本, 如果你写成@latest,会安装最新的rolldown-vite@7.1.5版本,这个版本要求node最少为v22.19, 而公司Jenkins CI机器上的node版本是v18, 所以我没有使用最新的版本, 指定了一个兼容node v18的版本

js 复制代码
  "pnpm": {
    "overrides": {
      "vite": "npm:rolldown-vite@6.3.16"
    }
  }

第二步 解决ace-builds引起的报错

执行pnpm i将vite换成rolldown-vite,运行项目, 不出意外意外果然发生了, 业务代码中引入的ace-builds

js 复制代码
  import { VAceEditor } from 'vue3-ace-editor';
  import 'ace-builds/src-noconflict/theme-chrome';
  import 'ace-builds/src-noconflict/theme-monokai.js';
  import 'ace-builds/src-noconflict/mode-python.js';
  import 'ace-builds/src-noconflict/mode-sh.js';

在控制台报如下错误:

js 复制代码
17:16:48 [vite] (client) Pre-transform error: Failed to resolve import "../lib/dom" from "node_modules/.vite/deps/ace-builds_src-noconflict_theme-chrome.js?v=a245d48f". Does the file exist?
  Plugin: vite:import-analysis
  File: D:/project/ai-platform-frontend/node_modules/.vite/deps/ace-builds_src-noconflict_theme-chrome.js?v=a245d48f:2:45
  1  |  import * as __require_for_vite_fUx58Z from "./chrome-css";
  2  |  import * as __require_for_vite_0FJVYG from "../lib/dom";
     |                                              ^
  3  |  import { __commonJS } from "./chunk-51aI8Tpl.js";
  4  |

产生这个错误的原因是: 导入的 ace-builds/src-noconflict/theme-chrome.js 里面其实是个 UMD/CommonJS 打包产物,代码大概是这样的:

js 复制代码
require("../lib/dom");

src-noconflict 目录下确实没有 ../lib/dom 这个文件。 但 Vite(基于 esbuild 的预构建逻辑)在扫描依赖时,会做一层 fallback alias 处理 ,会把 ace 的 require("../lib/dom") 内联成空模块),所以在 Vite 里能跑通。

可是 rolldown-vite 目前的 CommonJS 兼容实现还不完整,它会老老实实去找 ../lib/dom,于是就报 Does the file exist?

解决办法: 官方推荐用 src-min-noconflict(精简预构建版本),避免加载到内部 require。将原来的src-noconflict路径全部替换成src-min-noconflict,这样就不会出现对 ../lib/dom的引用 。

js 复制代码
import { VAceEditor } from 'vue3-ace-editor';
import 'ace-builds/src-min-noconflict/theme-chrome';
import 'ace-builds/src-min-noconflict/theme-monokai';
import 'ace-builds/src-min-noconflict/mode-python';
import 'ace-builds/src-min-noconflict/mode-sh';

第三步 解决vue3-ace-editor引起的报错

一波刚平,一波又起,引入的第三方组件 vue3-ace-editor, 也报同样的错误:

js 复制代码
18:36:37 [vite] Internal server error: Failed to resolve import "../lib/dom" from "node_modules/.vite/deps/vue3-ace-editor.js?v=7e2ec517". Does the file 
exist?
  Plugin: vite:import-analysis
  File: D:/project/ai-platform-frontend/node_modules/.vite/deps/vue3-ace-editor.js?v=7e2ec517:10:45
  8  |  import * as __require_for_vite_cLUzHK from "./default_english_messages";
  9  |  import * as __require_for_vite_r9MT0S from "./textmate-css";
  10 |  import * as __require_for_vite_xoz43m from "../lib/dom";
     |                                              ^
  11 |  import * as __require_for_vite_FEdWm0 from "./lib/lang";
  12 |  import * as __require_for_vite_nDfAqB from "./lib/net";

查看了一下vue3-ace-editor的源码, 发现里面也有对ace-builds的引入,难怪报出和原来一模一样的错

js 复制代码
import ace, { type Ace } from 'ace-builds';

可这是第三方包,不是自己写的业务文件。改还是不改呢?看了一下vue3-ace-editor里面的功能不是很复杂,决定对vue3-ace-editor打补丁, 可以看到有ts和js两个版本。

刚开始选择的是ts版本, 因为项目中用的是ts。 在项目src\components\创建 vue3-ace-editor文件夹, 将node_modules/vue3-ace-editor下的index.ts,index.d.ts,types.d.ts复制粘贴到src\components\vue3-ace-editor目录下。src\components\vue3-ace-editor\index.ts文件需要关注的地方如下:

js 复制代码
import ace, { type Ace } from 'ace-builds';
import ResizeObserver from 'resize-observer-polyfill';
// ...

移除和补充安装npm依赖包

bash 复制代码
pnpm remove vue3-ace-editor && pnpm add ace-builds  resize-observer-polyfill

修改ace的导入方式,将esm导入方式修改成umd加载方式

js 复制代码
// import ace from 'ace-builds';
import { type Ace } from 'ace-builds';
import ResizeObserver from 'resize-observer-polyfill';
// ...

node_modules\ace-builds\src-min\ace.js复制到根目录public\ace.js,在index.html中添加

js 复制代码
<script src="./ace.js"></script>

因为这个在线代码编辑器在项目中多处用到,再加上是压缩版本,所以全局加载对网站流量的浪费不是很大。改完之后又发现, 从ace-builds导出类型定义

js 复制代码
import { type Ace } from 'ace-builds'

会引入ace/lib/es6-shim这样的文件, 而ace/lib文件在node_modules/ace-builds下是不存在的。

js 复制代码
node_modules/.pnpm/ace-builds@1.39.0/node_modules/ace-builds/src-noconflict/ace.js?v=f6576fbe:5:45
  3  |    return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
  4  |  };
  5  |  import * as __require_for_vite_ikxleh from "./es6-shim";
     |                                              ^
  6  |  import * as __require_for_vite_0lQ2hq from "./deep_copy";
  7  |  import * as __require_for_vite_vSNRCb from "./useragent";

本来想把用到的类型拷贝到项目中,可是发现层级依赖很多。改不动。于是将ts版本换成了js版本。删除了index.js中的import ace from 'ace-builds'; ,并将业务代码中vue3-ace-editor引入路径进行替换, 运行了一下发现vue3-ace-editor终于不报ace-builds文件引入找不到的错误了。

js 复制代码
// 原来
import { VAceEditor } from 'vue3-ace-editor';
// 修改成:
import { VAceEditor } from '@/components/vue3-ace-editor';

第四步 解决recorder-core的wav.js报错

如下的代码, 在本地并不报错, 可是打包部署到线上环境之后

js 复制代码
import Recorder from 'recorder-core';
import 'recorder-core/src/engine/wav.js'

报错,不能从undefined对象中读取i18n属性

js 复制代码
index.vue:42

TypeError: Cannot read properties of undefined (reading 'i18n') at wav.js:16:30

wav.js:16

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'i18n') at wav.js:16:30

看了一下recorder-core/src/engine/wav.js中报错的代码片段

js 复制代码
(function(factory){

  var browser=typeof window=="object" && !!window.document;

  var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
  
  var rec=win.Recorder,ni=rec.i18n;

  factory(rec,ni,ni.$T,browser);

}(function(Recorder,i18n,$T,isBrowser){

"use strict";

是win.Recorder对象不存在导致的, 造成这个错误的原因是:recorder-core 的工作模式采用的是全局挂载的设计模式

  • 首先,它的核心脚本src/recorder-core.js会先执行,在全局的 window 对象上创建一个名为 Recorder 的主对象。
  • 然后,像 wav.jsmp3.js 这样的引擎(engine)或插件脚本再执行。这些插件脚本会假设 window.Recorder 已经存在,并把自己注册到 window.Recorder 对象上,或者从它那里获取配置和方法(比如 i18n)。
  • Vite (Rollup): 在生产打包时,Rollup 的算法通常能很好地遵循 ES Module 的 import 顺序,确保依赖先被执行。先导入 import Recorder from 'recorder-core' , 然后 import 'recorder-core/src/engine/wav.js' 时,它能保证前者创建 window.Recorder 的对象在后者的使用时存在。
  • rolldown-vite: Rolldown 作为一个新兴的、用 Rust 编写的高性能打包器,其内部的依赖图分析、代码分割(chunking)和模块排序算法与 Rollup 有所不同。在打包优化过程中,因为是并行处理文件, 错误地将它们分割到了不同的 chunk 中并以错误的顺序加载。这就导致 wav.js 运行时,window.Recorder 还未被定义,从而引发了错误。

简单来说,这是一个典型的因打包器优化导致代码执行时序错乱的问题。知道了原因, 解决起来就比较容易了。采用动态加载的方式, 就能保证执行顺序。

js 复制代码
import Recorder from 'recorder-core';
// @ts-ignore
async () => await import('recorder-core/src/engine/wav.js');

改完之后,果然不报错了。

最后

项目终于不报错了, 让我们看看改造前后的打包时间。两种编译工具的打包时间如下所示:

编译工具 时间
vite6 1m 21s
rolldown-vite@6.3.16 39.72s

效率的提升还是很明显的, 今天没白折腾。另外我去rolldown-vite github 官方仓库查看了一下v6的最高版本是v6.3.21, 按理说,后面的版本是对前面版本的改进和完善, 打包时间应该比前面的版本短,至少持平。 可是我把rolldown-vitev6.3.16换成v6.3.21之后, 发现打包时间变长了一些54.33s,猜测可能是增强了兼容性,牺牲了一部分性能。毕竟性能再高, 改造之后项目跑不起来也是白搭。考虑问题的方向没有问题。我还是继续使用6.3.16, 因为目前项目出现的错误还能hold住。本文完

相关推荐
天天进步201542 分钟前
从零到一:现代化充电桩App的React前端参考
前端·react.js·前端框架
柯南二号1 小时前
【大前端】React Native Flex 布局详解
前端·react native·react.js
龙在天2 小时前
npm run dev 做了什么❓小白也能看懂
前端
hellokai2 小时前
React Native新架构源码分析
android·前端·react native
li理3 小时前
鸿蒙应用开发完全指南:深度解析UIAbility、页面与导航的生命周期
前端·harmonyos
KubeSphere3 小时前
Kubernetes v1.34 重磅发布:调度更快,安全更强,AI 资源管理全面进化
前端
wifi歪f3 小时前
🎉 Stenciljs,一个Web Components框架新体验
前端·javascript
1024小神3 小时前
如何快速copy复制一个网站,或是将网站本地静态化访问
前端
掘金一周3 小时前
DeepSeek删豆包冲上热搜,大模型世子之争演都不演了 | 掘金一周 8.28
前端·人工智能·后端