14个不相干的站,发的是同一份给老浏览器的补丁
本文目录
- 你的页面到底在给谁多发一份代码?
- 怎么把“发给老浏览器的那一份”从产物里分出来?
- 先看标签属性
- 再看文件名
- 最后看字节
- 这份补丁有多大,多少人在发?
- 两种工具的思路差了一个数量级
- 为什么14个不相干的品牌,发的是同一串字节?
- 那份浏览器名单,到底是谁写的?
- 这份名单真的是2018年定下来的吗?
- 我拿这串名单推产物,又推错了一次
- 样式表那一层,比脚本还老
- 这些字节的代价,到底有多大?
- 兼容包这一头,代价基本是零
- 样式表这一头,是真花钱但花得不多
- 构建这一头,倒是真花时间
- 真正的代价在别处
- 自己站上怎么查一遍?
- 常见问题解答
- 页面上有nomodule脚本,会拖慢加载吗?
- 怎么知道我的构建目标是什么?
- 把兼容包关掉能省多少?
- 样式表里那些老前缀,能直接批量删吗?
- 为什么好几个不相干的站会发同一份文件?
- 这件事和SEO到底有没有关系?
- 这批数字能代表整个行业吗?
- 权威参考资料
摘要: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-哈希.js、index-legacy-哈希.js、polyfills-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 12 | 9 | anker、arcteryx、bigcommerce、byredo、eufy、gymshark、quince、soundcore、vuoriclothing |
| chrome 111 / edge 111 / firefox 111 / safari 16.4 | 1 | braun |
九比一。九个站的清单里,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.6 | chrome 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.com | chrome 64… | 637处 | 184处 |
| bigcommerce.com | chrome 64… | 514处 | 272处 |
| anker.com | chrome 64… | 399处 | 59处 |
| eufy.com | chrome 64… | 159处 | 28处 |
| braun.com | chrome 111… | 0处 | 0处 |
写着chrome 64的站,产物里可选链最多的一个出现了637次;写着chrome 111的那个站反而一次都没有。
结论是:这串常量管的是框架自己那部分代码的编译,管不到应用代码和预编译过的第三方包。一份配置被打进产物,不等于产物整体都听它的。braun那个0也不能反过来解读成“它更保守”,更可能只是我抓到的那份文件恰好没用到这个语法。
顺便把全样本的现代语法出现率也列一下,这组数字本身有点意思:100个可分析主产物里,可选链出现在67个站上,空值合并45个,异步函数61个,箭头函数89个。也就是说,同一个页面上,业务代码是按今年的浏览器编译的,兼容包是按几年前那份名单发的——两套年代并排跑,谁也不知道对方存在。
样式表那一层,比脚本还老
JS这边至少还有nomodule兜底,浏览器认得出来就不下载。CSS没有这个机制:写在样式表里的每一行,所有人都要下载,所有人都要解析。
我拿79份样式表做了一遍前缀考古,只统计那些“无前缀写法早就能用了”的属性。87.3%的样式表里至少还留着一条。
| 写法 | 站点覆盖 | 出现次数 | 它是给谁准备的 |
|---|---|---|---|
| -webkit-box-orient | 55.7% | 514 | 2009年那版flexbox草案 |
| -moz-column | 51.9% | 1019 | Firefox 51及更早 |
| -webkit-backface-visibility | 34.2% | 79 | Safari 8及更早 |
| -webkit-transform | 26.6% | 1624 | Safari 8及更早 |
| -ms-flex | 19.0% | 2794 | IE 10 / 11 |
| -webkit-box-flex / -pack / -align | 10%—11% | 1052 | 2009年那版flexbox草案 |
| -khtml- | 6.3% | 7 | Konqueror与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至少能确定这个站的框架还没跨过某个门槛。这个信息在做竞品分析和技术尽调时值钱。
第二,它是“没人负责”的证据。一个站可以每周上新品、每月改首页,同时它的构建配置从项目初始化那天起就没被打开过。这两件事完全不冲突,而后者才是排查问题时真正卡人的地方。产物里还留着什么别的痕迹,许可声明那一轮顺着同一批文件量过另一个维度。
第三,删这些东西的收益低、风险不为零,所以它会一直躺在那里。这才是它能活这么久的真正原因——不是没人知道,是没人有动力。
自己站上怎么查一遍?
浏览器里五分钟能查完,不用装任何东西。
- 打开首页,查看源代码,搜
nomodule。搜到了就说明你在发这份东西,搜不到不代表没有——有些框架用运行时判断,不写在HTML里。 - 开发者工具的网络面板筛JS,看文件名里有没有
polyfill、legacy、es5这几个词。找到就点开看体积。 - 如果站是自己构建的,翻
package.json里的browserslist字段,或者项目根目录的.browserslistrc。没有这两样,说明你在用框架的默认值——那就去框架文档里查默认值是什么。 - 样式表那边,把主样式表下载下来,搜
-ms-、-moz-、-khtml-。数一下有多少条,别急着删。 - 把查到的结果连同日期记一笔。这份记录的价值在半年后——那时候你能看出哪些跟着构建流程自己变了,哪些原地没动。
关于删不删,我的建议是分三类处理:应用自己写的老前缀,下次改那个组件时顺手清掉;构建工具自动加的,去改配置而不是改产物;平台和第三方注入的,记进台账但别硬删——删了下次平台更新还会回来,白忙一场。哪些东西是第三方带进来的,可以对着只读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
← 上一篇
62个站的前端库版本:中位停在四年前,最老的是2011年下一篇 →
没有了