不少团队在项目上线前会收到法务或安全团队的质询:项目里用到的几百个 npm 包分别是什么开源协议?有没有附带协议声明?如果回答不上来,说明许可证合规这件事还没有进入构建流程。Webpack 5 作为目前主流的前端构建工具,虽然官方并没有内置一个叫 License Compliance 的独立模块,但它的模块图和 stats 机制让许可证信息的采集变得完全可行,配合社区插件即可在每次构建时自动产出完整的第三方依赖许可证报告。本文就来详细讲讲这套方案的原理与落地步骤。

为什么前端项目必须重视许可证合规
一个典型的中大型前端项目,直接依赖加上传递依赖,node_modules 目录里往往有两三百个包。每个包都有各自的开源协议,MIT、Apache-2.0 这类宽松协议商用基本没有障碍,但 GPL、AGPL 这类协议带有强传染性条款,一旦代码被静态链接进最终产物,整个产物可能被要求以相同协议开源。对于商业闭源产品来说,这是不可接受的风险。
合规的最低要求通常是:在发布产物中附带所有第三方依赖的许可证声明。很多协议(例如 MIT)明确要求在软件的所有副本中包含版权声明和许可文本。如果打包后的 JS 文件完全没有体现这些信息,严格来说已经构成了协议违反。虽然大多数开源作者并不会追责,但一旦产品涉及融资、并购或者被竞争对手盯上,这类历史遗留问题会被逐一翻出来。
另一个现实问题是依赖的不可控性。今天引入的是 MIT 协议的 A 包,下周 A 包发布新版本时可能悄悄改成了更严格的协议。如果没有自动化手段,这种变化几乎不可能被人工发现。因此许可证合规必须做成构建流程中一个自动执行、持续校验的环节,而不是上线前的一次性人工检查。
Webpack 5 中采集许可证信息的原理与方案选型
Webpack 5 在编译过程中会构建完整的模块依赖图,每个被引用的模块都可以通过 module.resource 定位到磁盘上的真实文件,进而找到对应包的 package.json 和 LICENSE 文件。Webpack 官方提供的 stats 输出也包含模块维度的信息,可以借助 stats.toString({ modules: true }) 或 Compiler Hook 拿到所有参与打包的模块列表,这是自动采集许可证信息的基础。
社区里最成熟的方案是 license-webpack-plugin。它基于 Webpack 的 compilation 钩子工作:在构建结束后遍历所有模块,读取每个第三方包 package.json 中的 license 字段,同时抓取包目录下的 LICENSE、LICENCE、COPYING 等文件内容,最终生成一份汇总产物。相比手动维护依赖清单,它的优势在于结果永远与真实打包产物一致——只有真正进入 bundle 的包才会出现在报告里,那些装了但没用上的依赖不会被误报。
另一种轻量做法是使用 license-checker 之类的 CLI 工具直接扫描 node_modules。这种方式不依赖构建工具,但缺点也很明显:它统计的是安装的所有依赖,而非实际打包的模块,报告会比真实情况偏大。如果团队同时维护多个构建工具,可以把 license-checker 放在 CI 做粗筛,把 license-webpack-plugin 放在 Webpack 构建里做精确报告,两者互为补充。
license-webpack-plugin 配置实战
先安装插件:
npm install license-webpack-plugin --save-dev
然后在 webpack.config.js 中注册。下面是一份针对 Webpack 5 的典型配置,包含输出文件名、错误告警、协议白名单等核心选项:
const LicensePlugin = require('license-webpack-plugin').LicensePlugin;
module.exports = {
plugins: [
new LicensePlugin({
// 输出的许可证汇总文件,会出现在构建产物目录中
outputFilename: 'third-party-licenses.txt',
// 遇到无法识别协议的依赖时直接报错,阻断构建
unacceptableLicenseTest: (licenseName) => {
// GPL 系列协议视为不可接受,构建直接失败
return /GPL/.test(licenseName);
},
// 补充一份结构化的 JSON 报告,便于脚本分析
supplementalFiles: {
'license-report.json': (packages) => JSON.stringify(
packages.map(pkg => ({
name: pkg.name,
version: pkg.version,
license: pkg.license,
homepage: pkg.homepage
}), null, 2)
)
}
})
]
};这段配置做了两件事:一是把所有依赖的许可证文本汇总到 third-party-licenses.txt,随产物一起发布即可满足大多数协议的署名要求;二是设置 unacceptableLicenseTest,一旦发现 GPL 系协议就让构建失败,把风险拦截在 CI 阶段。JSON 报告则可以提交给法务或安全平台做存档与周期性比对。
实际使用中经常会遇到两类异常。第一类是某个包的 package.json 没有 license 字段,插件默认会警告但不中断,可以用 licenseFiles 和 missingLicenseText 选项补充兜底文本,或者在 additionalPackages 中手动登记协议信息。第二类是 monorepo 场景下本地 link 的包没有标准结构,这时可以按需排除,并在代码仓库中单独维护一份内部包的许可证清单,避免报告出现空缺。
常见开源协议的风险分级与团队规范建议
做合规不能只靠工具,团队还需要一份清晰的协议分级标准。通常可以把常见协议分为三档:MIT、ISC、BSD、Apache-2.0 属于低风险,附带声明即可自由商用;LGPL、MPL-2.0、EPL 属于中风险,对修改和链接方式有额外要求,引入前需要架构评审;GPL-2.0、GPL-3.0、AGPL、SSPL 属于高风险,闭源商业项目原则上禁止引入。CC 协议中的非商业条款(NC)同样不适合商用产品。
建议把这套分级固化到 CI 流水线中:license-webpack-plugin 负责拦截高风险协议并生成报告,报告文件作为构建产物归档,每次发版时与上一版本做 diff,新增依赖的协议变化一目了然。同时在前端团队规范中明确一条纪律——新增依赖必须在代码评审时说明其协议类型,从源头减少风险包的引入。工具加规范双管齐下,许可证合规才不会流于形式,真正成为构建流程中一个安静运转的环节。