跳转到内容

Rollup 打包与许可证管理

Rollup 打包与许可证管理

学习目标

本章分析 Chrome DevTools MCP 的构建与打包策略。你将理解:

  • 为什么需要 Rollup bundle(而非直接使用 TypeScript 编译结果)
  • 多 bundle 入口设计:third_party/index.jsdevtools-formatter-worker.jsdevtools-heap-snapshot-worker.js
  • inlineDynamicImports: true 的必要性
  • 许可证自动校验(白名单 + failOnViolation
  • Lighthouse 单独 bundle 的原因
  • chrome-devtools-frontend 第三方库的手动许可证处理

项目实践

为什么需要 Bundle?

Chrome DevTools MCP 依赖以下大型库:

依赖体积特点
@modelcontextprotocol/sdkMCP 协议实现
puppeteer-core浏览器自动化
lighthouse很大性能审计引擎
chrome-devtools-frontend很大DevTools 前端代码

直接使用 tsc 编译后,node_modules 需要随 npm 包一起发布。Bundle 的作用:

  1. 减小体积:只打包实际使用的代码(tree-shaking)
  2. 消除外部依赖:bundle 后 node_modules 不需要随包发布
  3. 许可证合规:自动收集所有依赖的许可证信息

多 Bundle 入口

rollup.config.mjs 定义了 3 个 bundle:
1. third_party/index.js
→ MCP SDK + Puppeteer + Lighthouse + 其他依赖
→ 主 bundle,inlineDynamicImports: true
2. devtools-formatter-worker.js
→ DevTools 格式化器 Web Worker
→ 独立的 Worker 环境
3. devtools-heap-snapshot-worker.js
→ DevTools 堆快照分析 Web Worker
→ 独立的 Worker 环境

inlineDynamicImports 的必要性

output: {
inlineDynamicImports: true,
format: 'esm',
}

inlineDynamicImports: true 将动态导入(import())内联到主 bundle 中。

为什么必须:MCP Server 通过 stdio 通信,如果 dynamic import 触发文件加载,可能因路径问题失败。内联后所有代码在同一个 bundle 中。

许可证自动校验

license({
thirdParty: {
allow: {
test: dependency => allowedLicenses.includes(dependency.license),
failOnUnlicensed: true,
failOnViolation: true,
},
output: {
file: './build/src/third_party/THIRD_PARTY_NOTICES',
// 收集每个依赖的名称、URL、版本、许可证、许可证文本
},
},
});

白名单

const allowedLicenses = [
'MIT', 'Apache 2.0', 'Apache-2.0',
'BSD-3-Clause', 'BSD-2-Clause', 'ISC', '0BSD',
];

如果依赖使用了非白名单许可证(如 GPL),构建会直接失败,确保发布的 npm 包不含许可证风险。

自定义依赖收集插件

listBundledDeps() — 自定义 Rollup 插件:
1. 遍历 bundle 中所有模块
2. 通过文件路径反推所属 npm 包
3. 收集所有被打包的依赖名称
4. 生成 bundled-packages.json

这个信息用于 npm 包的元数据,告知用户哪些依赖已被内嵌。

Lighthouse 单独 Bundle

Lighthouse 被单独打包为 lighthouse-devtools-mcp-bundle.js,原因:

  • Lighthouse 体积大,单独 bundle 可按需加载
  • 与 DevTools 前端有特殊集成(自定义 snapshot/navigation/generateReport 入口)
  • 通过 third_party/index.js 的 external 配置排除,避免重复打包

chrome-devtools-frontend 手动许可证处理

chrome-devtools-frontend 目录下有自己的第三方库(如 CodeMirror、marked 等),这些库不在 node_modules 中,因此 Rollup 的 license 插件无法自动检测。

处理方式

// 遍历 tsconfig.json 中 include 的 third_party 目录
const thirdPartyDirectories = tsConfig.include.filter(
location => location.includes('chrome-devtools-frontend/front_end/third_party')
);
// 为每个目录读取 LICENSE 文件并追加到 THIRD_PARTY_NOTICES

构建流程

设计取舍

取舍选择原因
Rollup vs Webpack vs esbuildRollup对 ESM 支持好,适合库打包
单一 bundle vs 多 bundle多 bundleWorker 需要独立环境,Lighthouse 按需加载
严格许可证 vs 宽松严格Google 发布标准,避免法律风险
tree-shaking vs 全量tree-shakingRollup 自动去除未使用代码

陷阱与对策

陷阱后果对策
新依赖使用了非白名单许可证构建失败添加依赖前检查许可证
inlineDynamicImports: falsestdio 模式下动态加载失败必须设为 true
忘记更新 bundled-packages.json用户不知道哪些依赖被内嵌自定义 listBundledDeps() 自动生成

参考来源