Rolldown:Rollup 兼容 API 之下的 Rust + Oxc 打包器
核验日期:2026-07-09。示例版本来自
examples/build-tools/package.json:rolldown1.1.3。Rolldown 官方文档当前latestrelease channel 为1.x.x;Vite 8 已把 Rolldown 作为统一 Rust bundler。
需要掌握的问题
| 主题 | 读者必须回答的问题 | 原理与边界 | 高频错误 | 代码或观察证据 | 来源 |
|---|---|---|---|---|---|
| 定位与 Vite 8 关系 | Rolldown 是什么,为什么不能把它等同于 Vite dev server? | Rolldown 是 Rust bundler,目标是以 Rollup 兼容 API 服务 Vite 和独立构建;Vite 提供 dev server、HMR、环境编排和框架体验 | 直接用 Rolldown 期待 Vite 的 HMR、HTML dev transform、依赖预构建和插件容器行为 | Vite 8 release、Rolldown getting started 中“通过 Vite 使用”说明 | Introduction、Vite 8 release |
| 构建流水线 | scan → resolve → parse → link → generate 每步输入输出是什么? | 从 input 扫描入口,resolver 解析依赖,Oxc/Rust 解析和转换模块,link 阶段建立图与 chunk,generate 输出 JS/CSS/assets/map | 在 transform 阶段期待最终文件名,或在 output 后才想改变 chunk 拓扑 | Mermaid 流程、共享 rolldown.config.mjs | Getting Started |
| Rust + Oxc 内置能力 | Rolldown 为什么能少装很多 Rollup 插件? | Rust core 负责并行图构建;Oxc 提供 TypeScript、JSX、syntax lowering、minify、resolver 相关能力;moduleTypes 让 CSS/JSON 等非 JS 类型有统一约定 | 继续照搬 Rollup 插件链导致 JS/native 边界过重;误以为内置 TS 转换会类型检查 | defineConfig、内置 transforms、module types、Oxc resolver | Notable Features、Module Types |
| Rollup 兼容配置与插件 | 哪些 Rollup API 可复用,哪些要回归? | 配置文件、input/output、JS API、watcher 和 Rollup 风格插件 API 尽量兼容;多数插件可复用,但内置能力可能替代插件,私有行为仍要验证 | 插件依赖 Rollup 内部对象、输出钩子时机或 CJS 插件细节,迁移后产物不同 | resolveId、load、transform、generateBundle 示例 | Getting Started、Why Plugin Hook Filters |
| plugin hook filter 与 JS/native 边界 | 为什么 JavaScript 插件会拖慢 Rust bundler? | Rust core 可并行处理模块,但每次进入 JS plugin hook 都要跨边界且常在单线程串行;filter 让 Rust 先判断是否需要调用 JS | 每个插件在 transform 里手写 if (!id.endsWith(...)) return,但仍对所有模块跨边界调用 | filter 写法、官方 10k benchmark 说明 | Why Plugin Hook Filters |
| dynamic import、chunk 与 Tree Shaking | Rolldown 如何决定 entry chunk、dynamic chunk、common chunk 和可删除代码? | dynamic import 形成 dynamic chunks;多入口共享模块可形成 common chunks;DCE 同时要求“未使用”和“无副作用”;manual code splitting 可覆盖默认策略 | CSS/副作用被误删;manual group 改变执行顺序;HTML 直接加载动态 chunk | detail.ts dynamic entry、SHOULD_NOT_REACH_BUNDLE 被删除、preserved 保留 | Automatic Code Splitting、Dead Code Elimination |
| CSS、assets、manifest 与 bundle analyzer | 独立 Rolldown 怎样生成浏览器可验收产物? | Rolldown 核心负责 bundle;示例用插件把 CSS 聚合为 asset、生成 manifest.json 和 index.html;bundle analyzer 可通过 generateBundle、manifest 或兼容可视化插件读取 bundle | 只输出 JS 文件,却以为已经是完整应用产物;analyzer 插件兼容性不回归 | cssAsset()、manifest()、html() 插件和 artifact test | Module Types、Getting Started |
| CJS、external 与兼容失败 | 迁移 Rollup 配置时哪些地方最容易和旧产物不同? | Rolldown 内置 CJS 支持,解析逻辑靠 oxc-resolver;external、platform、Node built-ins polyfill、CJS interop、插件私有 API 都可能影响结果 | 浏览器目标中误用 Node built-ins;CJS default/named interop 与旧插件链不同 | Notable features、Bundling CJS、Troubleshooting | Bundling CJS、Troubleshooting |
| 迁移与重大变化 | Rollup/Vite 迁移到 Rolldown/Vite 8 要核对什么? | Rolldown 1.x 是稳定 npm 线;Vite 8 用 Rolldown 统一过去 esbuild + Rollup 双管线;许多配置可自动转换,但复杂项目建议先用 rolldown-vite 隔离问题 | 认为 Vite 8 仍由 Rollup 生产构建;把本地小样例耗时当普遍性能结论 | 版本线表、Vite 8 迁移建议 | Vite 8 release、Getting Started |
| native binary 与供应链 | Rust bundler 的安装和执行边界是什么? | Rolldown 发布多平台 prebuilt binaries;无预构建平台可 fallback Wasm 或源码构建;插件仍以 Node 权限执行 | CI 架构不支持、Wasm fallback 性能不同、插件读取 secret、未锁定 native 包 | prebuilt 平台、Wasm fallback、插件执行边界 | Getting Started |
内容边界说明:
| 分类 | 本文落点 |
|---|---|
| 编程语言与类型系统 | 解释 TypeScript、JSX、ESM、CJS、dynamic import、DCE 和 Oxc 转换;不教学 JS/TS 语法,也不把 Rolldown 当类型检查器 |
| Browser APIs 与持久化能力 | 覆盖浏览器 ESM、dynamic import、CSS asset、HTML 引用、Source Map 和相对路径;IndexedDB、Storage、权限模型不适用 |
| UI 框架与元框架 | 只说明 Rolldown 与 Vite 8、插件生态、HMR 边界;框架渲染、调度和水合由框架文章负责 |
| 构建工具与运行时 | 完整覆盖 input、resolveId/load/transform、parse、link、chunk、Tree Shaking、output、插件、产物和故障定位 |
| 后端、数据与 Agent 技术 | 只覆盖 native binary、构建插件以 Node 权限执行、CI/供应链和 bundle 分析;请求生命周期、数据库一致性和 Agent 评测不适用 |
Rolldown 的主问题:用 Rollup 兼容接口统一高性能打包底座
Rolldown 不只想做一个“更快的 Rollup CLI”。它是 Vite 8 统一底层 bundler 的关键组件:用 Rust 和 Oxc 提供接近 esbuild 级别的性能,同时尽量保留 Rollup/Vite 插件生态的扩展方式。
要先划清边界:Rolldown 自身不是 Vite dev server,也不提供 Vite 的端到端 HMR、HTML 按请求转换、环境 API、框架插件编排或浏览器客户端。即使 Rolldown 核心存在 HMR 相关演进,直接使用 rolldown --config 时你得到的仍是 bundler;需要 Vite 开发体验时应通过 Vite 使用。
和相邻工具的边界:
| 工具 | 主要职责 | 与 Rolldown 的关系 |
|---|---|---|
| Rollup | ESM-first 打包器和插件 API 事实标准 | Rolldown 尽量兼容 Rollup 配置/API/插件,但实现和性能模型不同 |
| esbuild | Go 实现的高速转换/打包器 | Vite 8 之前常用于 dev 转换;Rolldown 试图以 Rust 统一 dev/build 底座 |
| Oxc | Rust 语言工具链,提供 parser、transform、minifier、resolver 等能力 | Rolldown 使用 Oxc 能力做内置转换、解析和优化 |
| Vite | 开发服务器、插件容器、生产构建编排 | Vite 8 把 Rolldown 作为 bundler;Vite 仍负责 dev server 和 HMR |
| tsdown | 面向库打包的上层工具 | Rolldown 文档建议库构建可关注 tsdown,而不是所有库都手写底层配置 |
严重问题完整流程:
| 症状 | 根因 | 观察命令 | 修正 |
|---|---|---|---|
| 直接运行 Rolldown 后没有 HMR | Rolldown 不是 dev server | 查看命令是否是 rolldown --config 而不是 vite dev | 需要开发服务器用 Vite;需要底层 bundle 才直接用 Rolldown |
| 插件迁移后变慢 | JS 插件对每个模块都跨边界调用 | 打开 plugin hook filter 或减少插件链 | 用内置能力替代插件,为 transform/load 加 filter |
| 构建成功但浏览器白屏 | 只输出 JS,未生成 HTML/CSS/manifest 或 public path 错 | 检查 dist/rolldown/index.html 和 Network | 用插件或上层工具生成完整浏览器产物 |
scan、resolve、parse、link、generate:按阶段定位问题
Rolldown 可以用 Rollup 风格配置;下面是最小示意,不包含本文示例里生成 HTML、CSS 和 manifest 的插件:
import { defineConfig } from 'rolldown';
export default defineConfig({
input: 'src/main.ts',
output: {
dir: 'dist/rolldown',
format: 'esm',
sourcemap: true,
entryFileNames: 'assets/[name]-[hash].js',
chunkFileNames: 'assets/[name]-[hash].js',
},
});这段配置会进入如下阶段:
| 阶段 | 输入 | 处理 | 输出 | 常见错误 |
|---|---|---|---|---|
| scan | 用户 entry、配置、插件 | 收集入口和初始导入请求 | 待解析 dependency | input 指错、HTML 当 entry 但没有对应插件 |
| resolve | source + importer | oxc-resolver、alias、condition、external、插件 resolveId | 稳定模块 id 或 external | 同包多份、Node built-ins 进入浏览器 bundle |
| parse | 模块源码 | Oxc/Rust parse JS/TS/JSX/CJS 等 module type | AST、imports/exports、side effect 初步信息 | 语法超出 transform 目标或模块类型识别错 |
| transform | 单模块代码 | 内置 TS/JSX/syntax lowering、插件 transform | 新代码和 source map | map 丢失、插件对所有文件运行 |
| link | 所有模块 | 建 module graph、绑定 exports、DCE、side effect 分析 | 可优化的图 | CJS 动态访问、side effect 不确定导致保守保留 |
| chunk | 图和 output 配置 | entry/dynamic/common/manual chunk 切分 | chunk graph | manual groups 改变执行顺序或缓存策略 |
| generate | chunk 和 assets | 渲染 JS、写 CSS/manifest/HTML/map | dist/ 产物 | 在错误 hook 里依赖最终 hash 文件名 |
调试建议:先定位阶段,再改配置。比如“找不到模块”通常在 resolve;“代码没降级”在 transform;“文件数量不对”在 chunk;“HTML 没引用 CSS”在 generate。
Rust + Oxc 内置能力:少装插件,但别省掉验证
Rolldown 的内置能力覆盖了很多旧 Rollup 项目会安装插件解决的问题:
| 能力 | 内置来源 | 取代或减少的插件 | 边界 |
|---|---|---|---|
| TypeScript | Oxc transform | @rollup/plugin-typescript 的部分转译职责 | 不做类型检查,不生成 .d.ts 发布策略 |
| JSX | Oxc transform | Babel/SWC JSX 转换链的一部分 | 框架插件仍可能需要额外 HMR/refresh 逻辑 |
| Syntax lowering | Oxc transform target | Babel/esbuild 转换的一部分 | 目标环境和 polyfill 仍要另行处理 |
| CJS interop | Rolldown 内置 CJS 支持 | @rollup/plugin-commonjs 的很多用法 | 动态 require、奇异 interop 仍要回归 |
| Module resolution | oxc-resolver | @rollup/plugin-node-resolve 的很多用法 | alias、exports、browser/node platform 需核对 |
| define/inject | transform 内置能力 | replace/inject 类插件的一部分 | AST-based 替换和文本替换语义不同 |
| minify | Oxc Minifier | terser/esbuild minify 的部分用法 | 压缩 bug workaround 和 legal comment 策略需测试 |
| CSS/moduleTypes | module type 约定和插件协作 | 非 JS 资源插件的一部分 | 独立应用产物仍要生成 CSS asset 和 HTML 引用 |
TypeScript 边界示例:
{
"scripts": {
"build": "rolldown --config rolldown.config.mjs",
"typecheck": "tsc --noEmit"
}
}不要把 “Rolldown 支持 TypeScript” 读成 “Rolldown 会替你做类型证明”。它更接近“能解析并删除/转换类型语法,让模块进入 bundle”。
CSS 边界也类似。Rolldown 的 module types 给 CSS 等非 JS 模块一个统一识别和插件协作模型;但独立使用 Rolldown 时,是否写出 CSS 文件、如何注入 HTML、是否提取 critical CSS,仍由配置或插件决定。本仓库示例用自定义 cssAsset() 插件显式产生 assets/styles.css。
Rollup 兼容插件:resolveId/load/transform/generateBundle 仍是核心语言
Rolldown 的插件 API 目标是兼容 Rollup,因此迁移时最先核对四类钩子:
| 钩子 | 输入 | 输出 | 适合做 | 不适合做 |
|---|---|---|---|---|
resolveId | import 请求和 importer | resolved id、external、null | alias、虚拟模块、external、条件导出 | 改完整源码 |
load | resolved id | 源码、map、moduleType、null | 虚拟模块、读取非标准资源 | 依赖最终 chunk 文件名 |
transform | 单模块 code/id | 新 code、map、moduleType | TS/CSS/MD/SVG 转换 | 全局 bundle 分析 |
generateBundle | 完整 bundle 对象 | emit asset、修改 bundle | manifest、HTML、bundle analyzer 数据 | 依赖文件已经写入磁盘 |
共享示例里 manifest() 使用 generateBundle,因为它必须看到完整 bundle:
function manifest() {
return {
name: 'manifest',
generateBundle(_options, bundle) {
const entries = {};
for (const item of Object.values(bundle)) {
if (item.type !== 'chunk' || !item.facadeModuleId) continue;
entries[toSourceName(item.facadeModuleId)] = {
file: item.fileName,
src: toSourceName(item.facadeModuleId),
isEntry: item.isEntry,
isDynamicEntry: item.isDynamicEntry,
};
}
this.emitFile({
type: 'asset',
fileName: 'manifest.json',
source: `${JSON.stringify(entries, null, 2)}\n`,
});
},
};
}一个 CSS 插件应该尽量用 hook filter 减少 JS/native 边界:
function cssAsset() {
return {
name: 'css-asset',
transform: {
filter: {
id: { include: /\.css$/ },
},
handler(code, id) {
return {
code: `export default ${JSON.stringify(id)};`,
map: null,
moduleType: 'js',
};
},
},
generateBundle() {
this.emitFile({
type: 'asset',
fileName: 'assets/styles.css',
source: '/* collected css */\n',
});
},
};
}严重问题完整流程:
| 症状 | 根因 | 观察命令 | 修正 |
|---|---|---|---|
| 插件逻辑正确但构建慢 | transform 对所有模块都跨入 JS 后再 return | 打印 hook 调用次数或用 profiler | 使用 transform.filter,让 Rust 先过滤 id |
| manifest 少了 dynamic entry | 在过早钩子里生成 manifest,看不到完整 bundle | 检查 manifest.json 是否有 src/detail.ts | 在 generateBundle 中读取 bundle |
| Source Map 指向虚拟模块或生成代码 | 插件返回 code 但 map 不完整 | 检查 .map.sources | 使用 MagicString 或转换器返回 map;虚拟 id 给可读 source |
plugin hook filter:Rust 并行和 JavaScript 插件之间的边界
Rolldown 的核心在 Rust 中并行处理模块;JavaScript 插件仍运行在 JS 侧。没有 filter 时,即使插件只关心 .css,hook 也可能对每个 .js、.ts、.jsx 模块跨边界调用一次。
官方文档用 10k 应用 benchmark 说明:传统写法下,插件数量增加会让构建时间线性变差;加 filter 后,Rust 在跨边界前先判断是否匹配,绝大多数无关模块不会进入 JS hook。
传统写法:
export default {
name: 'css-only',
transform(code, id) {
if (!id.endsWith('.css')) return null;
return { code: transformCss(code), map: null };
},
};更适合 Rolldown 的写法:
export default {
name: 'css-only',
transform: {
filter: {
id: { include: /\.css$/ },
},
handler(code) {
return { code: transformCss(code), map: null };
},
},
};取舍:
| 写法 | 性能 | 可靠性 | 使用难度 | 维护成本 | 适用规模 |
|---|---|---|---|---|---|
| hook 内手写 if | 小项目可接受 | 语义直观 | 低 | 低 | 插件少、模块少 |
| hook filter | 更好,避免大量无关跨边界 | 过滤条件要准确 | 中 | 中 | 多插件、大模块图、CI 构建 |
| 内置能力替代插件 | 通常最好 | 取决于内置语义是否覆盖 | 低到中 | 低 | TS/JSX/CJS/resolve/minify 等常见能力 |
判断标准:如果插件只处理某些扩展名、目录或模块类型,就写 filter;如果插件依赖所有模块信息,则保留全量 hook,但要接受性能成本。
dynamic import、chunk、Tree Shaking:默认切分不等于最佳切分
Rolldown 的自动代码分割规则可以从三个 chunk 类型理解:
| chunk 类型 | 来源 | 示例 |
|---|---|---|
| entry chunk | 用户 input 直接指定的入口 | src/main.ts |
| dynamic chunk | import() 形成的异步边界 | src/detail.ts |
| common chunk | 被多个 entry 静态共享的模块 | 多入口应用共享的 vendor 或工具模块 |
共享源码的动态导入:
button.addEventListener('click', async () => {
const { renderDetail } = await import('./detail');
renderDetail(panel);
});预期结果是 manifest.json 中能看到:
{
"src/main.ts": {
"isEntry": true
},
"src/detail.ts": {
"isDynamicEntry": true
}
}Tree Shaking/DCE 的判断不是“没被 import 就删除”这么简单。Rolldown 官方 DCE 文档给出的核心条件是:值未被使用,并且删除不会改变程序行为。共享源码提供两个观察点:
export const removableMarker = 'SHOULD_NOT_REACH_BUNDLE';globalThis.__BUILD_TOOLS_SIDE_EFFECT__ = 'preserved';预期结果:
SHOULD_NOT_REACH_BUNDLE被删除。preserved保留,因为side-effect.ts有顶层副作用。- CSS 不能被当成纯模块误删。
manual code splitting 适合控制缓存和加载策略,但会引入执行顺序风险。Rolldown 文档也提醒,自动/手动分块都要保持 singleton 和执行语义;手动 groups 还可能生成 runtime chunk 来保证顺序。
严重问题完整流程:
| 症状 | 根因 | 观察命令 | 修正 |
|---|---|---|---|
| 点击按钮才需要的模块进了 initial chunk | 插件或 manual group 把 dynamic dependency 合并到入口 | 查 manifest.json 和 chunk 文件 | 移除过宽 manual group,保留 dynamic import 边界 |
| CSS 或 polyfill 消失 | side effect 判断过激 | 搜索 CSS asset、preserved、运行页面 | 保守处理 CSS/副作用模块,必要时显式标记 |
| manual chunk 后运行顺序变了 | common/manual chunk 提前或延后了副作用模块 | 浏览器控制台和最小复现 | 减少手动分块,或使用执行顺序相关配置并写回归测试 |
CSS、manifest、HTML 与 bundle analyzer:独立 Rolldown 要自己定义应用产物
本仓库的 Rolldown 示例刻意不依赖 Vite,而是展示底层 bundler 如何补齐浏览器产物。
运行:
pnpm --filter @roadmap/build-tools-slice build:rolldown预期结果:
| 文件 | 用途 | 必须观察的事实 |
|---|---|---|
examples/build-tools/dist/rolldown/index.html | 浏览器入口 | 只引用相对路径的 entry JS 和 CSS |
examples/build-tools/dist/rolldown/manifest.json | 机器可读构建清单 | 包含 src/main.ts entry 和 src/detail.ts dynamic entry |
examples/build-tools/dist/rolldown/assets/*.js | entry/dynamic chunk | 包含必要运行时代码和业务代码 |
examples/build-tools/dist/rolldown/assets/styles.css | CSS asset | 来自 src/styles.css |
*.map | 调试映射 | JS map 包含 main.ts/detail.ts,CSS map 包含 styles.css |
自动验收:
node --test --test-name-pattern rolldown examples/build-tools/test/artifacts.test.mjsbundle analyzer 的思路不是必须依赖某个固定 UI。对独立 Rolldown 项目,有三种实用入口:
| 分析入口 | 能回答什么 | 成本 |
|---|---|---|
generateBundle 中遍历 bundle | entry/dynamic chunk、assets、文件大小、facade module | 最低,适合 CI 产物契约 |
manifest.json | 服务端模板、部署系统、async entry 可见性 | 低,但信息比完整 bundle 少 |
| Rollup/Vite 生态可视化插件 | 依赖图和体积可视化 | 中,需要确认 Rolldown 插件兼容性 |
最小 analyzer 插件:
function bundleAnalyzer() {
return {
name: 'bundle-analyzer',
generateBundle(_options, bundle) {
const report = Object.fromEntries(
Object.entries(bundle).map(([fileName, item]) => [
fileName,
{
type: item.type,
bytes: item.type === 'asset'
? String(item.source).length
: item.code.length,
},
]),
);
this.emitFile({
type: 'asset',
fileName: 'bundle-report.json',
source: `${JSON.stringify(report, null, 2)}\n`,
});
},
};
}CJS、external、platform 与兼容失败:Rollup 兼容不等于结果逐字相同
Rolldown 内置 CJS 支持,且解析基于 oxc-resolver,默认能覆盖很多过去需要 @rollup/plugin-node-resolve 和 @rollup/plugin-commonjs 的场景。但迁移仍要核对结果:
| 风险点 | 为什么会变 | 观察方式 | 修正 |
|---|---|---|---|
| CJS default/named interop | Rolldown largely follows esbuild semantics;旧 Rollup 插件链可能不同 | 写最小 CJS fixture,对比导入结果 | 给关键包加回归测试,必要时 external |
| Node built-ins | 浏览器目标默认不 polyfill Node built-ins | 搜索 node:、fs、path 报错 | 不在浏览器 bundle 引入 Node API;确需 polyfill 时显式 opt-in |
| external | 旧 Rollup 配置 external 函数可能依赖插件解析顺序 | 打印 external 命中和最终 import | 固定 peer/external 策略 |
| plugin private API | 插件依赖 Rollup 内部对象或 undocumented hook 行为 | 跑插件集成测试 | 换内置能力、更新插件或保留 Rollup |
| CSS/module type | 旧插件以 query 约定 CSS,Rolldown 可能采用 moduleTypes | 检查虚拟模块 id 和 CSS 输出 | 使用 moduleType 或兼容插件 |
external 示例:
const peerDependencies = new Set(['react', 'react-dom']);
export default {
input: 'src/index.ts',
external(id) {
const packageName = id.startsWith('@')
? id.split('/').slice(0, 2).join('/')
: id.split('/')[0];
return peerDependencies.has(packageName);
},
output: {
format: 'esm',
dir: 'dist',
},
};严重问题完整流程:
| 症状 | 根因 | 观察命令 | 修正 |
|---|---|---|---|
浏览器报 process is not defined | Node/SSR 依赖进入 browser bundle | 搜索产物 process.、node: | 设置 platform/define,或替换依赖 |
| 升级后某 CJS 包默认导出变了 | CJS interop 语义差异 | 对该包写 import fixture | external 该包,或调整导入方式 |
| 可视化插件报错 | 插件依赖 Rollup 内部 API | 最小配置只跑该插件 | 更新插件、换 analyzer 或用 generateBundle 自建报告 |
迁移与重大变化:Rolldown 1.x、Vite 8 和 Rollup 项目要分开看
Rolldown 官方 getting started 当前显示 latest release channel 为 1.x.x,本仓库使用 rolldown 1.1.3。对工程迁移来说,至少有三条线:
| 迁移线 | 重大变化 | 建议 |
|---|---|---|
| Rollup → Rolldown | Rust core、Oxc resolver/transform/minify、内置 CJS/TS/JSX、hook filter、moduleTypes | 先迁移可观测小包;保留 output fixture、CJS fixture、CSS fixture 和插件集成测试 |
Vite 7/rolldown-vite → Vite 8 | Vite 8 使用 Rolldown 作为统一 Rust bundler,并提供兼容层转换部分 esbuild/Rollup 配置 | 大项目先在 Vite 7 用 rolldown-vite 隔离 bundler 问题,再升 Vite 8 |
| 独立 Rolldown 1.x | 直接使用 CLI/API/config,自己负责 HTML、CSS、manifest、预览和部署链路 | 应用项目若需要 dev server/HMR,优先通过 Vite 使用 |
Vite 8 release 给出的关键信息:
- Vite 8 把过去 esbuild + Rollup 双 bundler 路径收敛到 Rolldown。
- 迁移目标是性能、兼容和统一工具链,而不是取消 Vite 上层职责。
- 多数项目可能无需大量配置变更,但复杂插件、框架和边缘模块语义仍要回归。
- Vite 8 要求 Node.js 20.19+ 或 22.12+。
不要把 Vite 8 的能力倒推给独立 Rolldown:
| 能力 | 独立 Rolldown | Vite 8 |
|---|---|---|
| 生产打包 | 是 | 是,底层使用 Rolldown |
| dev server | 否 | 是 |
| HMR | 否 | 是 |
| HTML 开发时 transform | 否 | 是 |
| framework plugin orchestration | 只作为底层插件 API | 是 |
| manifest/HTML | 可通过插件实现 | Vite build 内置支持 |
native binary、Wasm fallback 和插件供应链
Rolldown 使用 native binary 发布。官方 getting started 列出了 Tier 1/Tier 2/Experimental 平台,并说明无预构建平台时可以使用 Wasm build 或从源码构建。
工程边界:
| 边界 | 风险 | 控制措施 |
|---|---|---|
| native binary | CI/开发机架构不一致,optional package 缺失 | 固定 Node/pnpm;保留 lockfile;在 CI 打印 rolldown --version |
| Wasm fallback | 可用性更好,但性能/线程能力可能不同 | 把 fallback 当兼容路径,不把性能结果混在一起比较 |
| JavaScript plugins | 仍以 Node 权限执行,可读取文件和环境变量 | 审查构建依赖;PR CI 不暴露生产 secrets |
| analyzer/HTML/CSS 自定义插件 | 影响产物正确性和安全 headers | 对 HTML、相对路径、Source Map、CSS 做 artifact test |
缓存和供应链建议:
node-version
pnpm-lock-hash
rolldown-version
native-platform
config-hash
plugin-version
target-env严重问题完整流程:
| 症状 | 根因 | 观察命令 | 修正 |
|---|---|---|---|
| CI 中 Rolldown native package 加载失败 | 平台包未安装或架构不支持 | pnpm --filter @roadmap/build-tools-slice exec rolldown --version | 检查 optional dependency、平台支持;必要时配置 Wasm fallback |
| 本地 benchmark 和 CI 差异巨大 | native vs Wasm、CPU、缓存、Source Map、插件数量不同 | benchmark 输出环境信息 | 分开记录环境,不写无条件性能排名 |
| 构建插件泄露 secret | 插件拥有 Node 权限 | 检查插件源码和 CI env | 最小化 secrets,新增构建插件走审查 |
共享示例:用同一份输入验证 Rolldown 的底层打包能力
本仓库示例不通过 Vite,直接运行 Rolldown CLI:
pnpm --filter @roadmap/build-tools-slice build:rolldown配置特征节选:
import { defineConfig } from 'rolldown';
export default defineConfig({
input: 'src/main.ts',
plugins: [
cleanOutput(),
cssAsset(),
manifest(),
html(),
],
output: {
dir: 'dist/rolldown',
format: 'esm',
sourcemap: true,
minify: true,
entryFileNames: 'assets/[name]-[hash].js',
chunkFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash][extname]',
codeSplitting: true,
},
});预期输出:
dist/rolldown/index.html:包含 entry script 和 stylesheet,路径是相对路径。dist/rolldown/manifest.json:记录src/main.ts和src/detail.ts。dist/rolldown/assets/*.js:包含 initial chunk 和 dynamic chunk。dist/rolldown/assets/styles.css:由示例 CSS 插件输出。- Source Map:JS map 可追溯
main.ts、detail.ts,CSS map 可追溯styles.css。
自动验收:
node --test --test-name-pattern rolldown examples/build-tools/test/artifacts.test.mjs这组验收刻意覆盖底层 bundler 应该负责的证据:resolve/transform、chunk、Tree Shaking、CSS asset、manifest、HTML、Source Map。它不测试 Vite HMR,因为那不是 Rolldown 独立职责。