JS 生态中 Polyfill 滥用导致依赖膨胀分析与优化

作者:性能诊所时间:2026-10-31 10:59:30浏览:259

本文深入分析 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 函数的资料请关注本站其它相关文章!

作者最新文章