14个不相干的站,发的是同一份给老浏览器的补丁

14个不相干的站,发的是同一份给老浏览器的补丁
张文保 24 分钟阅读 1,756 阅读
本文目录
  1. 你的页面到底在给谁多发一份代码?
  2. 怎么把“发给老浏览器的那一份”从产物里分出来?
  3. 先看标签属性
  4. 再看文件名
  5. 最后看字节
  6. 这份补丁有多大,多少人在发?
  7. 两种工具的思路差了一个数量级
  8. 为什么14个不相干的品牌,发的是同一串字节?
  9. 那份浏览器名单,到底是谁写的?
  10. 这份名单真的是2018年定下来的吗?
  11. 我拿这串名单推产物,又推错了一次
  12. 样式表那一层,比脚本还老
  13. 这些字节的代价,到底有多大?
  14. 兼容包这一头,代价基本是零
  15. 样式表这一头,是真花钱但花得不多
  16. 构建这一头,倒是真花时间
  17. 真正的代价在别处
  18. 自己站上怎么查一遍?
  19. 常见问题解答
  20. 页面上有nomodule脚本,会拖慢加载吗?
  21. 怎么知道我的构建目标是什么?
  22. 把兼容包关掉能省多少?
  23. 样式表里那些老前缀,能直接批量删吗?
  24. 为什么好几个不相干的站会发同一份文件?
  25. 这件事和SEO到底有没有关系?
  26. 这批数字能代表整个行业吗?
  27. 权威参考资料
摘要:118个海外品牌电商站里,24个还在页面上挂着专门发给老浏览器的那份兼容脚本。抓到具体文件的34个站,这份补丁合计4.84 MB,单站中位99.6 KB,最大的一份1.37 MB。更值得看的是重合度——14个互不相干的品牌发的是同一串字节,112594个字节一个不差,连其中两个把文件名改掉的站也一样。这份代码不是他们各自决定要发的,是框架出厂时就在那儿。

先说一个可能反直觉的前提:现代浏览器根本不会下载这份兼容脚本。这是浏览器厂商十年前就定好的规矩——带nomodule标记的脚本,认得模块语法的浏览器一律跳过。所以下面要讲的这几兆字节,对你的访客来说,一个字节都没多花。

那还有什么好查的?因为它是一张标签。标签上写着这个站的构建配置是哪一年定的、之后有没有人动过。而这张标签,绝大多数情况下不是站主贴的。

你的页面到底在给谁多发一份代码?

2017年前后,浏览器界达成了一个约定:<script type="module">给认得ES模块的浏览器看,<script nomodule>给不认得的看。前者会跳过后者,后者会跳过前者,两份代码互不干扰。MDN上关于script元素的文档把这条规则写得很清楚:nomodule是一个布尔属性,支持模块的浏览器见到它就不执行。

这套双份发货的做法,在 web.dev那篇讲怎么发布现代JavaScript的文章里被推荐成标准做法:新浏览器拿小的那份,老浏览器拿带满补丁的那份,谁也不亏。

问题出在“老浏览器”这四个字上。这份东西刚流行的时候,老浏览器指的是IE 11。而 微软官方的生命周期公告写着,IE 11桌面版在2022年6月15日就停止支持了,四年多以前的事。

所以今天页面上还挂着这一份,意味着什么?意味着这行配置从写下来到现在,没人再回头看过。

怎么把“发给老浏览器的那一份”从产物里分出来?

三条判据,逐层收紧。

先看标签属性

最直接的是HTML里的nomodule属性。这条不会错,写了就是写了。118个可测站里,24个站的首页至少有一个这样的脚本标签,占20.3%;反过来,用type="module"的有85个站,占72.0%。

再看文件名

构建工具给这份产物起的名字有明显规律:polyfills-哈希.jsindex-legacy-哈希.jspolyfills-legacy-哈希.js。Next.js出的叫前者,Vite的legacy插件出的叫后两者。按这套关键词加上nomodule标记一起筛,一共抓到34个站的兼容包文件。

最后看字节

这一层是意外收获。我给每份下载到的文件算了个内容哈希,本意是去重省空间,结果发现哈希撞得离谱——同一串哈希在十几个站上反复出现。到这一步,“这份代码是谁决定发的”这个问题就有答案了。

顺带说下采集口径:每个站只发7个请求,一份首页、一份主样式表、五份JS,JS是按文件名关键词加体积挑的。上一轮同一批站我每站发17个请求,结果50个站把我限流了;这一轮131个站跑完,一个429都没有。站内连续请求数是独立于请求速率的第二个变量,这条经验值一次返工的代价。

这份补丁有多大,多少人在发?

口径数值
首页带nomodule脚本标签的站24 / 118(20.3%)
抓到具体兼容包文件的站34
兼容包字节合计5072891字节(4.84 MB)
单站合计 中位102027字节(99.6 KB)
单站合计 平均149202字节
最大的一个站1431565字节(1.37 MB)

最大那份来自article.com,一份叫index-legacy.846ba371.js的文件,1.37 MB。这是Vite的legacy插件干的活:它不只发polyfill,它把整个应用重新编译一遍发给老浏览器,所以体积跟主应用一个量级。

再往下是bellroy.com的411 KB,同样是Vite出品。剩下大部分站的兼容包都在100 KB上下——这个数字后面会解释为什么这么整齐。

两种工具的思路差了一个数量级

同样叫“给老浏览器的那一份”,两大构建工具的做法完全不同,体积也就差出十倍。

Next.js的做法是只补语言特性:它生成的那份polyfills里装的是Promise、fetch、Object.assign这类老浏览器缺的东西,业务代码一行都不重复。所以它的体积几乎是个常数,一百来KB,跟你的应用有多大没关系。

Vite的legacy插件走的是另一条路:它把整个应用按老语法重新编译一份,再配一份polyfill。这意味着你的应用越大,这份兼容产物就越大,article.com那1.37 MB就是这么来的。它的好处是老浏览器真的能跑起来,代价是构建产物直接翻倍。

选哪条路本来是个明确的取舍,但实际情况往往是:脚手架初始化时那个选项就是默认开着的,没人做过这个取舍。构建产物名字里那串内容哈希本身也是个有意思的东西,带哈希的静态资源一年后有一半地址会404,那一轮量的是同一套机制的另一面。

为什么14个不相干的品牌,发的是同一串字节?

这是本轮最扎实的一个发现。polyfills-42372ed130431b0a.js,112594个字节,出现在下面这些站上:

站点路径长什么样
bigcommerce.com/_next/static/chunks/polyfills-42372ed130431b0a.js
braun.com/en-us/_next/static/chunks/polyfills-42372ed130431b0a.js
byredo.com/_next/static/chunks/polyfills-42372ed130431b0a.js
eufy.com/_next/static/chunks/polyfills-42372ed130431b0a.js
gymshark.com/_next/static/chunks/polyfills-42372ed130431b0a.js
ouraring.com/b/a/ecom-website/v1.129.0/_next/static/chunks/polyfills-…
philips.com/c-resources/_next/static/chunks/polyfills-…
quince.com/prod/_next/static/chunks/polyfills-…?dpl=33862632050
rituals.com/_next/static/chunks/polyfills-42372ed130431b0a.js
ecoflow.com/web2/_next/static/chunks/03~yq9q893hmn.js
rapha.cc/_next/static/chunks/0cz1d0mv5g_q7.js

加上没列全的几个,一共14个站。最后两行值得单独看:ecoflow和rapha把文件名换掉了,路径里看不出polyfill三个字,但下载下来一比对,112594个字节和其他十二个站一模一样。改名改的是招牌,货还是同一批。

一个电动车品牌、一个电动工具品牌、一个香水品牌、一个健身服品牌、一个智能戒指品牌,还有一家做电商SaaS的公司,发的是同一份文件。它们之间没有任何关系,唯一的共同点是都用了Next.js。

顺着这条线往下挖,同类的还有几组:8个Shopify站共用一份44907字节的es-modules-shim.2.4.0.js,11个站共用一份13938字节的Shopify平台加载器。这些都不是店主选的,是平台注入的。同一类“平台替你做的决定”我在支付页那份出厂脚本策略的实测里量过另一个切面,而平台推下来的东西会自己变、变了也不通知你这件事,则在两天半里四分之一的站自己换了脚本那一轮里有完整记录。

那份浏览器名单,到底是谁写的?

兼容包发给谁,取决于一份浏览器版本清单。前端这行有个通用格式叫browserslist,它的官方仓库就是维护这套语法的地方,几乎所有构建工具都读它。

有意思的是,这份清单有时候会被原样打进产物里。我在扫描JS时撞见了这么一行:

e.exports=["chrome 64","edge 79","firefox 67","opera 51","safari 12"]

顺着这个模式扫全样本,10份产物里读到了这串数组。分布是这样的:

读到的名单份数站点
chrome 64 / edge 79 / firefox 67 / opera 51 / safari 129anker、arcteryx、bigcommerce、byredo、eufy、gymshark、quince、soundcore、vuoriclothing
chrome 111 / edge 111 / firefox 111 / safari 16.41braun

九比一。九个站的清单里,Chrome停在64版——那是2018年1月的版本。而 Next.js官方文档现在写的默认支持范围,是Chrome 111、Edge 111、Firefox 111、Safari 16.4,也就是2023年3月那一批。

看到这儿我以为结论已经很清楚了:这九个站的项目是2018年前后建的,从此没升过级。

这份名单真的是2018年定下来的吗?

不是。这是这一轮我推错的一步,而且推得挺自然。

把Next.js各个版本的源码翻出来对了一遍,那个常量叫MODERN_BROWSERSLIST_TARGET,历史是这样的:

Next.js版本常量取值
v13.5.6chrome 64 / edge 79 / firefox 67 / opera 51 / safari 12
v14.2.3同上
v15.0.0同上
v15.5.0同上
canary(当前)chrome 111 / edge 111 / firefox 111 / safari 16.4

也就是说,从13系一路到15.5,这串数字一个字没改。它不是这九个站各自在2018年选的,是框架从头到尾用了好些年的出厂默认值。

还有个细节值得一提:唯一读到新名单的braun.com,它的兼容包哈希跟前面那14个站是同一个。编译目标那个常量换了,兼容包那份字节没换——这两样东西在框架内部本来就不联动。所以哈希能当框架版本的粗指纹用,但不能拿来推它的编译目标,反过来也一样。

更妙的是最新那一版源码里的注释,写着这份清单是用browserslist "baseline widely available"这条查询在2025年10月1日重新生成的。换句话说,那个真正的“这个默认值是哪一年定的”的答案,不在你的产物里,在框架仓库的提交历史里。你从数值本身往回推,只会推出一个错的年份。

这一条我打算记很久。看到一个像年份证据的东西,第一反应应该是去问定它的那个人,而不是拿数值本身当日期用。同样的道理适用于那份87行的robots.txt有33个站一个字没改过,也适用于会跟着访客系统时钟走的页脚版权年份——一个数字看起来在记录时间,不代表它真的在记录时间。

我拿这串名单推产物,又推错了一次

既然读到了编译目标,那顺手做个验证:如果目标是Chrome 64,产物里就不该出现Chrome 80才支持的可选链语法(?.),它应该被编译成老写法。

数出来的结果正相反:

站点读到的名单主产物里的?.主产物里的??
quince.comchrome 64…637处184处
bigcommerce.comchrome 64…514处272处
anker.comchrome 64…399处59处
eufy.comchrome 64…159处28处
braun.comchrome 111…0处0处

写着chrome 64的站,产物里可选链最多的一个出现了637次;写着chrome 111的那个站反而一次都没有。

结论是:这串常量管的是框架自己那部分代码的编译,管不到应用代码和预编译过的第三方包。一份配置被打进产物,不等于产物整体都听它的。braun那个0也不能反过来解读成“它更保守”,更可能只是我抓到的那份文件恰好没用到这个语法。

顺便把全样本的现代语法出现率也列一下,这组数字本身有点意思:100个可分析主产物里,可选链出现在67个站上,空值合并45个,异步函数61个,箭头函数89个。也就是说,同一个页面上,业务代码是按今年的浏览器编译的,兼容包是按几年前那份名单发的——两套年代并排跑,谁也不知道对方存在。

样式表那一层,比脚本还老

JS这边至少还有nomodule兜底,浏览器认得出来就不下载。CSS没有这个机制:写在样式表里的每一行,所有人都要下载,所有人都要解析。

我拿79份样式表做了一遍前缀考古,只统计那些“无前缀写法早就能用了”的属性。87.3%的样式表里至少还留着一条

写法站点覆盖出现次数它是给谁准备的
-webkit-box-orient55.7%5142009年那版flexbox草案
-moz-column51.9%1019Firefox 51及更早
-webkit-backface-visibility34.2%79Safari 8及更早
-webkit-transform26.6%1624Safari 8及更早
-ms-flex19.0%2794IE 10 / 11
-webkit-box-flex / -pack / -align10%—11%10522009年那版flexbox草案
-khtml-6.3%7Konqueror与Safari 1.x

整体上86.1%的样式表里能找到-ms-开头的属性,也就是当年专门写给IE的那一套。-ms-flex一项就出现了2794次。

最下面那行-khtml-得解释一下:KHTML是Konqueror的渲染引擎,Safari最早就是从它fork出去的,这个前缀的使用高峰在2005年前后。它出现在5个2026年还在卖货的品牌站上,一共7次。MDN对厂商前缀这个概念的解释里专门提了一句:这套机制现在基本被废弃了,因为它带来的维护负担远大于收益——这段话本身就是历史的一部分。

另外还有几处更老的CSS hack:4个站里有IE 6/7的星号写法,2个站有IE 6的下划线写法,1个站有IE 8/9的反斜杠9。这些东西的年纪比很多做电商的同行的工龄都大。

这里要说一句公道话:不是所有带前缀的写法都该算进来。有一批前缀今天仍然必要——做多行文本截断的那个、控制表单控件默认外观的那个、Safari上那个背景模糊的那个,删了会真出问题。我在统计时把这类单独列了白名单,只数那些“无前缀版本早就全线可用”的属性。把仍在服役的和已经退役的混在一起数,得出的数字会大得离谱,而且没法拿去做决策

另外这79份样式表里有48份是我上一轮存下的“该站类名定义最多的那份主样式表”,剩下31份是这一轮首页声明的第一份同域样式表。混用口径是因为本轮每站只抓一份,而那一份在14个Shopify站上抓到的是同一个平台文件,代表不了主题。这类“主题带进来的东西”,恰恰是选主题时最该看代码而不是看演示图的原因。

脚本那边的化石也不少。扫全部下载到的JS,含IE时代浏览器嗅探的有134份文件:attachEvent出现在72个站上共262次,documentMode在36个站上,ActiveXObject在38个站上,直接匹配MSIE字符串的还有16个站。这一层和 head里那些过时HTML标签响应头里那248个没有接收端的死字段是同一种地层,只是介质不同。连HTML注释里那些没人读的构建日期也属于这一层——它们的共同点是:谁都能看见,谁都不负责。

这些字节的代价,到底有多大?

这一节我得把话说透,因为很容易被写成危言耸听。

兼容包这一头,代价基本是零

带nomodule的脚本,现代浏览器不下载。Googlebot用的渲染引擎是新版Chromium,同样不下载——它跟真实浏览器的差别到底在哪,JS渲染那一轮的三件担心里两件没发生做过对照。所以那将近5 MB里的绝大部分,既不占访客带宽,也不占抓取预算。页面体积把商品链接顶掉那一轮讲的是另一回事,那里的字节是真的要传的。

样式表这一头,是真花钱但花得不多

死前缀的字节占整份样式表的比例,中位数是0.065%。一份260 KB的样式表里,这些给已消失浏览器准备的属性名加起来不到200个字节。算下来一年省的流量买不起一杯咖啡。要真想在样式表上省字节,1149个类名里这一页只用上200个那一头的浪费大了三个数量级;再往前一步,先确认服务器有没有在压缩CSS这类文件比抠属性名有用得多。样式表整体阻塞渲染的那部分成本,则是关键渲染路径那一头的事,跟前缀无关。

构建这一头,倒是真花时间

关掉legacy构建能省的不是流量,是流水线时间。Vite那条路等于把整个应用编译两遍,中大型项目里这一遍常常占掉构建总耗时的三到四成。对一个每天要发好几次版的团队,这个数比几十KB的传输量实在得多。

还有部署包体积和CDN存储。一份1.37 MB的产物,乘以你保留的历史版本数,占的空间不算小。当然这些都是内部成本,不进任何一份对外的体检报告——这大概也是它长期没人管的原因之一。

真正的代价在别处

第一,它是构建配置的年龄标签。上面那张Next.js版本表说明了,读到chrome 64至少能确定这个站的框架还没跨过某个门槛。这个信息在做竞品分析和技术尽调时值钱。

第二,它是“没人负责”的证据。一个站可以每周上新品、每月改首页,同时它的构建配置从项目初始化那天起就没被打开过。这两件事完全不冲突,而后者才是排查问题时真正卡人的地方。产物里还留着什么别的痕迹,许可声明那一轮顺着同一批文件量过另一个维度。

第三,删这些东西的收益低、风险不为零,所以它会一直躺在那里。这才是它能活这么久的真正原因——不是没人知道,是没人有动力。

自己站上怎么查一遍?

浏览器里五分钟能查完,不用装任何东西。

  1. 打开首页,查看源代码,搜nomodule。搜到了就说明你在发这份东西,搜不到不代表没有——有些框架用运行时判断,不写在HTML里。
  2. 开发者工具的网络面板筛JS,看文件名里有没有polyfilllegacyes5这几个词。找到就点开看体积。
  3. 如果站是自己构建的,翻package.json里的browserslist字段,或者项目根目录的.browserslistrc。没有这两样,说明你在用框架的默认值——那就去框架文档里查默认值是什么。
  4. 样式表那边,把主样式表下载下来,搜-ms--moz--khtml-。数一下有多少条,别急着删。
  5. 把查到的结果连同日期记一笔。这份记录的价值在半年后——那时候你能看出哪些跟着构建流程自己变了,哪些原地没动。

关于删不删,我的建议是分三类处理:应用自己写的老前缀,下次改那个组件时顺手清掉;构建工具自动加的,去改配置而不是改产物;平台和第三方注入的,记进台账但别硬删——删了下次平台更新还会回来,白忙一场。哪些东西是第三方带进来的,可以对着只读HTML看不到的那8个第三方域名那份清单一起盘。

保哥去年帮一个宠物用品独立站做前端体检,翻出主题里一整块给IE写的降级样式,团队第一反应是删。后来查了一下他们自己的分析后台,那半年里IE的访问量是0,但那块样式跟商品图的比例控制写在同一个文件里,删完页面上有三处图片错位。最后的处理是留着不动,在文档里记了一行“这块代码的目标浏览器已不存在,重构商品卡片时一并处理”。这个选择我觉得是对的——技术债不一定要马上还,但一定要记在账上。真到了要动主题、动构建的那一天,先把改版不掉流量的那份清单过一遍再动手。

版本号那一头我在另一轮实测里单独量过:62个站读出来的库版本,年龄中位数是4.5年,最老一个停在2011年。那篇讲的是“这块东西是哪年放上去的”,这篇讲的是“这块东西是发给谁的”,两个问题合起来才是一份完整的年龄报告。

常见问题解答

页面上有nomodule脚本,会拖慢加载吗?

对现代浏览器不会,它连下载都不会下载。真正会受影响的是两类情况:一是你的构建把兼容包和主包混在了一起没分开发,二是有些老式的降级方案不用nomodule而用运行时判断,那种是要下载的。前者看HTML就能确认,后者要看具体实现。

怎么知道我的构建目标是什么?

优先看项目里的browserslist配置,可能在package.json里,也可能是单独的 .browserslistrc文件。两处都没有,就是在吃框架默认值。Next.js、Vite、Create React App的默认值各不相同,而且会随版本变,去对应文档里查当前版本的那个值,别凭印象。

把兼容包关掉能省多少?

对访客的实际传输量,接近于零,因为本来就没人下载。能省的是构建时间、CDN存储和部署包体积。如果你的构建流水线跑得慢,关掉legacy构建通常能砍掉可观的一段时间,这个收益比流量收益实在。

样式表里那些老前缀,能直接批量删吗?

不建议用正则批量删。这些前缀里混着少数今天仍然必要的(比如做多行文本截断的那个、控制表单控件外观的那个),一刀切会出事。可行的做法是在构建流程里配好自动加前缀的工具,让它按你的目标浏览器重新生成,而不是手工去删旧的。

为什么好几个不相干的站会发同一份文件?

因为那份文件是框架生成的,同一个框架版本产出同一份字节。文件名里那串哈希就是内容哈希,内容一样哈希就一样。这也意味着哈希可以当框架版本的指纹用——两个站哈希相同,它们的框架版本大概率也相同。

这件事和SEO到底有没有关系?

直接关系很弱。爬虫不下载nomodule脚本,样式表里那点前缀字节也影响不了什么。有关系的是间接层面:构建配置多年没动的站,往往其他该跟进的事也没跟,比如新的图片格式、新的加载优先级提示、新的结构化数据类型。它是个信号,不是个原因。

这批数字能代表整个行业吗?

不能。样本是131个海外品牌电商站,能测的118个,其中Shopify站占了一多半,Next.js站只有13个——而nomodule那20.3%里,Next.js站贡献了13个,占了一半以上。换一批以自建站为主的样本,比例会完全不同。方法可以照搬,数字不要照搬。

权威参考资料

分享到
标签
版权声明

本文标题:《14个不相干的站,发的是同一份给老浏览器的补丁》

本文链接:https://zhangwenbao.com/legacy-browser-bundle-factory-default-audit.html

版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交