不少团队在升级到 Webpack 5 之后,把注意力都放在了持久化缓存、模块联邦这些大特性上,却很少有人注意到一个容易被忽视的角落:构建产物中的法定权利声明,也就是我们常说的版权与许可证注释。当你的项目里引用了几十个甚至上百个开源依赖时,每一个依赖自带的 LICENSE、MIT、Apache-2.0 等声明,都属于原作者的法定权利。Webpack 5 生态在压缩与注释处理策略上的变化,直接影响这些声明是否被正确保留,处理不当可能会给商业项目带来真实的合规风险。这篇文章就从配置层面入手,聊聊如何在 Webpack 5 的构建体系里把这件事做对。

一、什么是构建产物中的法定权利声明
开源不等于免费随意使用。绝大多数开源许可证,无论是宽松的 MIT、BSD,还是要求衍生作品同样开源的 GPL 系列,都包含一条共同要求:在分发软件副本时,必须保留原作者的版权声明和许可证文本。前端项目打包后的 bundle 本质上就是一次代码再分发,所以这些声明不是可有可无的装饰,而是法律层面的义务。
具体到产物文件里,法定权利声明通常以块注释的形式出现在文件头部或代码中间,比如一段典型的 MIT 声明:
/*! * lodash 4.17.21 * Copyright (c) JS Foundation and other contributors * Licensed under MIT */
注意注释开头的感叹号,这不是随手写的。在压缩工具的约定里,以感叹号开头的注释被称为法律注释,压缩器在默认情况下会保留它们,而普通注释则会被清除。Webpack 5 默认使用 TerserWebpackPlugin 作为压缩工具,它对这类注释的处理策略决定了最终产物中法定权利声明的去留。如果你在配置里手动关闭了保留选项,或者使用了某些激进的注释清除插件,就可能在不自知的情况下侵犯了原作者的法定权利。
二、Webpack 5 中控制注释保留的关键配置
Webpack 5 的压缩环节由 terser-webpack-plugin 负责,它底层调用 Terser,而 Terser 提供了 output.comments 配项来控制注释处理。这个配置支持几种取值:false 表示全部移除,"some" 表示保留包含 @license 或 @preserve 标记的注释,"all" 表示保留全部注释,还可以传入一个函数做自定义判断。
更推荐的写法是使用 extractComments 选项,它可以把所有许可证注释从代码中抽离出来,集中放到一个单独的文件中,同时保证该文件会被包含在最终的分发包里。这样既不污染业务代码的体积统计,又满足了保留声明的法律要求:
// webpack.config.js
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
format: {
// 不在代码里内联保留,改由 extractComments 统一抽取
comments: false
}
},
extractComments: {
condition: /^\**!|@preserve|@license|@cc_on/,
filename: "static/js/[name].licenses.txt",
banner: false
}
})
]
}
};上面这段配置中,condition 用正则匹配需要抽取的注释,filename 指定抽取后的存放路径。配置完成后,构建产物目录下会生成一个 licenses 文件,里面聚合了所有第三方库的版权信息。需要特别提醒的是,抽取出来的文件必须随产物一起发布,如果你在部署脚本里只上传了 js 文件而丢掉了 licenses 文件,合规要求同样没有被满足。
除了压缩环节,源码层面还可以用 BannerPlugin 主动注入自己项目的版权声明,声明产物归属和使用授权:
const webpack = require("webpack");
module.exports = {
plugins: [
new webpack.BannerPlugin({
banner: "Copyright (c) 2024 Your Company. All rights reserved."
})
]
};三、用插件自动生成许可证合规清单
仅保留产物中的注释还不够。真正完整的合规流程,要求团队清楚地知道项目里到底引用了哪些许可证,其中有没有 GPL 这类可能引发传染性开源义务的协议。手动去查 node_modules 目录显然不现实,好在社区提供了成熟的解决方案。
license-checker-webpack-plugin 是与 Webpack 5 配合较好的选择之一,它会在每次构建时扫描依赖树,生成许可证统计报告,并支持配置白名单,一旦发现不在白名单内的许可证就中断构建:
const LicenseCheckerWebpackPlugin =
require("license-checker-webpack-plugin");
module.exports = {
plugins: [
new LicenseCheckerWebpackPlugin({
allow: ["MIT", "Apache-2.0", "BSD-3-Clause", "ISC"],
// 生成报告文件,方便法务或合规团队审阅
outputFilename: "license-report.txt",
// 遇到未知或不允许的许可证时直接报错,阻止构建通过
emitError: true
})
]
};这样配置后,任何一个新依赖如果携带了白名单之外的许可证,构建日志会立即给出明确的错误提示,开发者必须在源头解决问题,而不是等产物发布之后才发现风险。对于金融、医疗这类对合规要求严格的行业,把许可证检查纳入 CI 流水线几乎是必备动作。
四、几个常见的踩坑点
第一个坑是 CDN 引入的第三方库。通过 script 标签从 CDN 引入的库不经过 Webpack 处理,压缩注释的保留策略对它们无效,团队需要单独确认这些库的许可证条款并留存记录。第二个坑是 Webpack 5 的持久化缓存。缓存只缓存模块的处理结果,不会替你缓存合规结论,依赖版本一旦升级,许可证可能发生变化,因此许可证扫描必须每次构建都执行,不能因为开启了缓存就跳过检查。
第三个坑是 SourceMap。有些团队为了让线上排错方便,会同时发布 SourceMap 文件,这些文件同样属于代码分发的一部分,也要遵循相同的许可证要求。建议在发布产物清单里把 SourceMap、抽取的 licenses 文件和业务 js 一并纳入管理,避免遗漏。
总的来说,法定权利声明这件事技术含量不高,但责任重大。Webpack 5 提供的工具链已经把保留注释、抽取许可证、构建期校验这一整套能力都准备好了,剩下的就是团队是否愿意花半小时把它们配置进构建流程。与其在收到开源作者的律师函之后再补救,不如现在就动手检查一下自己的构建配置。