JS 生态中 Polyfill 滥用导致依赖膨胀分析与优化
本文深入分析 JavaScript 生态系统中 Polyfill 函数被误用导致的依赖树爆炸问题。通过剖析 ESLint 相关插件源码,指出许多包在运行时直接调用 Polyfill 而非按需注入,导致大量冗余依赖。通过移除不必要的 Polyfill 依赖,可将依赖数量从 97 个大幅降至 15 个,显著优化构建速度与磁盘占用。
引言
简而言之,许多高人气 npm 包的依赖数量比实际需求多出 6 到 8 倍。其中大部分属于多余的 polyfill(功能补丁),这也是导致 node_modules 文件夹体积异常庞大的主要原因之一。eslint 生态系统似乎是这一现象中受冲击最严重的领域。
本期《前端翻译计划》分享“加速 JS 生态系统系列博客”中的部分内容,涵盖但不限于以下主题:
- PostCSS,SVGO 等工具
- 模块解析机制
- ESLint 配置与使用
- npm 脚本优化
- draft-js emoji 插件
- Polyfill 滥用现象
- 桶装文件崩溃问题
- Tailwind CSS
Polyfill 滥用现象
我们研究了运行时性能,并认为考察 Node 模块的安装时间同样具有参考价值。关于算法优化或使用高性能系统调用的讨论已有很多,但为何我们会遭遇此类问题?为何每个 node_modules 文件夹都如此臃肿?这些多余的依赖究竟源自何处?
这一切始于我的一位朋友发现其项目的依赖树结构异常。每次更新依赖时,都会引入数个新的子依赖,且随着后续每次更新,依赖总数持续增加。
├─┬ arraybuffer.prototype.slice 1.0.2
│ └─┬ define-properties 1.2.1
│ └── define-data-property 1.1.0
├─┬ function.prototype.name 1.1.6
│ └─┬ define-properties 1.2.1
│ └── define-data-property 1.1.0
├─┬ globalthis 1.0.3
│ └─┬ define-properties 1.2.1
│ └── define-data-property 1.1.0
├─┬ object.assign 4.1.4
│ └─┬ define-properties 1.2.1
│ └── define-data-property 1.1.0
├─┬ regexp.prototype.flags 1.5.1
│ ├─┬ define-properties 1.2.1
│ │ └── define-data-property 1.1.0
│ └─┬ set-function-name 2.0.1
│ └── define-data-property 1.1.0
├─┬ string.prototype.trim 1.2.8
│ └─┬ define-properties 1.2.1
│ └── define-data-property 1.1.0
├─┬ string.prototype.trimend 1.0.7
│ └─┬ define-properties 1.2.1
│ └── define-data-property 1.1.0
└─┬ string.prototype.trimstart 1.0.7
└─┬ define-properties 1.2.1
└── define-data-property 1.1.0
客观来说,一个包依赖其他包是有充分理由的。但我们开始观察到一个模式:新增的依赖大多是针对 JS 函数的 polyfill,而这些函数实际上已被广泛支持。例如,Object.defineProperties 方法自 2013 年发布的 Node 0.10.0 版本起就已存在,甚至 IE9 也支持它。然而,大量软件包仍依赖 polyfill 来实现这一功能。
在引入 define-properties 的各种包中,eslint-plugin-react 引起了我的注意,因为它是 React 生态系统中非常流行的插件。为什么它要为 Object.defineProperties 引入 polyfill?毕竟所有 JS 引擎都内置了该功能。
没有 Polyfill 的 Polyfill
阅读包的源码后发现了更奇怪的现象:polyfill 函数被直接导入并调用,而不是在运行时环境中修补缺失的功能。polyfill 的设计初衷是对用户代码透明。它应该检查需要打补丁的函数或方法是否可用,并且仅在需要时才添加它。当不需要 polyfill 时,它不应直接失效。对我而言,奇怪的是这些函数像库中的普通函数一样被直接使用。
// 为何 defined 函数直接导入?
var define = require('define-properties')
// 更糟糕的是,为何它被直接调用了?
define(polyfill, {
getPolyfill: getPolyfill,
implementation: implementation,
shim: shim
})
相反,它们应该直接调用 Object.defineProperties。polyfill 的重点在于环境补丁,而非直接调用。将其与 Object.defineProperties 的 polyfill 比较,正确的做法应该是:
// 检查当前环境是否已经支持 Object.defineProperties
// 如果是,那我们直接躺平就欧了
if (!Object.defineProperties) {
// Patch in Object.defineProperties here
}
讽刺的是,使用 define-properties 的高频热点位于其他 polyfill 内部,它们加载了更多 polyfill。define-properties 包依赖更多的依赖,而不仅仅是它本身。
var keys = require('object-keys')
// ...
var defineDataProperty = require('define-data-property')
var supportsDescriptors = require('has-property-descriptors')()
var defineProperties = function (object, map) {
// ...
}
module.exports = defineProperties
在 eslint-plugin-react 内部,该 polyfill 通过 Object.entries() 的 polyfill 加载来处理其配置:
const fromEntries = require('object.fromentries') // <- 为何它直接就使用了?
const entries = require('object.entries') // <- 为何它直接就使用了?
const allRules = require('../lib/rules')
function filterRules(rules, predicate) {
return fromEntries(entries(rules).filter(entry => predicate(entry[1])))
}
const activeRules = filterRules(allRules, rule => !rule.meta.deprecated)
整理小结
在撰写本文时,安装 eslint-plugin-react 总共需要引入高达 97 个依赖。我好奇其中有多少是 polyfill,并开始在本地逐个进行补丁处理。完成所有操作后,依赖总数断崖式下跌至 15 个。在原来的 97 个依赖项中,有 82 个不再需要。
巧合的是,在各种 eslint 预设中同样流行的 eslint-plugin-import 也显示出类似问题。安装后,node_modules 文件夹将塞满 87 个软件包。经过另一次本地清理之后,我将这个数字减少到了 17 个。
填满每个人的磁盘空间
现在您可能想知道自己是否深受其害。我进行了快速搜索,基本上您能想到的所有高人气 eslint 插件或预设都难逃此例。出于某种原因,这整个考验让我想起了不久前此行业发生的 is-even/is-odd 事件。
拥有如此多的依赖使得审核项目的依赖变得极其困难。这也浪费空间。例如:删除项目中的所有 eslint 插件和预设就删除了 220 个包。
pnpm -r rm eslint-plugin-react eslint-plugin-import eslint-import-resolver-typescript eslint-config-next eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-prettier prettier eslint-config-prettier eslint-plugin-react-hooks
Scope: all 8 workspace projects
. | -220 ----------------------
也许我们一开始就不需要那么多依赖。我想起了 Erlang 编程语言创建者的这句精彩名言:
您只想要一根香蕉,但您得到的是一只拿着香蕉的大猩猩和整个丛林。
免责声明
本文属于语冰的直男翻译,略有删改,仅供粉丝参考。英文原版请传送 Speeding up the JavaScript ecosystem - Polyfills gone rogue[1]
https://marvinh.dev/blog/speeding-up-javascript-ecosystem-part-6
以上就是 JS 生态系统加速 Polyfill 函数使用实例探索的详细内容,更多关于 JS Polyfill 函数的资料请关注本站其它相关文章!
JS数组查找性能对比:find、some与includes该选谁
2026-11-05 17:48:00
怎么优化 React 应用性能?避免不必要的重渲染
2026-11-05 12:43:40
怎么利用 Nuxt.js useHead 优化 SEO 与元数据管理
2026-11-05 10:37:58
JS混淆常见手段解析:字符串加密与控制流平坦化还原思路
2026-11-04 19:07:42
JavaScript能在浏览器中直接获取MAC地址吗
2026-11-03 20:34:17
