开源许可声明该跟着JS一起发出去,114个站里40个一条不剩
本文目录
- 哪些注释是构建工具刻意留下来的?
- 114个站里40个的产物一条声明都没有,这说明什么?
- 自有产物和第三方脚本,哪一边更守规矩?
- 那36个旁挂的LICENSE文件,还剩几个能打开?
- 一段GPL v3的代码是怎么进到5个电商站里的?
- 商业授权的库出现在19个站上,谁在为它付钱?
- 那份声明清单,其实是一张技术栈台账
- 判据写窄了,漏掉的全是最常见的那一类
- 9月11日之后,这件事会变吗?
- 独立站该怎么查自己这一块?
- 一次实际的处理
- 常见问题解答
- 删掉压缩产物里的许可声明,会有什么实际后果?
- 那个LICENSE.txt文件,用户又不会去看,有必要保证它能打开吗?
- 用Shopify或者WordPress主题,这一块的责任在谁?
- 保留这些声明会不会拖慢页面?
- 这件事和SEO到底有没有关系?
- 权威参考资料
摘要:114个海外电商站的1241份JS文件里,去重后有316条许可声明,分布在74个站上;剩下40个站的产物里一条都没有。webpack有个默认动作,是把许可证抽到旁边一个LICENSE.txt里、代码里只留一句去那儿看——这样的文件本次找到36个,7个打不开。其中osano.js.LICENSE.txt在三个互不相干的站上全部返回403,连响应体都是同样的111字节。另有一段GPL v3的分析脚本出现在5个电商站上,不是这些品牌自己装的。
aloyoga.com的首页加载了一个叫osano.js的脚本,是Osano这家做同意管理的厂商的。翻它的代码,末尾有一行注释:
For license information please see osano.js.LICENSE.txt
这句话的意思是:这个文件里打包了一堆开源库,它们的许可证要求保留版权声明,声明我给你放到旁边那个文件里了,你要看就去看。
那个文件的地址就在同一目录下。我访问了一下,403。
换functionofbeauty.com,同一家厂商,不同的租户ID,同样的一句话,同样的403。再换snowpeak.com,还是403。三个站三条不同的URL,返回的响应体长度都是111字节——同一个错误页。
那句话本身没错,代码里确实该留这么一句,这是构建工具按规矩办的事。问题在于,这句话把履行义务的责任交给了一个文件,而那个文件从来没人确认过它能不能打开。
哪些注释是构建工具刻意留下来的?
压缩JS的时候,注释是第一批被删的东西——它不影响执行,纯占体积,这一层的账在页面字节预算那边算得很细。但压缩器不会全删,因为有一类注释是法律要求保留的:MIT、BSD、Apache这些许可证都写着,再分发时必须保留版权声明和许可文本。删了就是违约。
于是压缩器给了这类注释一个豁免通道,判据是写法而不是内容。Terser的选项文档里,format.comments的默认值是some,原文解释是by default it keeps JSDoc-style comments that contain @license, @copyright, @preserve or start with !——默认保留含@license、@copyright、@preserve或者以感叹号开头的注释。所以你在压缩后的代码里看到的/*!,不是谁忘了删,是工具按规则留的。
esbuild把这类注释直接叫做legal comment,定义是any statement-level comment in JS or rule-level comment in CSS that contains @license or @preserve or that starts with //! or /*!,并提供五种处置方式:全删、原地保留、挪到文件末尾、挪到外部文件并留一行指路、挪到外部文件且不留指路。它的默认行为是eof when bundling is enabled and inline otherwise——打包时挪到文件末尾。
webpack那边则是另一种默认:把它们抽成一个同名的.LICENSE.txt,在原文件里留一句For license information please see ...。开头那三个403,就是这条路径的产物。
把这几件事连起来看,结论有点让人不舒服:你的前端有没有履行开源许可的署名义务,取决于打包工具的默认值,而不是任何人的决定。这跟第三方脚本在两天半里自己改了行为是同一个毛病——默认值在替你做决定,而你甚至不知道有这么个选项。
114个站里40个的产物一条声明都没有,这说明什么?
做法是这样:131个站取首页,127个返回200;从HTML里抽出所有外链脚本,每站最多取14个,实际取到1241份JS文件、覆盖114个站;把每个文件里的块注释扒出来,先按写法筛出构建工具刻意保留的那一批,再判断其中哪些真的是许可声明。
结果:74个站(64.9%)的产物里至少有一条许可声明,40个站(35.1%)一条都没有。去重后的声明条目共316条。单站条数最多的是iittala,16条;anker、innisfree、soundcore各15条;uniqlo 13条。
零声明的这40个站里,有几个名字很显眼:ikea、on、quince、warbyparker、gymshark、brooklinen、decathlon、traeger。这些站的前端复杂度都不低,说它们一个开源库都没用,不可能。顺带一提,它们在字体那一轮实测里的表现参差不齐,这两件事之间看不出相关性——不是某一类团队的通病。
更合理的解释有三种,而且都很常见:
- 构建配置把这类注释关了。Terser的
comments: false、esbuild的--legal-comments=none,一行配置的事。有人为了压体积这么干过,有人是从别处抄的配置里本来就带着。 - 声明被抽到外部文件了,而我只测首页直接引用的脚本。如果那个LICENSE.txt没有被HTML引用,我这一轮就看不到它。这是本次方法的边界,得承认。
- 脚本本身是通过其他脚本动态注入的。首页HTML里没有
script src,我就抓不到。132个独立站首页有28个是空壳那一次已经量过这类站的比例。
所以这40这个数不能读成四十个站在违约。它能读的是:在这40个站的首页产物这一层,署名义务的履行痕迹是零,任何一个想核对的人——包括这些品牌自己的法务——都没有可查的东西。
自有产物和第三方脚本,哪一边更守规矩?
开工前我有个预判:第三方SaaS厂商发出来的脚本会更规范,因为它们是要给成百上千个客户用的,法务盯得紧;品牌自己打包的主题产物会松一些。
我把1241份JS按主机名分了两类:域名属于站点自身或者走Shopify主题目录的算自有产物,其余算第三方脚本。实测结果和预判是反的。
| 分类 | 文件数 | 带声明的文件 | 文件层比例 | 站数 | 有声明的站 | 站层比例 |
|---|---|---|---|---|---|---|
| 自有产物 | 791 | 88 | 11.1% | 99 | 53 | 53.5% |
| 第三方脚本 | 450 | 58 | 12.9% | 93 | 40 | 43.0% |
站层比例上,自有产物反而高出10.5个百分点。文件层比例上两边差不多,第三方略高。也就是说,那个我以为存在的差距并不存在。
回头想,预判错在哪儿其实挺清楚:第三方脚本里塞的往往是厂商自研代码加少数几个依赖,而品牌的主题产物是把一整个node_modules揉进去,携带的开源库本来就多得多。守不守规矩这件事,这组数根本没量到;它量到的是两边各自打包了多少别人的代码。
这跟满意度调查那个92%只覆盖7.4%买家是同一类陷阱:一个比值看着在回答A问题,实际回答的是B问题。碰上这种情况,老老实实把预判和结果一起写出来,比悄悄换个说法要诚实。
那36个旁挂的LICENSE文件,还剩几个能打开?
这一轮是本文最有意思的部分。既然webpack的默认动作是把责任转交给一个外部文件,那就该去看看那些文件的状态。
我从316条声明里正则抽出所有For license information please see xxx.LICENSE.txt的指向,按URL去重得到36个地址,逐个访问、跟随跳转。
29个能取到内容,7个取不到,占19.4%,涉及5个站。取不到的这7个分成三种:
- 403,四个。三个是前面说的osano.js.LICENSE.txt(aloyoga、functionofbeauty、snowpeak),一个是aloyoga的
cdn.optimizely.com/js/client.min.js.LICENSE.txt。这类的共同点是:主脚本正常返回,只有旁边那个txt被CDN规则挡掉了。多半是某条只放行js后缀的缓存或防盗链规则,顺手把txt也拦了。CDN的缓存键与回源规则一旦按后缀写死,这类误伤很难被发现,因为没人会去访问那个文件。 - 404,两个。functionofbeauty的
static.rechargecdn.com/assets/storefront/xdr.js.LICENSE.txt,ice-watch的cdn.506.io/eg/script.js.LICENSE.txt。文件根本没被部署上去。 - 软404,一个。byredo的
nr-loader-spa-1.291.1.min.js.LICENSE.txt返回404,但响应体有96370字节——它把单页应用的HTML当错误页回给了你。站内搜索页搜不到也返回200那一篇讲过这类身份错乱,这次是反过来:状态码对了,内容完全不对。
能打开的那29个也值得翻一翻。体积跨度很大:theordinary那份jquery.min.js.LICENSE.txt只有89字节,通篇就一行/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */;bugaboo那份commons.js.LICENSE.txt有10345字节,里面挨个列着object-assign、js-cookie等一串库的完整声明。
materialkitchen那份里有个小彩蛋:Selectric这个jQuery下拉插件的声明是一段ASCII点阵画的闪电加名字,占了大半个文件。它在2017年被人画进代码,此后被压缩器一路当作法律文本原样保留,搬到了2026年的一个厨具站的CDN上。压缩器不认识画,它只认识那个开头的感叹号。
一段GPL v3的代码是怎么进到5个电商站里的?
按许可证类型统计站数,MIT 37个站,Apache 2.0 10个,BSD 6个,ISC 4个,MPL 2个,明确写着保留所有权利或者附加商业条款的19个。还有一格是GPL系列,7个站。
GPL这一格值得单独查,因为它跟前面那些不一样。MIT和BSD只要求你保留声明,GPL要求的是:谁拿到了程序,谁就有权拿到对应的源码。自由软件基金会的GPL常见问题对这一点讲得很细。
逐条翻原文之后,7个站里5个是同一个东西:
dwAnalytics - Web Analytics Tracking * Based partially on Piwik * @link http://piwik.org * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
文件路径也一模一样,都是/internal/jscript/dwanalytics-22.2.js。这是Salesforce Commerce Cloud平台自带的分析脚本,基于Piwik(现在叫Matomo,本身是GPL v3)改的。bugaboo、canyon、cybex-online、joolz、zwilling这5个站都跑在同一套平台上,脚本是平台放进每个商家页面的。
这一格的排查路径和查一个插件是从哪儿来的是一样的:先定位文件路径,再看它属于平台、主题还是你自己。另外两个各有各的来路:jysk是Drupal站,命中的是Drupal核心资源的@license GPL-2.0-or-later;swarovski命中的是jQuery blockUI这个老插件,它本身是MIT与GPL双许可,选MIT就行。
所以真正需要留意的是SFCC那一格。这不是这5个品牌引进来的代码,是平台带的;但代码是从他们的域名发到用户浏览器里的。这里到底谁是分发者、附随源码义务在谁头上,是个需要法务判断的问题,我不做结论。我能给的是一个具体事实:如果哪天有人问起,这5个站里没有任何一个能在自己的产物里指出对应源码在哪。
这件事的形状和换个平台只是换一批老代码完全一致:你以为在用一个平台,其实是在替这个平台承接它的历史包袱。选建站方案的时候,这一格从来没出现在任何对比表上。
商业授权的库出现在19个站上,谁在为它付钱?
那19个带商业或专有条款的站里,最典型的是GSAP。这是个做动画的老牌商业库,6个站在用:anker、arcteryx、casetify、framebridge、graza.co、soundcore。声明原文是这样:
CustomEase 3.15.0 * https://gsap.com * @license Copyright 2008-2026, GreenSock. All rights reserved. * Subject to the terms at gsap.com/standard-license or for Club GreenSock members, the agreement issued with that membership.
注意CustomEase这个名字。GSAP的核心是免费的,但一批插件属于会员专享,CustomEase就在其中。这段声明等于在页面上公开写着:这个站要么买了会员,要么没买。你用的哪些库需要付费,写在你自己的产物里,谁都能读。这跟响应头里那些没人写却一直在的字段属于同一类信息——不是你想公开的,也不是你想隐藏的,就是没人管过。
另一条更直接。avocadogreenmattress和hellotushy的产物里有Extend这家保修服务商的SDK,声明写着Copyright (c) 2019-present, Extend, Inc. All rights reserved. No access or use is permitted except pursuant to authorization granted by Extend——未经Extend授权,不得访问或使用。而这份写着不得未经授权访问的文件,正挂在CDN上供全网无条件下载。
这句话你可能觉得眼熟。字体文件里那段禁止存放在公开服务器上的条款,是完全一样的结构:一段为别的场景写的法律文本,被原样塞进了一个必然公开的交付物。区别只在于,字体那边是人手工填进name表的,这边是构建工具自动搬运的。
还有一类是带时间戳的。Okendo这家评论工具在5个站上留下同一条声明,格式是0.104.0 - 2026-08-31T07:41:04.260Z Copyright (c) 2026 Okendo Pty Ltd——版本号加精确到毫秒的构建时刻。HTML注释那一层的构建日期是同一类信息,只不过这次它藏在一条法律声明里。
那份声明清单,其实是一张技术栈台账
把316条声明按库名归并之后,它就变成了一份可读的清单。最扎眼的是jQuery:11个站的声明里能读出确切的版本号,一共8个不同版本。
| 版本 | 站数 | 站点 |
|---|---|---|
| 3.4.1 | 3 | charleskeith、monos、snowpeak |
| 3.7.1 | 2 | jysk、swarovski |
| 3.6.4 / 3.6.3 / 3.6.0 | 各1 | hellotushy、philips、materialkitchen |
| 2.1.4 | 1 | segway |
| 1.12.4 | 1 | assos |
| 1.9.1 | 1 | magicspoon |
magicspoon那个1.9.1是2013年的版本,assos的1.12.4是2016年的,segway的2.1.4是2015年的。这三个数不是我逆向出来的,是它们自己写在许可声明里的——为了满足署名义务而保留的那行字,顺带把版本号也交代了。
其余的分布:React 5个站,Sizzle 4个,Vue 3个,Splide 2个。uniqlo那条最长的声明来自Cédric Mesnil的加密库,是完整的BSD三句式条款,光那一段就有四百多字符。
这份清单的用法很直接:接手一个陌生的站,或者要摸清竞品的前端家底,读它比逆向整个bundle快得多。CMS上线第一年那些隐性失分项里,有好几项其实都能从这份清单上先看出苗头。
判据写窄了,漏掉的全是最常见的那一类
本批A轴那边尺子坏了三次,这一轴只坏了一次,但坏得很典型。
我的第一版判据要求许可声明里出现MIT License、Apache License、BSD License、Copyright (c) 年份这一类完整写法。跑出来288条,看着挺规整。核对时才发现被判成非许可的那一堆里,混着这些:
js-cookie v3.0.5 | MIT——只有三个字母,没有License那个词jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license——括号c后面跟的是名字不是年份ieee754. BSD-3-Clause License. Feross Aboukhadijeh——写的是BSD-3-Clause不是BSDSplide.js * Version : 4.1.4 * License : MIT——License和MIT中间隔着一个冒号(c) Andrea Giammarchi @webreflection ISC——ISC三个字母孤零零挂在末尾
放宽判据后从288条变成316条,多出28条。数量上不算多,但方向是系统性的:漏掉的全是短格式,而短格式恰恰是最常见的那一种写法。jQuery那一条一漏就是15个站,js-cookie 4个站。要是没核对,这篇文章的开头会写成开源库大多不留声明,而真相是留了,只是留得很短。
顺带修的还有两处:同一份JS被同一页面从两个域名各引用一次(casetify的CDN域和主域),按URL去重不够,得再按体积和首条声明归并;以及前面那个byredo的软404,状态码判死不够,还得看响应体大小。
这类返工的规律,做到现在已经很清楚了:第一版就跑出规整数字的判据,几乎一定是把一批东西粗暴地归进了同一格。越规整越要查。
9月11日之后,这件事会变吗?
会变一点,但不是变在合规压力上。
欧盟网络韧性法案的漏洞报告义务从2026年9月11日起适用,完整适用要到2027年12月。它要求的软件物料清单,格式得是机器可读的SPDX标识符或者CycloneDX,至少覆盖顶层依赖。
要说清楚的是,网络韧性法案管的是安全,不是许可合规,它不会因为你少留一行版权声明就找你。但它会带来一个副作用:为了出这份清单,你得先搞清楚自己的产物里到底打包了什么。而这件事一旦做了,许可那一格自然就浮出来了。
这里有个技术上的难点值得提前知道。生成物料清单的常规工具是读package.json和锁文件,它看到的是依赖树;而打包工具把这些依赖揉进一个文件之后,产物里到底剩下哪些代码、哪些声明被保留了,读清单的工具是看不到的。清单和产物是两份不同的东西,两边对不上的情况很常见。
所以更实际的做法是两头都看:一头从package.json拉依赖树,一头从构建产物里把保留下来的声明扒出来,对一遍。两边对不上的那几项,往往就是这些年里被谁顺手加进来、又没记在任何地方的东西。这和选开源项目时该看哪些公开信号是一条线上的活——引进来之前看清楚,引进来之后还得知道它现在在哪儿。
独立站该怎么查自己这一块?
整套排查半小时以内,做完基本一劳永逸。
第一步,看看你的产物里还有没有声明。打开首页,在开发者工具里找到你自己的那个主bundle,搜@license和/*!。一条都搜不到,说明构建配置把它们删干净了,或者抽到外部去了。
第二步,如果有指向LICENSE.txt的那一句,把地址拷出来直接访问。本次实测19.4%打不开,这个比例不低,而且它是最容易修的一项——多半是CDN规则或者部署脚本漏了txt后缀,改一条规则的事。
第三步,查一下自己的构建配置。webpack看TerserPlugin的extractComments,esbuild看legal-comments,Vite底层走的是esbuild和Rollup。如果被显式设成了false或者none,问一句为什么——绝大多数情况下答案是当年为了压体积随手加的,而它省下的字节以KB计。
第四步,把第三方脚本单独列一张表。你能改的只有自己的构建配置,第三方发过来什么样就是什么样。但你至少该知道页面上跑着谁的代码、各自什么许可。支付页的CSP实测里那张脚本清单,可以顺手拿来做这件事的底稿。
一次实际的处理
保哥去年给一个做家居用品的独立站做前端体检,搜主bundle时一条@license都没有。翻构建配置,Terser的comments被显式写成了false,git记录里那次提交的说明是压体积。把它改回默认值重新构建,产物大了3.1KB,Gzip之后不到1KB,页面加载时间在多次测量里没有可分辨的差异。改完之后那个bundle里多出十几条声明,包括几个当时团队自己都不知道还在用的库——这一步的附带收获,比合规本身还实在。
常见问题解答
删掉压缩产物里的许可声明,会有什么实际后果?
法律上,MIT、BSD、Apache这几个最常见的许可证都把保留版权声明与许可文本列为再分发的前提条件,删了就是不满足条件,理论上授权即告终止。实务上,前端产物因为这个被追究的案例极少——通常是在收购尽调、大客户供应商审查、或者开源社区公开点名这三类场合才会成为问题。真正的成本不在诉讼风险,而在于那一刻你要花几天时间去重建一份根本没人维护过的清单。
那个LICENSE.txt文件,用户又不会去看,有必要保证它能打开吗?
有必要,而且这是全套里最便宜的一项。许可证要求的是随分发提供声明,代码里那句For license information please see是你履行义务的方式,指向的文件打不开,等于这句话没说。本次36个文件里7个打不开,其中4个是403——不是文件没了,是CDN规则把txt挡在外面。改一条规则,几分钟的事,比事后解释省力得多。
用Shopify或者WordPress主题,这一块的责任在谁?
分两段看。主题作者把开源库打包进主题时,保留声明是他的义务;但产物是从你的域名发出去的,对外你也是分发者之一。实际操作上你能做的是:把主题产物里的声明搜一遍,一条都没有就去问主题作者,看是他删的还是构建配置的问题。选主题看代码质量的时候,这一项可以顺手当一个观察指标——连许可声明都不留的主题,别的地方通常也糙。
保留这些声明会不会拖慢页面?
影响小到可以忽略。本次能打开的29个LICENSE.txt里,中位数在几百字节到两三KB之间,最大的那个10345字节。就算全部内联回主bundle,Gzip之后增量通常在1KB以内,对页面体积那本账不构成任何影响。真要省字节,该动的是图片、字体和没用上的第三方脚本,不是这几行注释。
这件事和SEO到底有没有关系?
没有直接的排名关系,搜索引擎不看许可声明。有关系的是两条间接路径。一是那个打不开的LICENSE.txt通常暴露的是CDN规则问题,同一条规则常常也在挡别的东西,值得顺藤摸下去。二是产物里那份声明清单,是判断一个站前端家底最快的方式——你接手一个新站、或者要分析竞品用了什么,读它比逆向整个bundle省事得多。从代码逆向出爬虫的真实偏好用的是同一路手法。
权威参考资料
本文标题:《开源许可声明该跟着JS一起发出去,114个站里40个一条不剩》
本文链接:https://zhangwenbao.com/js-bundle-license-notice-audit.html
版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0