core-js 3 实战指南:从按需 polyfill 到 Babel 配置的完整路径
【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js
你的代码用了Set.prototype.union和Promise.allSettled,CI 里跑得好好的,一到老版 iOS Safari 就抛undefined is not a function——这类报错的根子,是目标引擎还没实现这些新 API。core-js 就是为这一步兜底的模块化标准库 polyfill:它把 ECMAScript 与 Web 标准的实现按模块拆开,让你按缺口补齐,而不是赌用户环境的运气。
为什么 polyfill 这件事还值得认真做
写代码时你面对的不是一个环境,而是一堆版本各异的引擎:V8、JavaScriptCore、SpiderMonkey、Hermes,各自实现新特性的时间点都不一样。polyfill 的通用思路是「加载时先探测,缺什么补什么」,而 core-js 的每个模块都内置了这类检测逻辑——目标环境已经原生支持的能力不会被重复覆盖。
它值得单独拿出来讲的理由有两个。一是覆盖面:从 Promise、Symbol、迭代器、TypedArray 这些早已标准化的能力,到 Set 操作方法、Promise.try这类提案阶段的新 API,再到URL、structuredClone等 WHATWG/W3C 跨平台特性,都在一套模块体系里。二是工程化:它自带一份「各浏览器版本实现了哪些模块」的数据(core-js-compat),把「要不要补」从经验判断变成了数据查询,这正是构建工具能自动化处理 polyfill 的前提。
能力拆解一:模块化标准库,每个 API 一个独立模块
结论:core-js 的价值首先在于「颗粒度」——它不做一整块的全量补丁,而是把每个 API 拆成可单独加载的模块。
- 模块粒度细到单个方法,例如
es.array.flat-map只负责Array.prototype.flatMap一项,互不牵连; - 覆盖范围横跨已标准化特性与提案特性,仓库里
packages/core-js/modules/下的 500 多个模块文件就是这套体系的实物; - 每个模块加载时都会先检测原生实现,已有能力直接跳过,所以「多打一份补丁」的开销通常为零。
能力拆解二:入口分级,用目录结构表达成熟度
结论:入口的命名规则就是 core-js 的质量分级表,看懂它就能少踩很多版本坑。
模块前缀传递的是成熟度信号:es.前缀代表已标准化的 ECMAScript 特性,web.前缀代表 Web 标准,esnext.前缀代表提案阶段的能力——提案不等于稳定,用之前应当确认它所处的 stage。目录层面则有stable/(只含已稳定 API)、actual/(稳定 + 已实现提案)、full/(再加到非标准扩展)、proposals/(按提案主题聚合)几档。选哪一档,本质是「敢用多新的 API」这个问题的工程化表达。
能力拆解三:core-js-pure 与 core-js-compat,两种不同的「省」
结论:想省包体有两种互不冲突的路线——不污染全局,和按目标环境裁剪。
core-js-pure把同样的能力以命名导入的方式提供,不碰全局命名空间,适合库和框架场景:你的补丁实例和用户的补丁实例各走各的,不会出现 A 依赖Promise补丁、B 依赖原生Promise时互相打架的问题。
core-js-compat则回答另一个问题:「按我的 targets,到底缺哪些模块?」它接受 browserslist 查询,返回所需模块清单和每个模块对应的目标版本区间。构建工具(包括 Babel)正是消费这份数据来决定注入行为的——数据驱动,而非人肉拍脑袋。
Babel 集成:preset-env 的 useBuiltIns 三种模式怎么选
如果你用 Babel 做语法降级,先记住一件事:@babel/polyfill已废弃,替代写法是入口式引入。
下面这段是 Babel 7.4 之后比较典型的 preset-env 配置,usage模式会自动扫描源码、只注入实际用到的 polyfill:
// babel.config.js module.exports = { presets: [ ['@babel/preset-env', { targets: 'defaults, not ie 11', useBuiltIns: 'usage', corejs: 3, }], ], };三种模式各有适用面:entry模式由你在入口声明core-js/stable,Babel 负责按 targets 删减冗余模块,体积确定、行为透明,适合对包体敏感的站点;usage模式自动按需注入,7.4 之后检测准确度大幅提升,适合多入口库和 monorepo;manual则是完全手工,一般只有特殊场景才需要。若项目里还有 async/await,别忘了入口处补一行regenerator-runtime/runtime,它负责生成器函数的运行时支持,属于 polyfill 体系的另一半。
上手建议与常见坑
三条可直接执行的建议:
- 先用
core-js-compat查一遍你的 targets 实际需要的模块清单,往往比直觉少得多,这决定了后面所有取舍; - 先定「全局还是局部」的基调:应用项目倾向全局入口,库和框架倾向
core-js-pure,两者混用时要清楚各自的边界——pure 版不补全局环境; - 升级 core-js 大版本或 Babel 时,把兼容测试跑完整再放量。
几个高频坑:不要从旧博客照抄core-js/stage/...这类早已变动的入口名,以仓库当前目录结构为准;esnext.提案不要整包引入生产,按proposals/下的主题挑选;useBuiltIns: 'usage'在 Babel 7.4 之前存在漏检,老项目要么升级 Babel,要么退回entry模式。
小结
core-js 3 把「支持到哪个浏览器版本」从一句口号,变成了构建产物里一条可枚举、可裁剪的模块清单;再叠加 Babel 的自动注入,polyfill 这件事才算真正从手工活变成了流水线。随着提案持续进入标准,这套「数据驱动 + 模块化」的架构还会继续接住下一批新 API。
【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考