创见博客
core-js:让新 API 在旧浏览器中可用
七崽爱吃小饼干2026/09/16阅读 0专栏 webpack构建高级

说到兼容旧浏览器,很多人第一反应是 Babel。但 Babel 只解决语法问题,解决不了API问题——Promise、Array.prototype.includes、Object.assign 这些新方法在旧浏览器里根本不存在,语法转译再完美也变不出来。补上这些运行时能力的,就是 core-js。

一、为什么需要 core-js

先分清两类「新特性」:

第一类:语法(syntax)

js
const fn = (a = 1) => a; // 箭头函数、默认参数

旧浏览器不认识这些写法,会直接语法报错,页面完全跑不起来。这类由 Babel 转译解决——把新语法改写成旧语法。

第二类:内置 API(built-in)

js
Promise.resolve();
[1, 2, 3].includes(2);
Object.assign({}, { a: 1 });
'abc'.padStart(5, '0');
async function foo() {}

这些都是运行时方法,旧浏览器可能压根没有 Promise,甚至连 Array.prototype.includes 都是 undefined。语法转译帮不了它们——Babel 会原样保留 [1,2,3].includes(2),然后运行时抛出 includes is not a function。

要补上这类能力,就必须在运行时先定义好这些方法,也就是 polyfill。core-js 就是目前最主流、最完整的 polyfill 库。

二、core-js 是什么

core-js 是由 Denis Pushkarev(zloirock)维护的 JavaScript 标准库 polyfill 实现,特点是:

  • 覆盖全面:从 ES5 到最新标准,包括 Promise、Symbol、Map/Set、迭代器、各种数组/字符串/对象方法、URL、queueMicrotask 等。
  • 按模块组织:可以全量引入,也可以精细到单个特性。
  • 支持提案特性:除稳定特性外,还提供 proposals 目录下的实验性特性。
  • 被广泛依赖:Babel、主流框架及大量工具链都基于它做 polyfill。

它的引入粒度大致是:

core-js/stable          // 所有稳定特性
core-js/es              // 同上,仅 ES 标准
core-js/features        // 稳定 + 提案
core-js/es/array/from   // 单个特性

由于是污染全局的 polyfill(直接修改 Promise、Array.prototype 等),它能透明地让旧环境像新环境一样工作。

三、怎么用

1. 最直接:手动全量引入

js
// src/index.js 顶部
import 'core-js/stable';

这样所有稳定特性都会被补上,简单但体积最大——很多用不到的特性也一起进来了。

2. 按需手动引入

js
import 'core-js/es/promise';
import 'core-js/es/array/includes';
import 'core-js/es/object/assign';

体积可控,但需要人工判断用了哪些特性,维护成本高。

3. 配合 @babel/preset-env 自动引入(推荐)

这才是实际项目最常用的方式:Babel 根据代码里实际使用的特性和 browserslist 的目标环境,自动注入所需的 core-js 模块。

bash
npm install core-js --save-dev
js
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.m?js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              [
                '@babel/preset-env',
                {
                  useBuiltIns: 'usage', // 按使用情况自动引入
                  corejs: '3.37',       // 指定 core-js 版本
                },
              ],
            ],
          },
        },
      },
    ],
  },
};

useBuiltIns 的取值:

取值行为说明
false不自动引入 polyfill默认,需要自己管理
'entry'在入口处按目标环境引入所需 polyfill需手动写 import 'core-js/stable',Babel 会精简它
'usage'按代码实际使用逐文件引入最省体积,推荐应用项目使用

⚠️ corejs 选项必须与安装的 core-js 主版本匹配。使用 core-js@3 时写 corejs: 3 或 corejs: '3.37';如果写错,Babel 会给出警告,或引入错误的模块。

4. 不污染全局:transform-runtime

useBuiltIns 会修改全局对象。如果你开发的是类库/组件库,直接污染使用者的全局环境是不可接受的。这时改用 @babel/plugin-transform-runtime + @babel/runtime-corejs3:

bash
npm install @babel/runtime-corejs3 --save-dev
npm install @babel/plugin-transform-runtime --save-dev
js
{
  presets: ['@babel/preset-env'],
  plugins: [
    [
      '@babel/plugin-transform-runtime',
      {
        corejs: 3,     // 使用 core-js@3 做 polyfill
        helpers: true, // 复用 Babel helper,减小体积
      },
    ],
  ],
}

它会以模块局部引用的方式提供 polyfill,而不是改写全局,适合库作者。

5. 三种方式对比

方式是否污染全局体积适用场景
手动 import 'core-js/stable'是大快速验证、老项目
useBuiltIns: 'usage'是小(按需)应用项目(推荐)
transform-runtime + corejs否较小类库、组件库

注意:不要同时为同一个文件同时使用 useBuiltIns 和 transform-runtime 的 corejs polyfill,会造成重复引入和体积浪费,Babel 也会提示冲突。

四、和 browserslist 的配合

Babel 判断「需要补哪些 polyfill」的依据,正是 browserslist。目标环境越新,注入的 polyfill 越少:

json
{
  "browserslist": [
    "> 0.5%",
    "last 2 versions",
    "not dead"
  ]
}

如果目标里包含很老的浏览器,polyfill 会明显增多;反之,只面向现代浏览器时,core-js 的引入量会大幅减少。所以浏览器兼容范围是控制体积的关键杠杆。

五、注意事项

  1. core-js 有版本区分。core-js@2 与 @3 的模块路径不同(core-js/es6 vs core-js/es),确认 corejs 选项与实际安装版本一致。
  2. regenerator-runtime:使用 generator / async/await 时,除了 core-js,还需要 regenerator 运行时。useBuiltIns: 'usage' / 'entry' 通常会帮你自动引入,也可手动 import 'regenerator-runtime/runtime'。
  3. polyfill 必须早于业务代码执行。用 'usage' 时由 Babel 自动保证顺序;手动引入时一定要放在入口最前面。
  4. 不要重复引入。同一份 polyfill 被多个地方引入会增大体积,尽量统一由 preset-env 或入口管理。
  5. 有副作用的模块。core-js 属于有副作用的 polyfill,如果你配置了 Tree Shaking 的 sideEffects,注意不要把它误删(一般作为依赖引入的包不受项目 sideEffects 影响)。
  6. 维护现状:core-js 由个人长期维护,是否继续依赖可按团队情况评估。它仍是当前生态的事实标准,短期内没有等量替代品。

六、完整配置示例(应用项目)

js
// webpack.config.js
const path = require('path');

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/[name].[contenthash:8].js',
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.m?js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
            presets: [
              [
                '@babel/preset-env',
                {
                  // 保留 ESM,交给 webpack 做 Tree Shaking
                  modules: false,
                  useBuiltIns: 'usage',
                  corejs: '3.37',
                },
              ],
            ],
          },
        },
      },
    ],
  },
};

package.json:

json
{
  "browserslist": ["> 0.5%", "last 2 versions", "not dead"],
  "devDependencies": {
    "core-js": "^3.37.0"
  }
}

小结

问题答案
Babel 能补 Promise 吗不能,Babel 只管语法
谁负责补 APIcore-js
应用项目怎么用@babel/preset-env + useBuiltIns: 'usage' + corejs: 3
库项目怎么用@babel/plugin-transform-runtime + @babel/runtime-corejs3
体积怎么控制靠 browserslist + usage 按需引入
注意什么版本匹配、regenerator、避免重复引入

一句话:Babel 负责「让新语法能跑」,core-js 负责「让新 API 存在」。 两者结合,再加上合理的 browserslist,才能既兼容旧环境,又不让产物体积失控。

评论
0/100