JS 生态系统加速Polyfill函数使用实例探索
作者:大家的林语冰 人猫神话
引言
长话短说:一大坨人气爆棚的 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(Internet Explorer 9)也支持它。虽然但是,一大坨软件包依赖 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函数的资料请关注脚本之家其它相关文章!