Rolldown viteManifestPlugin 插件指南:在 Vite 构建中生成 manifest.json 资产映射
2026/9/15 13:14:44 网站建设 项目流程

Rolldown viteManifestPlugin 插件指南:在 Vite 构建中生成 manifest.json 资产映射

【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown

导读

viteManifestPlugin是 Rolldown 内置(builtin)插件之一,专门用于在构建结束后生成一份manifest.json,将源码中的原始文件名映射到最终产出的 chunk 与 asset 文件。它由 Vite 的manifestPlugin移植而来,核心价值在于服务端渲染(SSR)时的资源注入与构建产物的清单管理。读完本文,你将掌握该插件的引入方式、两个核心配置项(root/outPath)的用法、manifest 输出的字段结构,以及它在 Rolldown 内部的实现原理与测试验证方式。

插件定位:为 Vite 而生,为 SSR 服务

该插件位于仓库的 crates/rolldown_plugin_vite_manifest/ 目录,其官方说明(README.md)开宗明义:这是一个为vite生成manifest.json(将原始文件名映射到产出的 assets/chunks)的插件,移植自 Vite 的manifestPlugin

其核心工作内容是:收集构建产生的所有 chunk 与 asset,将它们与源码中的原始名称关联起来,最终输出一份 manifest JSON。这份清单文件在以下场景中尤为实用:

  • 服务端渲染(SSR):服务端需要根据入口模块名定位对应的打包产物(如入口 chunk、其依赖的动态导入 chunk 与样式文件),从而在 HTML 中注入正确的<script>/<link>标签;
  • 资源注入:前端框架或构建编排系统可以通过 manifest 反向查找某个原始资源文件被编译成了哪个最终文件。

值得注意的是,README 中有一则明确提示(> [!NOTE]):

该插件仅面向vite使用,不推荐外部单独使用;其 API 可能在 Rolldown 的 minor 版本之间发生变化,但在同一个 minor 版本内保持兼容。

这意味着如果你想在自己的非 Vite 构建脚本中稳定使用它,需要自行承担 API 变动的风险;同时由于插件深度依赖 Vite 生态约定(如isLegacycssEntries等回调语义),脱离 Vite 环境使用价值有限。

快速上手:Debug 用法示例

README 中给出了一份可直接运行的调试用法示例,完整继承了原文的配置骨架。在项目的入口配置(如rolldown.config.js)中引入:

import { defineConfig } from 'rolldown'; import { viteManifestPlugin } from 'rolldown/experimental'; export default defineConfig({ input: { entry: './main.ts', }, plugins: [ viteManifestPlugin({ root: path.resolve(import.meta.dirname), outPath: path.resolve(import.meta.dirname, 'dist/manifest.json'), }), ], });

这段配置的关键信息有三点:

  1. 插件入口路径viteManifestPluginrolldown/experimental导出(对应源码 packages/rolldown/src/experimental-index.ts),表明其当前处于 experimental 阶段,API 可能随 minor 版本演进;
  2. root必须显式提供:插件需要以项目根目录为基准,把模块的绝对路径换算成相对路径作为 manifest 的 key(详见下文实现解析);
  3. outPath决定输出位置:manifest 最终会作为一份 emit 资源写入该路径。

配置项一览

README 中的 Options 表格完整如下:

OptionTypeDescription
rootstringProject root directory
outPathstringWhere to write the manifest output file

root:项目根目录

作为 manifest key 的计算基准。实现中,root被用于把 chunk 的facade_module_id(入口模块绝对路径)裁剪为相对路径,从而得到稳定、可读的清单键名(见 src/utils.rs 中get_chunk_namemodule_id.relative_path(&self.root)的调用)。因此,传入不一致的root会直接导致 manifest 中的 key 与源码路径对不上号,务必使用与构建入口一致的绝对路径。

outPath:manifest 输出文件路径

manifest 的写入位置。它在插件内部并非直接落盘,而是通过emit_file_asyncEmittedAsset的形式交还给打包器统一输出(见 src/lib.rs),这样能够复用 Rolldown 的产物发射机制,与其他 emit 文件保持一致的命名与写入流程。

绑定层的额外选项(JS 侧可见)

在 Rust 绑定层,配置结构还包含两个回调字段(见 crates/rolldown_binding/src/options/plugin/config/binding_vite_manifest_plugin_config.rs):

  • is_legacy(JS 侧isLegacy):(options) => boolean形式的回调,用于判断当前输出是否针对 legacy 构建。为true时,chunk 名会被追加-legacy后缀(实现见 utils.rs),这是 Vite legacy 插件生态的常见约定;
  • css_entries(JS 侧cssEntries):() => Record<string, string>形式的回调,返回「css 产物文件名 → 入口模块名」的映射,用于在 manifest 中为 asset 标注其所属的 CSS 入口(见 lib.rs)。

这两个回调是 Vite 侧集成时由 Vite 包装层注入的,普通调试场景可像测试用例那样传入空实现(见下文测试章节)。

manifest 输出格式详解

插件输出的 JSON 主体由ManifestChunk结构序列化而来,字段采用 camelCase 命名,并且大量使用skip_serializing_if省略空字段,让最终清单保持精简(定义见 src/utils.rs):

字段类型含义何时出现
filestring最终产出的文件名始终
namestringchunk 名(如入口main有值时
namesstring[]asset 的完整名称列表有值时
srcstring原始源文件路径(相对rootchunk 有facade_module_id
isEntryboolean是否为入口 chunktrue时输出
isDynamicEntryboolean是否为动态入口 chunktrue时输出
importsstring[]静态依赖的 chunk 名列表非空时输出
dynamicImportsstring[]动态 import 的 chunk 名列表非空时输出
cssstring[]关联的 CSS 文件预留字段(当前固定为None
assetsstring[]关联的 asset 文件预留字段(当前固定为None

一个真实的输出样例来自仓库测试快照 packages/rolldown/tests/fixtures/builtin-plugin/manifest/manifest.json.snap:

{ "asset.txt": { "file": "asset.txt", "src": "asset.txt" }, "chunk.js": { "file": "chunk.js", "isDynamicEntry": true, "name": "chunk", "src": "chunk.js" }, "main.js": { "dynamicImports": ["chunk.js"], "file": "main.js", "isEntry": true, "name": "main", "src": "main.js" } }

可以直观看到:manifest 的 key 是源码路径(如main.js),而值中file指向最终产物(main.js),并且通过imports/dynamicImports完整描述了 chunk 之间的依赖拓扑。

底层实现原理

挂钩点:generate_bundle

插件注册了唯一一个 hook:generateBundle(见 src/lib.rs 的register_hook_usage),插件内部名称为builtin:vite-manifest(lib.rs),该名称也注册在绑定层的 binding_builtin_plugin_name.rs 中,JS 侧通过 vite-manifest-plugin.ts 的new BuiltinPlugin('builtin:vite-manifest', config)与之对应。这意味着整个插件以 Rust 原生实现运行,没有额外的 JS 插件调度开销。

输出排序:BTreeMap

在 generate_bundle 中,插件用BTreeMap收集 manifest 条目,从而保证最终 JSON 的 key 按字典序排列、输出稳定可复现:

// Use BTreeMap to make the result sorted let mut manifest = BTreeMap::default();

随后遍历bundle中的每个Output,按 chunk 与 asset 分派处理。

Chunk 的命名规则

对每个Output::Chunk,通过get_chunk_name计算 manifest key(utils.rs):

  • 若 chunk 存在facade_module_id(即它是某个模块的包装入口),则取该模块相对root的路径作为名称,并做normalize_path归一化、剔除\0字符;
  • 若没有facade_module_id(内部拆分出来的 chunk),则以_+ 产物文件名的形式兜底命名。

同时,create_chunk会通过get_internal_imports把 chunk 的静态importsdynamic_imports映射为「内部 chunk 名列表」——只有当依赖文件能在当前 bundle 中命中同名 chunk 时才保留,天然过滤掉 external 的外部模块。

Asset 的处理与「JS 优先」去重策略

Output::Asset,插件会把每个唯一的 asset 按原始文件名写入 manifest;若 asset 没有original_file_names,则退化为_+ 文件名。这里有一个值得注意的细节(lib.rs):

如果 JS chunk 与 asset chunk 由同一个源文件生成,优先保留 JS chunk,因为它包含更丰富的信息(isEntryimportsdynamicImports等)。

这一点由工具函数is_non_js_file(utils.rs)保证:只有当当前已存在的 manifest 条目对应的产物不是.js/.cjs/.mjs时,asset 才会覆盖写入;否则跳过。这避免了「同一源文件既产出 JS 又产出 asset 时 key 冲突被错误覆盖」的问题。

输出与未来规划

最后,manifest 以serde_json::to_string_pretty的美化 JSON 格式,通过emit_file_async发射为EmittedAsset(文件名为outPath)lib.rs。源码中还保留了一段注释掉的逻辑(TODO: uncomment these when multiple outputs are supported,对应 Vite 侧对rollupOptions.output数组长度的判断),说明当前实现假定单输出场景,多输出(multiple outputs)支持仍在规划中。

测试验证:一个可复现的闭环

仓库在 packages/rolldown/tests/fixtures/builtin-plugin/manifest/_config.ts 提供了一个完整的集成测试夹具,可作为自定义验证的最小模板:

import path from 'node:path'; import { defineTest } from 'rolldown-tests'; import { viteManifestPlugin } from 'rolldown/experimental'; import { expect } from 'vitest'; export default defineTest({ sequential: true, config: { output: { chunkFileNames: '[name].js', assetFileNames: '[name][extname]', }, plugins: [ viteManifestPlugin({ root: path.resolve(import.meta.dirname), outPath: 'manifest.json', cssEntries: () => Object.fromEntries(new Map().entries()), }), { name: 'test', buildStart() { this.emitFile({ type: 'asset', name: 'asset.txt', source: 'hello world', originalFileName: 'asset.txt', }); }, }, ], }, async afterTest() { const manifest = await import('./dist/manifest.json'); await expect(manifest.default).toMatchFileSnapshot( path.resolve(import.meta.dirname, 'manifest.json.snap'), ); }, });

该测试同时覆盖了三条核心行为,值得在接入该插件时重点回归:

  1. 入口 chunk 记录main.jsisEntry: truedynamicImports: ["chunk.js"]呈现;
  2. 动态入口 chunk 记录:动态导入产生的chunk.js被标记为isDynamicEntry: true
  3. emit 的 asset 记录:通过emitFile发射的asset.txt以原始文件名出现在 manifest 中,且没有多余的空字段(names等被省略)。

使用注意事项小结

  • 仅在 Vite 生态内使用:README 明确不推荐外部使用,且 API 在 minor 版本间可能变动,升级 Rolldown 时需关注 changelog;
  • rootoutPath建议传绝对路径:前者决定 manifest key 的相对路径基准,后者决定产物写入位置,两者语义不同但都影响最终清单的正确性;
  • manifest key 可能包含非 JS 产物:asset 条目与 chunk 条目共存,且同一源文件的 JS 产物优先级更高,解析清单时应以file字段为准而非假设 key 即源码路径;
  • 当前为单输出假设:多输出配置(output数组)尚未支持,源码中相关逻辑仍处于 TODO 状态。

通过这份指南,你可以快速在自己的 Rolldown + Vite 项目中接入viteManifestPlugin,并结合上文给出的源码路径与测试夹具,深入理解乃至扩展这份 manifest 生成能力。

【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询