Vite 致谢页全解析:依赖鸣谢清单如何由 LICENSE.md 与 package.json 自动生成
Vite 官方文档中的致谢页(Acknowledgements)并不是一份人工维护的静态名单——页面上的依赖包、作者与赞助信息全部由 VitePress 数据加载器在构建时从仓库文件自动读取和聚合。本文沿这条完整的"许可证汇总 → LICENSE.md → 致谢页"流水线逐环节展开,并解释 Vite "绝大多数依赖放入 devDependencies 再预打包"的依赖策略,帮助你在阅读这份鸣谢清单的同时,理解 Vite 核心包是如何保持轻量的。
一、致谢页包含什么:人、资金与包的完整清单
致谢页的页面结构由四个板块组成,这也是本文的骨架:
- Contributors(贡献者):Vite 由一个国际化团队开发,核心成员见团队页;同时鸣谢所有通过代码、Bug 报告、文档及文档翻译帮助改进 Vite 的贡献者。
- Sponsors(赞助者):页面通过 VitePress 主题提供的
useSponsor可组合函数和VPSponsors组件动态渲染赞助商列表,支持渠道为 GitHub Sponsors 与 Open Collective。 - Dependencies(依赖):又分两部分——Notable Dependencies(重点依赖,以卡片形式展示作者、描述与仓库/赞助链接)和 Bundled Dependency Authors(被打包进发行产物的依赖的作者汇总表,按作者归组)。
- Development Tools(开发工具):支撑 Vite 自身开发工作流的工具清单。
- Past Notable Dependencies(历史重点依赖):记录 Vite 早期版本使用、如今已被替换的项目。
页面本身只是"展示层"(docs/acknowledgements.md 中大量使用 v-for 循环渲染数据数组),真正决定页面内容的是下一节的数据源文件。
二、数据源:一个构建期读取 node_modules 的 VitePress 数据加载器
致谢页所有动态数据来自 docs/_data/acknowledgements.data.ts。这个文件是一个标准的 VitePress data loader(导出 { watch, load }),构建文档时执行,其工作分三步:
1. 静态名单 + 解析 LICENSE.md 得到"被打包依赖"全集。 文件开头硬编码了三份名单(acknowledgements.data.ts#L5-L20):
// Notable dependencies to highlight (by package name)
const notableDependencies = [
'rolldown',
'postcss',
'lightningcss',
'chokidar',
'magic-string',
]
// Dev tools used for development
const devToolNames = [
'eslint',
'oxfmt',
'typescript',
'vitest',
'playwright-chromium',
]
而"Bundled dependencies"并不在这里写死,而是由 parseBundledDependenciesFromLicense()(acknowledgements.data.ts#L113-L126)从 packages/vite/LICENSE.md 中解析:
const bundledSection = content.split('# Bundled dependencies:\n')[1]
// Match all ## headers which contain package names (comma-separated for grouped packages)
const deps = [...bundledSection.matchAll(/^## (.+)$/gm)].flatMap((m) =>
m[1].split(',').map((n) => n.trim()),
)
注意 split(',') 这一步:LICENSE.md 中的 ## 标题可能是"pkg1, pkg2, pkg3"这样的逗号分组形式(例如当前的 ## braces, fill-range, is-number、## mlly, ufo),解析器必须按逗号拆开才能还原出真实包名——这个细节与第三节 LICENSE.md 的生成逻辑严格对应。
2. 逐包读取 node_modules 中的 package.json。
readPackageInfo()(acknowledgements.data.ts#L195-L219)依次尝试 packages/vite/node_modules/<name>/package.json 和仓库根 node_modules/<name>/package.json,提取 name、version、description、author、repository、funding 字段。包不存在时返回 null 并被过滤——注释说明原因:某些包可能只是可选 peer 依赖,并未真正安装。配套的两个归一化函数覆盖了 npm 元数据的各种"方言":
normalizeRepository()(acknowledgements.data.ts#L128-L159):将git+https://...、ssh://user@host.com:org/repo.git、github:org/repo、gitlab:、bitbucket:以及裸的owner/repo等形式统一成可用的 HTTPS 链接;parseAuthor():同时支持对象形式和字符串形式("Name <email> (url)"),拆出姓名与个人主页;normalizeFunding():funding字段无论是字符串、对象还是数组,统一取第一个 URL。
3. 按作者归组,生成"Bundled Dependency Authors"表。
groupByAuthor()(acknowledgements.data.ts#L221-L264)把非重点的打包依赖(bundledDependencies 中排除掉 notableDependencies 后的部分)按 author 字段分桶,包名与作者均按字母序排序。有一个展示层面的优化:若某作者名下所有包的 funding URL 完全一致,则把赞助链接提升到作者级别,包列表里就不再逐个重复显示 Sponsor 按钮。
最后,loader 通过 watch: ['../../packages/vite/LICENSE.md'](acknowledgements.data.ts#L314-L319)声明了对 LICENSE.md 的监听——依赖集合变化导致 LICENSE.md 重新生成后,文档构建会自动重建该页数据,整个链路因此是自我同步的。
三、流水线上游:LICENSE.md 本身是构建时自动生成的
packages/vite/LICENSE.md 并非手写。它的生成逻辑在 packages/vite/rollupLicensePlugin.ts:一个包装 rollup-plugin-license 的 thirdParty 钩子的构建插件,做四件事:
- 读取核心许可证:以仓库根 LICENSE(MIT)作为 "Vite core license" 段落写入文件开头;
- 排序与分组(rollupLicensePlugin.ts#L22-L43):依赖按名称排序;许可证全文相同的依赖被合并成一个
## pkg1, pkg2, pkg3分组标题——这正是第二节数据加载器要按逗号切分的来源。若同组依赖的许可证与作者也完全一致,License:/By:/Repositories:元信息只打印一次; - 输出汇总:在
# Licenses of bundled dependencies段落中列出产物包含的全部许可证类型(当前为 Apache-2.0、BSD-2-Clause、CC0-1.0、ISC、MIT),随后是# Bundled dependencies:明细区; - 落盘并提醒提交(rollupLicensePlugin.ts#L108-L116):只有内容变化时才写回
LICENSE.md,并在终端打印黄色警告 "LICENSE.md updated. You should commit the updated file."。插件还覆盖了renderChunk/generateBundle钩子,在 watch 模式下直接跳过,避免开发期反复写文件。
该插件挂载在 packages/vite/rolldown.config.ts 的 nodeConfig 上(rolldown.config.ts#L134-L138):
plugins: [
shimDepsPlugin({ /* ... */ }),
buildTimeImportMetaUrlPlugin(),
licensePlugin(
path.resolve(dirname, 'LICENSE.md'),
'Vite core license',
'Vite',
),
// ...
]
这个 nodeConfig 打包 src/node/index.ts、src/node/cli.ts、src/node/internalIndex.ts 三个入口,并把 pkg.dependencies 与 pkg.peerDependencies 的全部键名标记为 external(rolldown.config.ts#L86-L99)——也就是说真正被内联进 dist 的只有 devDependencies,而 rollup-plugin-license 在生成报告时恰好只统计"实际被打包进来"的依赖,于是 LICENSE.md 的内容就精确等于"发行产物中包含的第三方代码",致谢页也因此得名 "Bundled" dependencies。
四、为什么有这么多"Bundled Dependencies":Vite 的依赖瘦身策略
packages/vite/package.json 末尾有这样一条注释,堪称理解致谢页的前提:
"//": "READ CONTRIBUTING.md to understand what to put under deps vs. devDeps!"
CONTRIBUTING.md 的 "Notes on Dependencies" 一节给出了完整规则:
- 目标:Vite 追求轻量,包括对 npm 依赖数量和体积的敏感;
- 核心机制:"We use Rolldown to pre-bundle most dependencies before publishing"——发布前用 Rolldown 把大部分依赖预打包进产物,因此即使某个依赖在运行时源码中被使用,默认也应放进
devDependencies; - 例外(必须进
dependencies的情况):类型包(@types/*);含二进制文件无法被打包的依赖(如rolldown、lightningcss);其自带类型会出现在 Vite 公开类型中的依赖(如rolldown); - 约束:由于 devDependencies 打包后从产物中"消失",源码中不能用普通
require('somedep')(ESM 中会被忽略、发布后也找不到),而要写成(await import('somedep')).default的懒加载形式,兼顾启动性能与打包正确性; - 体积纪律:CONTRIBUTING 举了一个真实案例——
http-proxy本身约 380 kB,而http-proxy-middleware会拖入约 3 MB 的传递依赖,相比之下在http-proxy之上写几行自定义中间件即可,这也是 Vite 选择自研中间件的原因。
对照 packages/vite/package.json#L71-L77 可以看到策略的实际结果——vite 的运行时 dependencies 只有 5 个:
"dependencies": {
"lightningcss": "^1.33.0",
"picomatch": "^4.0.7",
"postcss": "^8.5.26",
"rolldown": "~1.2.6",
"tinyglobby": "^0.2.17"
}
外加 optionalDependencies 中的 fsevents(macOS 原生监听)。而 devDependencies 里则躺着 chokidar、magic-string、es-module-lexer、sirv、connect 等三十多个"运行时也用"的包——它们全部会被打进 dist/node,并因此出现在 LICENSE.md 与致谢页中。
类型方面还有一个配套机制:为了让 Vite 能在 TypeScript 项目中被完整引用(例如供 VitePress 使用),需要把部分依赖的类型内联到 packages/vite/src/types,然后用 pnpm run build-types-check 校验打包后的类型不依赖任何 devDependencies。此外,rolldown.config.ts 中还有一个 bundleSizeLimit(55) 插件(rolldown.config.ts#L388-L416),当 module-runner 产物超过约 55 kB 时直接令构建失败——"轻量"在这里是硬约束,不只是口号。
五、Notable Dependencies 与开发工具
数据文件中的两份静态名单对应页面两组卡片:
Notable Dependencies:rolldown、postcss、lightningcss、chokidar、magic-string。它们在源码中的位置可以从 import 关系直接印证:
rolldown是当前 Vite 的构建与转换引擎:从 optimizer/scan.ts、optimizer/index.ts(依赖预构建)到 build.ts、pluginContainer.ts、hmr.ts 等几十个核心模块都直接从rolldown导入插件与类型;magic-string用于精确的源码改写,Vite 自己的构建配置就用它实现shimDepsPlugin与buildTimeImportMetaUrlPlugin(rolldown.config.ts#L229-L299);postcss是 CSS 处理管线核心,配合postcss-import、postcss-load-config、postcss-modules等(均在 devDependencies 中,打包时通过shimDepsPlugin剔除冗余 import,见 rolldown.config.ts#L101-L132 的注释);lightningcss用于 CSS 压缩等原生加速路径;chokidar是文件监听基础,仓库根 patches/ 目录中还维护着chokidar@3.6.0.patch等 pnpm patch,说明 Vite 会直接修补依赖行为而非整体替换。
Development Tools:eslint、oxfmt、typescript、vitest、playwright-chromium,与根 package.json 的 devDependencies 一一对应。仓库脚本体现了它们的分工:pnpm lint(eslint 9 + typescript-eslint)、pnpm format(oxfmt,同时由 lint-staged 在 pre-commit 时执行)、pnpm typecheck(tsc 多项目配置)、pnpm test(vitest 单元测试 + 基于 Playwright 的 test-serve/test-build 集成测试,测试目标即 playground/ 下上百个场景目录)、pnpm docs(VitePress 构建本文所在文档站)。
六、Past Notable Dependencies:一份引擎演进的对照记录
acknowledgements.data.ts#L23-L55 中的 pastNotableDependencies 列出了 Vite 曾经依赖、如今已替换或移除的六个项目,其"replacement 备注"恰好勾勒出 Vite 底层的演进轨迹:
| 包 | 用途 | 现状(据数据文件备注与 package.json 印证) |
|---|---|---|
esbuild |
JS/TS 打包与压缩 | 描述为 "now using Rolldown, Oxc, and LightningCSS";但 esbuild 仍保留为可选 peerDependency 与 devDependency(^0.27.0 || ^0.28.0 / ^0.28.2),部分转换路径仍可能需要用户安装 |
rollup |
ESM 打包器 | "now using Rolldown";rollup 仍留在 devDependencies(^4.59.0),主要服务于构建工具链生态(如许可证插件 rollup-plugin-license,其类型即来自 rollup) |
http-proxy |
HTTP 代理 | "now using http-proxy-3",对应 devDependencies 中的 http-proxy-3: ^1.23.3 |
acorn |
JavaScript 解析器 | 已从依赖中移除(解析能力由 Oxc 体系承接,可参见 plugins/oxc.ts 的存在) |
fast-glob |
快速 glob 匹配 | "now using tinyglobby/fdir",对应运行时依赖 tinyglobby: ^0.2.17 |
debug |
调试日志 | "now using obug",对应 devDependencies 中的 obug: ^1.0.2 |
从源码结构看,"Rolldown 取代 esbuild/rollup"这一迁移已完成度很高:vite 的构建入口、模块图、HMR、SSR 转换等核心模块均直接面向 rolldown 的插件 API 编写,而不再经由 esbuild 或 rollup 中转。
七、给包作者的提示:你的 package.json 元数据决定你在致谢页的样子
原文档中有一个面向第三方包作者的说明,值得单独强调:
This section is automatically generated from the
authorandfundingfields in each package'spackage.json. If you'd like to update how your package appears here, you can update these fields in your package.
结合第二节的解析逻辑,其含义非常具体:如果你的包出现在 vite 的 devDependencies 中、被预打包进了 dist/node(从而进入 LICENSE.md 的 # Bundled dependencies: 区),那么该包在 package.json 中声明的 author(支持对象或 "Name <email> (url)" 字符串)将决定它是否以及以谁的名义出现在 "Bundled Dependency Authors" 表中,funding 字段(字符串、对象或数组的第一个 URL)则决定作者行/包名旁是否出现 Sponsor 链接;repository 字段会被 normalizeRepository() 归一化后展示。换言之,更新你所在包的 author 与 funding 字段,就是更新它在 Vite 致谢页呈现方式的唯一途径——而 Vite 侧的整条链路(构建生成 LICENSE.md → 数据加载器解析 → 页面渲染)无需任何人工介入。
小结
Vite 的致谢页看似一份静态鸣谢名单,实际是一条完整的自动化管线:构建时 rollupLicensePlugin 将预打包依赖的许可证汇总写入 LICENSE.md,文档构建时 acknowledgements.data.ts 再解析该文件并结合 node_modules 中的 package.json 元数据生成卡片与作者表。这条管线背后,是 Vite "运行时依赖仅 5 个、其余全部预打包"的依赖纪律(CONTRIBUTING.md)与对产物体积的硬约束。理解这套机制后,你可以把致谢页当作观察 Vite 依赖演进的窗口——包括 esbuild 到 Rolldown、http-proxy 到 http-proxy-3 这样已经落地的替换。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00