core-js-compat 兼容性数据引擎:基于 Browserslist 精准计算 core-js polyfill 模块清单
【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js
core-js-compat是 core-js 生态中专司“兼容性情报”的包:它内置了每个 core-js 模块(如es.array.at、esnext.iterator.map)在各个运行时引擎中可用/缺失的版本数据,并对外提供compat()这一核心查询 API,输入一段 Browserslist 查询或目标环境对象,即可精确输出“需要打补丁的模块清单”与“每个模块具体缺在哪个引擎版本”。在 core-js 项目中,它是core-js-builder按目标环境裁剪构建产物的数据源;对于任何想实现按需 polyfill、减少打包体积的工程化项目,掌握它就能把“兼容性策略”从拍脑袋变成可编程、可验证的工程决策。读完本文你将掌握compat()全部选项的语义、targets的两种写法、模块过滤与版本回溯的机制,并理解这份数据是如何从源码生成和校验的。
一、core-js-compat 是什么:包的定位与核心数据流
从仓库目录 packages/core-js-compat/package.json 可以看到,当前仓库中该包的版本为3.50.0,类型为 CommonJS("type": "commonjs"),入口是 index.js,并附带了完整的 TypeScript 声明("types": "index.d.ts")。它唯一的运行时依赖是browserslist(^4.28.8),Node 版本要求>= 6.4.0。
包的职责可以用一句话概括:维护“core-js 模块 × 运行时引擎版本”的兼容性矩阵,并把“目标环境”翻译成“需要的模块列表”。整个数据流如下:
- 原始数据保存在 packages/core-js-compat/src/data.mjs,形如
{ 'es.array.at': { chrome: '92', firefox: '90', safari: '15.4', ... } },表示“该模块在各引擎中最早可用的版本”; - 构建脚本 scripts/build-compat/data.mjs 通过 src/mapping.mjs 中的引擎映射表(Chrome→Node、Chrome→Deno、Chrome→Electron、Safari→iOS 等)把稀疏数据补全为完整矩阵,产出
data.json、modules.json、external.json等构建产物; - 运行时入口 compat.js 读取这些数据,结合 targets-parser.js 解析出的目标环境,逐模块比对版本,输出
{ list, targets }。
二、核心 API:compat()一步求出所需模块清单
官方 README 给出的最典型用法如下,这也是core-js-builder内部实际调用的方式:
import compat from 'core-js-compat'; const { list, // array of required modules targets, // object with targets for each module } = compat({ targets: '> 1%', // browserslist query or object of minimum environment versions to support modules: [ // optional list / filter of modules - regex, string or an array of them: 'core-js/actual', // - an entry point 'esnext.array.unique-by', // - a module name (or just a start of a module name) /^web\./, // - regex that a module name must satisfy ], exclude: [ // optional list / filter of modules to exclude, the signature is similar to `modules` 'web.atob', ], version: '3.50', // used `core-js` version, by default - the latest inverse: false, // inverse of the result - shows modules that are NOT required for the target environment });list是按顺序排列的模块名数组,可直接作为 polyfill 引入清单使用;targets则以模块名为键,给出该模块在哪些引擎的哪些版本上确实缺失(即需要打补丁的具体环境)。例如上述配置在文档中给出的输出形态为:
console.log(targets); /* => { 'es.error.cause': { ios: '14.5-14.8' }, 'es.array.includes': { firefox: '100' }, 'es.array.push': { chrome: '100', edge: '101', ios: '14.5-14.8', safari: '15.4' }, 'esnext.array.group': { chrome: '100', edge: '101', firefox: '100', ios: '14.5-14.8', safari: '15.4' }, 'web.immediate': { chrome: '100', edge: '101', firefox: '100', ios: '14.5-14.8', safari: '15.4' }, 'web.structured-clone': { chrome: '100', edge: '101', firefox: '100', ios: '14.5-14.8', safari: '15.4' } // ... } */注意一个细节:targets中的版本是目标环境的版本,而不是该模块“缺失”的边界版本。比如'es.array.at': { ios: '14.5-14.8' }表示“在您声明的目标集合里,iOS Safari 14.5-14.8 还不支持Array.prototype.at,因此需要引入es.array.at这个模块”。判定逻辑在 compat.js 的checkModule中实现:
function checkModule(name, targets) { const result = { required: !targets, targets: {} }; if (!targets) return result; const requirements = data[name]; // 该模块在各引擎的“最低可用版本” for (const [engine, version] of targets) { if (!has(requirements, engine) || compare(version, '<', requirements[engine])) { result.required = true; result.targets[engine] = version; } } return result; }即:目标引擎版本低于该模块的最低可用版本,或数据中根本没有该引擎的记录,则该模块对当前环境是必需的。版本比较使用 helpers.js 中自实现的三段式 SemVer 比较(major.minor.patch逐段比较,缺省段按0处理),不依赖额外的 semver 库。
结果集合的组装顺序
在 compat.js 的主流程中,最终结果的组装顺序是:
modules(或已废弃的filter)先经normalizeModules归一化为 Set;exclude同样归一化后,把被排除的模块从modules中剔除;- 若指定了
version,用getModulesListForTargetVersion(version)求交集,保证结果只包含该 core-js 版本实际存在的模块; - 默认(非
inverse)情况下调用filterOutStabilizedProposals,把“已转正的提案模块”过滤掉(见下文); - 逐模块执行
checkModule,满足条件(check.required ^ inverse为真)时加入list与targets。
modules/exclude若传了非法值(如空字符串导致匹配不到任何模块),会抛出Specified invalid module name or pattern的TypeError(见 compat.js)。
三、targets选项详解:Browserslist 查询与目标对象
targets支持两种形态:一段 Browserslist 查询字符串,或一个声明“各引擎最低支持版本”的对象。
3.1 Browserslist 查询
直接传入字符串即可:
'defaults, not IE 11, maintained node versions'它会被交给 targets-parser.js 内部的browserslist(query)展开为[引擎, 版本]对列表。browsers字段也接受同样的查询形式(见下文 3.3)。
3.2 目标对象:完整字段表
对象形式声明各引擎的最低版本,以下字段全部可选(示例与注释来自官方 README,版本值均为字符串):
({ android: '4.0', // Android WebView version bun: '0.1.2', // Bun version chrome: '38', // Chrome version 'chrome-android': '18', // Chrome for Android version deno: '1.12', // Deno version edge: '13', // Edge version electron: '5.0', // Electron framework version firefox: '15', // Firefox version 'firefox-android': '4', // Firefox for Android version hermes: '0.11', // Hermes version ie: '8', // Internet Explorer version ios: '13.0', // iOS Safari version node: 'current', // NodeJS version, 'current' = 当前运行的 Node 版本 opera: '12', // Opera version 'opera-android': '7', // Opera for Android version phantom: '1.9', // PhantomJS headless browser version quest: '5.0', // Meta Quest Browser version 'react-native': '0.70', // React Native version (默认 Hermes 引擎) rhino: '1.7.13', // Rhino engine version safari: '14.0', // Safari version samsung: '14.0', // Samsung Internet version esmodules: true | 'intersect', // 见 3.3 browsers: '> 0.25%', // Browserslist query 或含目标浏览器的对象 })类型定义见 compat.d.ts:这些引擎名来自 shared.d.ts 中声明的Target联合类型,且支持别名quest/oculus、react-native/react/reactnative、opera-android/opera_mobile(opera_mobile已标记为 deprecated)。
3.3esmodules与browsers两个特殊字段
browsers:值可以是一段 Browserslist 查询(字符串或数组),也可以是{ engine: version }形式的目标对象,二者都会被展开并合并进最终的引擎集合(见 targets-parser.js)。esmodules: true:忽略browsers目标,直接使用“支持 ES Modules 的所有浏览器”的最低版本集合。该集合来自 src/external.mjs:export default { modules: { bun: '0.1.1', chrome: '61', deno: '1.0', edge: '16', firefox: '60', node: '13.2', safari: '10.1', }, };从这份数据可见,ES Modules 基线定义为 Chrome 61、Edge 16、Firefox 60、Safari 10.1、Node 13.2 等(见 targets-parser.js)。
esmodules: 'intersect':将browsers目标与browserslist目标取交集,每个引擎取两者中更高的版本(因为版本越高越严格,取最大值等价于“同时满足两套要求”)。实现见 targets-parser.js:若某引擎不在 ES Modules 基线数据中,则从结果中删除。
3.4 解析细节:别名、合法性过滤与去重
targets-parser.js还做了几件容易被忽略的事(targets-parser.js):
- 引擎别名归一化:
and_chr→chrome-android、and_ff→firefox-android、ie_mob→ie、ios_saf→ios、oculus→quest、op_mob/opera_mobile→opera-android、react/reactnative→react-native; - 目标键统一转为小写(
toLowerKeys); - 只保留
validTargets白名单(targets-parser.js)中的引擎,未知引擎被静默过滤; - 同一引擎出现多个版本时,
reduced取最低版本(compare(version, '<=', reduced.get(engine))时更新),因为目标是“最低支持的版本”; node: 'current'会被替换为process.versions.node的实际版本号。
四、模块过滤:modules与exclude
modules与exclude使用完全相同的过滤器语法(见 compat.js),支持三种形式,可混合传入数组:
| 形式 | 示例 | 语义 |
|---|---|---|
| 入口点(entry point) | 'core-js/actual' | 展开为该入口点下挂载的全部模块,依据entries映射 |
| 模块名前缀 | 'esnext.array.unique-by' | 精确匹配该模块名;若传的是前缀(如'esnext.array.'),匹配所有以该前缀开头的模块 |
| 正则 | /^web\./ | 模块名需满足该正则 |
具体匹配逻辑:字符串先查entries映射表(存在则直接返回对应模块数组),否则退化为allModules.filter(it => it.startsWith(filter));正则则直接对全部模块名执行test。无论哪种方式,匹配结果为空都会抛TypeError。最终经normalizeModules合并为一个Set去重。
exclude的典型用途是排除某些已知有坑或有替代方案的模块(例如 README 示例中排除了web.atob),它会在modules归一化之后被剔除。filter参数是modules的旧名(已在类型声明中标记@deprecated,见 compat.d.ts,并计划在 core-js@4 移除,见 compat.js 的 TODO 注释)。
已转正提案的自动折叠
默认情况下,compat()会执行filterOutStabilizedProposals(helpers.js):若同时存在esnext.xxx与已转正的es.xxx两个模块,则删除esnext.xxx。这与 core-js 的版本演进策略一致——提案一旦进入标准,esnext.命名空间的模块会被es.版本取代,避免同一功能重复打补丁。在 src/data.mjs 中可以看到大量renamed映射(如esnext.array.at→es.array.at),正是这套演进机制的数据侧印证。
五、version与inverse:版本约束与反向查询
5.1version:限定 core-js 版本范围
不同 core-js 版本能提供的模块集合不同,version用于把结果限制在指定版本的可用模块内,默认是最新版本。其实现是 get-modules-list-for-target-version.js:
module.exports = function (raw) { const corejs = semver(raw); if (corejs.major !== 3) { throw new RangeError('This version of `core-js-compat` works only with `core-js@3`.'); } const result = []; for (const version of Object.keys(modulesByVersions)) { if (compare(version, '<=', corejs)) { result.push(...modulesByVersions[version]); } } return intersection(result, modules); };modulesByVersions(数据源在 src/modules-by-versions.mjs)记录了“从 3.1 开始每个 minor 版本新增了哪些模块”,因此该函数返回“目标版本及之前累积引入的所有模块”,再与全部模块求交集保证顺序与合法性。注意它只支持 core-js@3(传入其他主版本会抛RangeError)。在 compat.js 中,这个结果再与已过滤的modules取交集,完成版本约束。
5.2inverse:反向模式
inverse: true时输出逻辑取反(compat.js 的check.required ^ inverse):返回目标环境中“不需要”的模块列表。这在做兼容性审计或对比不同目标策略时很有用——例如想确认“哪些新 API 我可以放心在目标环境直接用,而无需 polyfill”。注意反向模式下,filterOutStabilizedProposals 不再执行,以保证结果完整。
六、附加 API:data/entries/modules/getModulesListForTargetVersion
core-js-compat默认导出的其实是compat()函数与以下四个属性的合并对象(见 index.js),也可以按子路径单独引入:
// 等价于上面的 compat({ targets, modules, version }) require('core-js-compat/compat')({ targets, modules, version }); // => { list, targets } // 或 require('core-js-compat').compat({ targets, modules, version }); // 完整兼容性数据:{ [模块名]: { [引擎名]: 最早支持版本 } } require('core-js-compat/data'); // 或 require('core-js-compat').data; // 入口点 → 模块数组 的映射:{ [入口点]: Array<模块名> } require('core-js-compat/entries'); // 或 require('core-js-compat').entries; // 全部模块名数组 require('core-js-compat/modules'); // 或 require('core-js-compat').modules; // 指定 core-js 版本可用的模块子集 require('core-js-compat/get-modules-list-for-target-version')('3.50'); // => Array<模块名> // 或 require('core-js-compat').getModulesListForTargetVersion('3.50');其中data是判断“某模块在某引擎是否缺失”的直接依据:结构为{ [ModuleName]: { [EngineName]: EngineVersion } },值为该引擎中该模块的最低可用版本。TypeScript 侧对应声明见 index.d.ts 与 get-modules-list-for-target-version.d.ts。entries映射则由 scripts/build-compat/entries.mjs 通过静态解析packages/core-js下各入口文件(actual/、es/、full/、stable/、web/、proposals/、stage/等)的 import 依赖链自动生成,因此入口点展开结果与实际打包行为完全一致。
七、数据是如何生成的:从稀疏源数据到完整矩阵
7.1 源数据与手工标注的“坑”
src/data.mjs(约 3300 行)是手工维护的稀疏兼容性数据,只标注部分引擎,并大量注释了各引擎的真实 Bug 场景。例如:
es.symbol.dispose:Node 标注为20.5.0,注释说明 Node 20.4.0 虽引入该 API 但 descriptor 实现有误(src/data.mjs);es.array.includes:Firefox 标注102(而非最初支持的 48),注释指出 FF99-101 在稀疏数组上存在缺陷(src/data.mjs);es.suppressed-error.constructor:Chrome 标注 136,注释记录了 Chromium 多次启用/回滚的历史(src/data.mjs)。
这解释了为什么兼容性数据不能用“第一次实现版本”一刀切——core-js 的兼容性判定标准是“行为是否与规范完全一致”,而非“API 是否存在”。
7.2 引擎映射与自动补全
src/mapping.mjs 维护了跨引擎版本映射:ChromeToNode(含 io.js 历史版本)、ChromeToDeno、ChromeToElectron、ChromeToOpera(分段公式)、ChromeToChromeAndroid、ChromeAndroidToSamsung、SafariToIOS、SafariToBun、HermesToReactNative等,每个表都标注了数据来源(如 Node 发行记录、Electron releases、MDN browser-compat-data)。构建脚本 scripts/build-compat/data.mjs 会基于这些映射把稀疏数据补全为完整矩阵:例如es.*模块通过ChromeToNode/ChromeToDeno推断 Node/Deno 版本,通过ChromeToElectron推断 Electron 版本,再通过SafariToIOS等补齐移动端,最后对键排序输出 JSON 构建产物(data.json、modules.json、external.json),并同步生成浏览器端测试数据 tests/compat/compat-data.js 的基线。
7.3 数据的正确性校验
仓库提供了两层自动化保障:
- tests/compat-data/tests-coverage.mjs 校验“每个 compat 数据模块都有对应的运行时测试”:它把数据中的全部模块与 tests/compat/tests.js 里注册的测试做比对,缺失测试(或新增了数据外的测试)都会直接报错,确保数据不落后于测试、测试不落后于数据;
- tests/compat-data/modules-by-versions.mjs 校验
modules-by-versions与线上core-js-compat@3.0.0基线一致,防止新增模块漏登记版本。
八、在 core-js 生态中的真实应用:core-js-builder 的按需构建
compat()最直接的消费者是core-js-builder。在 packages/core-js-builder/index.js 中:
const { list, targets: compatTargets } = compat({ targets, modules, exclude: blacklist || exclude });构建器把compat()返回的list作为 webpack 的入口模块数组(list.map(it => require.resolve(core-js/modules/${ it })),见 index.js),从而只为目标环境打包缺失的 polyfill;targets则用于在summary输出中打印每个模块对应的缺失环境(index.js)。从 core-js-builder/index.d.ts 可以看到,builder 的modules、exclude、targets选项类型直接复用了core-js-compat的CompatOptions。这就是“按需 polyfill、压缩体积”的核心链路:环境声明 → compat 数据 → 精确模块清单 → 最小化打包。
九、写在最后:如何为这份数据做贡献
如果你在真实环境发现某个引擎版本的兼容性标注不准确,可以参与维护这份数据。仓库的 CONTRIBUTING.md 中“如何更新 core-js-compat 数据”一节说明了流程:通常需要先在 tests/compat/tests.js 中补充/修正对应模块的运行时测试,再更新 src/data.mjs 中的版本标注,然后重新运行构建脚本 scripts/build-compat/data.mjs 生成产物,并确保通过 tests/compat-data 下的覆盖率校验。仓库还提供了可视化兼容性表格与浏览器测试运行器,可直接在浏览器中逐模块查看各引擎的支持情况(其数据来源即 tests/compat/tests.js 的运行时探测结果)。
从“目标环境”到“精确 polyfill 清单”,core-js-compat用一份可维护、可测试、可版本回溯的数据,把前端工程化里最容易被拍脑袋决定的兼容性策略,变成了一套严谨可复用的基础设施。
【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考