普通视图

发现新文章,点击刷新页面。
今天 — 2026年1月21日首页

Polyfill方式解决前端兼容性问题:core-js包结构与各种配置策略

作者 漂流瓶jz
2026年1月20日 23:57

简介

在之前我介绍过Babel:解锁Babel核心功能:从转义语法到插件开发,Babel是一个使用AST转义JavaScript语法,提高代码在浏览器兼容性的工具。但有些ECMAScript并不是新的语法,而是一些新对象,新方法等等,这些并不能使用AST抽象语法树来转义。因此Babel利用core-js实现这些代码的兼容性。

core-js是一个知名的前端工具库,里面包含了ECMAScript标准中提供的新对象/新方法等,而且是使用旧版本支持的语法来实现这些新的API。这样即使浏览器没有实现标准中的新API,也能通过注入core-js代码来提供对应的功能。

像这种通过注入代码实现浏览器没有提供的API特性,叫做Polyfill。这个单词的本意是填充材料,在JavaScript领域中,这些注入的代码就类似“填充材料”一样,帮助我们提高代码的兼容性。另外core-js还提供了一些还在提议中的API的实现。

core-js使用方式

使用前后对比

要想看到core-js使用前后的效果对比,首先需要确定某个特性和对应的执行环境,在这个环境中对应的特性不存在。我本地是Node.js v18.19.1版本,这个版本并没有实现Promise.try这个方法,因此我们就用这个方法进行实验。首先是没有引入core-js的场景:

Promise.try(() => {
  console.log('jzplp!')
})

/* 执行结果
Promise.try(() => {
           ^
TypeError: Promise.try is not a function
*/

可以看到没有引入core-js,直接使用Promise.try时,会因为没有该方法而报错。然后再试试引入core-js的效果:

require('core-js')
Promise.try(() => {
  console.log('jzplp!')
})

/* 输出结果
jzplp!
*/

可以看到引入core-js后,原本不存在的API被填充了,我们的代码可以正常执行并拿到结果了。这就是core-js提高兼容性的效果。

单个API引入

core-js不仅可以直接引入全部语法,还可以仅引入单个API,比如某个对象或某个方法。首先看下只引入Promise对象:

// require('core-js/full') 等于 require('core-js')
require('core-js/full/promise')
Promise.try(() => {
  console.log('jzplp!')
})

/* 输出结果
jzplp!
*/

然后再看下直接引入对象中的某个方法:

require('core-js/full/promise/try')
Promise.try(() => {
  console.log('jzplp!')
})

/* 输出结果
jzplp!
*/

不注入全局对象

前面展示的场景,core-js都是将API直接注入到全局,这样使用这些API就如环境本身支持一样,基本感受不到区别。但如果我们不希望直接注入到全局时,core-js也提供了使用方式:

const promise = require('core-js-pure/full/promise');
promise.try(() => {
  console.log('jzplp!')
})
Promise.try(() => {
  console.log('jzplp!2')
})
/* 输出结果
jzplp!
Promise.try(() => {
           ^
TypeError: Promise.try is not a function
*/

可以看到,使用core-js-pure这个包之后,可以直接导出我们希望要的API,而不直接注入到全局。此时直接使用全局对象方法依然报错。而core-js这个包虽然也能导出,但它还是会直接注入全局,我们看下例子:

const promise = require('core-js/full/promise');
promise.try(() => {
  console.log('jzplp!')
})
Promise.try(() => {
  console.log('jzplp!2')
})

/* 输出结果
jzplp!
jzplp!2
*/

因此,如果希望仅使用导出对象,还是需要使用core-js-pure这个包。core-js-pure也可以仅导出对象方法:

const try2 = require("core-js-pure/full/promise/try");
Promise.try = try2;
Promise.try(() => {
  console.log("jzplp!");
});

/* 输出结果
jzplp!
*/

因为导出的对象方法不能独立使用,因此在例子中我们还是将其注入到Promise对象后使用。

特性分类引入

core-js中包含非常多API特性的兼容代码,有些是已经稳定的特性,有些是还处在提议阶段的,不稳定的特性。我们直接引入core-js会把这些特性全部引入,但如果不需要那些不稳定特性,core-js也提供了多种引入方式:

  • core-js 引入所有特性,包括早期的提议
  • core-js/full 等于引入core-js
  • core-js/actual 包含稳定的ES和Web标准特性,以及stage3的特性
  • core-js/stable 包含稳定的ES和Web标准特性
  • core-js/es 包含稳定的ES特性

这里我们举两个例子尝试下。首先由于ECMAScript标准一直在更新中,有些特性现在是提议,未来可能就已经被列入正式特性了。因此这里的例子需要明确环境和core-js版本。这里我们使用Node.js v18.19.1和core-js@3.47.0版本,以写这篇文章的时间为准。

首先第一个特性是:数组的lastIndex属性,这是一个stage1阶段的API,这里针对不同的引入方式进行尝试:

// 不引入core-js尝试
const arr = ["jz", "plp"];
console.log(arr.lastIndex);
/* 输出结果
undefined
*/

// 引入core-js/full
require("core-js/full");
const arr = ["jz", "plp"];
console.log(arr.lastIndex);
/* 输出结果
1
*/

// 引入core-js/actual
require("core-js/actual");
const arr = ["jz", "plp"];
console.log(arr.lastIndex);

/* 输出结果
undefined
*/

首先当不引入core-js时,因为不支持这个API,所以输出undefined。core-js/full支持stage1阶段的API,可以正确输出结果。但core-js/actual仅支持stage3阶段的API,因此还是不支持这个API。

然后我们再看下另外一个API,数组的groupBy方法。这是一个stage3阶段的API:

// 不引入core-js尝试
const arr = [
  { group: 1, value: "jz" },
  { group: 2, value: "jz2" },
  { group: 1, value: "plp" },
];
const arrNew = arr.groupBy(item => item.group);
console.log(arrNew)
/* 输出结果
const arrNew = arr.groupBy(item => item.group);
                   ^
TypeError: arr.groupBy is not a function
*/

// 引入core-js/actual
require("core-js/actual");
const arr = [
  { group: 1, value: "jz" },
  { group: 2, value: "jz2" },
  { group: 1, value: "plp" },
];
const arrNew = arr.groupBy(item => item.group);
console.log(arrNew)
/* 输出结果
[Object: null prototype] {
  '1': [ { group: 1, value: 'jz' }, { group: 1, value: 'plp' } ],
  '2': [ { group: 2, value: 'jz2' } ]
}
*/

// 引入core-js/stable
require("core-js/stable");
const arr = [
  { group: 1, value: "jz" },
  { group: 2, value: "jz2" },
  { group: 1, value: "plp" },
];
const arrNew = arr.groupBy(item => item.group);
console.log(arrNew)
/* 输出结果
const arrNew = arr.groupBy(item => item.group);
                   ^
TypeError: arr.groupBy is not a function
*/

可以看到,不引入core-js时不支持,引入了core-js/actual(包含stage3阶段的API)后支持并能输出正确的结果。core-js/stable中不支持又报错了。

core-js源码结构

前面描述了很多core-js的引入方式,这里我们看一下源码结构,看看core-js内部是如何组织的。

core-js源码目录

core-js
├─actual
│   ├─array
│   │  ├─at.js
│   │  ├─concat.js
│   │  └─...
│   ├─set
│   │  └─...
│   └─...
├─es
│   └─...
├─features
│   └─...
├─index.js
└─...

首先列出core-js源码目录的示意图,可以看到core-js内部有很多目录,对应前面的各种引入方式。这里我们列出每个目录的内容:

  • actual 包含稳定的ES和Web标准特性,以及stage3的特性
  • es 包含稳定的ES特性
  • features 没有说明,猜测和full类似
  • full 所以特性包括早期提议
  • internals 包内部使用的逻辑
  • modules 实际特性的代码实现
  • proposals 包含提议的特性
  • stable 包含稳定的ES和Web标准特性
  • stage 按照stage阶段列出提议特性
  • web 包含Web标准特性
  • configurator.js 是否强制引入逻辑,后面会描述
  • index.js 内容为导出full目录,因此导入core-js等于导入core-js/full

层层引用

在目录中actual, es, full, stable, es是我们已经介绍过的。另外还有web目录仅包含web标准的特性,features和full类似(index.js中直接导出full目录)。

proposals目录包含提议的特性,以特性名来命名文件名。而stage目录中包含0.js, 1.js, 2.js等等,是根据stage阶段来整理的,方便整理和引入对应阶段的特性。

这样整理目录虽然清晰,但这些目录中的特性都是重复的,不可能在每个目录中把特性都实现一遍。因此上面这些目录的文件中,存放的都是实现的引用,并不是特性代码实现本身。真正的实现在modules目录中。modules目录中是以特性名作为命名的文件,文件有固定的前缀名:es.表示ES标准;esnext.表示提议中的标准;web.表示web标准。

这里以我们上面提到过的两个特性为例,看看引用路径,首先是Promise.try:

  • 使用者引入 core-js/full/promise/try.js
  • 引入 actual/promise/try.js
  • 引入 actual/promise/try.js
  • 引入 stable/promise/try.js
  • 引入 es/promise/try.js
  • 最终引入 modules/es.promise.try.js

然后是groupBy方法:

  • 使用者引入 core-js/actual/array/group-by.js
  • 最终引入 modules/esnext.array.group-by.js

可以看到,core-js内部的特性是经过层层引入,最终引入具体的实现代码的。

core-js-pure与core-js-bundle

除了core-js之外,core-js-pure与core-js-bundle这两个包也提供了兼容性。core-js-pure内部的目录结构与core-js一致,只不过core-js-pure不将特性注入到全局。core-js-bundle比较特殊,它是将core-js代码经过打包后再提供,它的结构如下:

core-js-bundle
├─index.js
├─minified.js
├─minified.js.map
└─...

其中index.js是打包过后的特性集合代码,minified.js是经过压缩混淆后的代码。core-js-bundle只能全部引入并注入到全局,不能引入部分目录或者导出某个属性。

打包和浏览器效果

创建Webpack示例

首先创建一个Webapck项目,方便后续打包查看效果。首先执行:

# 创建项目
npm init -y
# 安装webpack依赖
npm add webpack webpack-cli html-webpack-plugin
# 安装core-js依赖
npm add core-js core-js-pure core-js-bundle

创建src/index.js,内容如下:

const arr = ["jz", "plp"];
console.log(arr.lastIndex);

在package.json文件的scripts中增加命令:"build": "webpack"。最后是Webpack配置文件webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'production', // 生产模式
  entry: './src/index.js', // 源码入口
  plugins: [
    new HtmlWebpackPlugin({ // 生成HTML页面入口
      title: 'jzplp的core-js实验', // 页面标题
    }),
  ],
  output: {
    filename: 'main.js', // 生成文件名
    path: path.resolve(__dirname, 'dist'),  // 生成文件目录
    clean: true, // 生成前删除dist目录内容
  },
};

命令行运行npm run build,即可使用Webpack打包。在dist目录中生成了两个文件,一个是main.js,里面是打包后的js代码;index.html可以让我们在浏览器查看效果。由于我们没有引入core-js,浏览器没有预置lastIndex这个提议中的特性,因此输出undefined。

core-js打包

这里引入core-js,然后打包查看效果。首先是全量引入:

require("core-js");
const arr = ["jz", "plp"];
console.log(arr.lastIndex);

此时浏览器输出1,表示core-js注入成功,lastIndex特性生效了。但是我们查看main.js,发现居然有267KB。这是因为它把所有特性都引入了。

如果引入require("core-js/full/array"),此时新特性也可以生效。因为只引入了数组相关特性,因此main.js的大小为59.3KB,比全量引入小很多。

如果引入require("core-js/full/array/last-index"),此时新特性也可以生效。因为只引入了这一个特性,因此main.js的大小为12.2KB。

Babel与core-js

从前面打包的例子中可以看到,core-js整个打包进项目中是非常巨大的,可能比你正常项目的大小还要更大。这样明显会造成占用资源更多,页面加载时间变慢等问题。一个解决办法是,只引入我们代码中使用到的特性,以及我们要适配的浏览器版本中不兼容的特性,用不到的特性不打包进代码中。Babel就提供了这样的功能。

创建Babel示例

# 创建项目
npm init -y
# 安装webpack依赖
npm add @babel/core @babel/cli @babel/preset-env
# 安装core-js依赖
npm add core-js core-js-pure core-js-bundle

创建src/index.js,内容如下:

require('core-js');
const jzplp = 1;

在package.json文件的scripts中增加命令:"babel": "babel src --out-dir lib"。最后是Babel配置文件babel.config.json:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "chrome": "100"
        }
      }
    ]
  ]
}

targets中表示我们需要兼容的浏览器版本。执行npm run babel,生成结果再lib/index.js中,内容如下。可以看到未对core-js做任何处理。

"use strict";

require('core-js');
const jzplp = 1;

preset-env配置entry

@babel/preset-env是一个Babel预设,可以根据配置为代码增加兼容性处理。前面创建Babel示例时已经增加了这个预设,但是没有增加core-js配置。这里我们加一下:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "chrome": "100"
        },
        "useBuiltIns": "entry",
        "corejs": "3.47.0"
      }
    ]
  ]
}

这里增加了corejs版本和useBuiltIns配置,值为entry。配置这个值,会使得@babel/preset-env根据配置的浏览器版本兼容性,选择引入哪些core-js中的特性。这里再执行命令行,结果如下:

"use strict";

require("core-js/modules/es.symbol.async-dispose.js");
require("core-js/modules/es.symbol.dispose.js");
// ... 更多es特性省略
require("core-js/modules/esnext.array.filter-out.js");
require("core-js/modules/esnext.array.filter-reject.js");
// ... 更多esnext特性省略
require("core-js/modules/web.dom-exception.stack.js");
require("core-js/modules/web.immediate.js");
// ... 更多web特性省略
const jzplp = 1;

可以看到core-js被拆开,直接引入了特性本身。在配置chrome: 100版本时,引入的特性为215个。我们修改配置chrome: 140版本时,再重新生成代码,此时引入的特性为150个。可以看到确实时根据浏览器版本选择不同的特性引入。这对于其它core-js的引入方式也生效:

// 源代码
require('core-js/stable');
const jzplp = 1;

// 生成代码
"use strict";

require("core-js/modules/es.symbol.async-dispose.js");
require("core-js/modules/es.symbol.dispose.js");
// ... 更多es特性省略
require("core-js/modules/web.dom-exception.stack.js");
require("core-js/modules/web.immediate.js");
// ... 更多web特性省略
const jzplp = 1;

我们引入core-js/stable,可以看到生成代码中不引入esnext特性了。在配置chrome: 100版本时,引入的特性为71个,配置chrome: 100版本时,引入的特性为6个。同样的,如果引入换成core-js/full/array,就会只引入数组相关特性,而且也是根据浏览器兼容版本引入。

preset-env配置usage

@babel/preset-env的useBuiltIns配置值为usage时,Babel不仅会跟根据配置的浏览器版本兼容性,还会根据代码中实际使用的特性来选择引入哪些core-js中的特性。首先是Babel配置:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "chrome": "100"
        },
        "useBuiltIns": "usage",
        "corejs": "3.47.0"
      }
    ]
  ]
}

然后是要处理的代码,注意配置usage时是不需要手动引入core-js的。我们配置不同的Chrome浏览器版本,看看输出结果如何:

// 源代码
const jzplp = new Promise();
const b = new Map();

// chrome 50 生成代码 
"use strict";

require("core-js/modules/es.array.iterator.js");
require("core-js/modules/es.map.js");
require("core-js/modules/es.promise.js");
require("core-js/modules/web.dom-collections.iterator.js");
const jzplp = new Promise();
const b = new Map();

// chrome 100 生成代码 
"use strict";

const jzplp = new Promise();
const b = new Map();

首先可以看到,引入core-js中的特性数量变得非常少了,代码中没有用到的特性不再引入。其次不同的浏览器版本引入的特性不一样,因此还是会根据浏览器兼容性引入特性。我们再修改一下源代码试试:

// 源代码
const jzplp = new Promise();
const b = new Map();
Promise.try(() =>{});

// chrome 50 生成代码 
"use strict";

require("core-js/modules/es.array.iterator.js");
require("core-js/modules/es.map.js");
require("core-js/modules/es.promise.js");
require("core-js/modules/es.promise.try.js");
require("core-js/modules/web.dom-collections.iterator.js");
const jzplp = new Promise();
const b = new Map();
Promise.try(() => {});

// chrome 100 生成代码 
"use strict";

require("core-js/modules/es.promise.try.js");
const jzplp = new Promise();
const b = new Map();
Promise.try(() => {});

可以看到,源代码中增加了Promise.try,引入的特性也随之增加了对应的core-js特性引入。因此,使用@babel/preset-env的usage配置,可以保证兼容性的同时,最小化引入core-js特性。另外这个配置并不会自动引入提议特性,如果需要则额外配置proposals为true。

@babel/polyfill

@babel/polyfill是一个已经被弃用的包,推荐直接使用core-js/stable。查看@babel/polyfill源码,发现他就是引入了core-js特性与regenerator-runtime这个包。regenerator-runtime也是一个兼容性相关的包,可以帮助添加generatore和async/await相关语法。作为替代可以这样引入:

import 'core-js/stable';
import 'regenerator-runtime/runtime';

@babel/runtime

@babel/runtime就像自动引入版的core-js-pure。它还是根据代码实际使用的特性来注入core-js特性,但它不注入到全局,而是引入这些API再调用。这里我们使用@babel/plugin-transform-runtime插件,里面包含了@babel/runtime相关逻辑。首先看下Babel配置:

{
  "plugins": [["@babel/plugin-transform-runtime", { "corejs": 3 }]]
}

再转义上一节中的代码,结果如下:

// 源代码
const jzplp = new Promise();
const b = new Map();
Promise.try(() =>{});

// 生成代码
import _Promise from "@babel/runtime-corejs3/core-js-stable/promise";
import _Map from "@babel/runtime-corejs3/core-js-stable/map";
const jzplp = new _Promise();
const b = new _Map();
_Promise.try(() => {});

可以看到,虽然没有直接引入core-js-pure,但效果是一样的。打开@babel/runtime-corejs3这个包查看,里面实际上就是导出了core-js-pure中的特性。例如:

// @babel/runtime-corejs3/core-js-stable/map.js 文件内容
module.exports = require("core-js-pure/stable/map");

core-js/configurator强制控制

如果希望在正常引入core-js时,对于部分特殊属性进行引入或者不引入的控制,就需要用到core-js/configurator。这个工具可以配置三种选项:

  • useNative: 当环境中有这个特性时不引入,当确定没有时才引入
  • usePolyfill: 明确引入这个特性
  • useFeatureDetection: 默认行为,和不使用core-js/configurator一致

useNative不引入

首先试试不引入特性,这里我们使用Promise这个特性为例。首先是不引入core-js的效果,可以看到全局Promise对象被我们改掉了。

const jzplp = {};
Promise = jzplp;
console.log(Promise, Promise === jzplp);

/* 输出结果
{} true
*/

然后在中间引入core-js试试。可以看到我们改掉的Promise,被core-js给改回去了。

const jzplp = {};
Promise = jzplp;
require("core-js/actual");
console.log(Promise, Promise === jzplp);

/* 输出结果
[Function: Promise] false
*/

这时候,如果不希望core-js改掉我们自定义的Promise,可以利用useNative配置,强制core-js不引入这个特性。看结果core-js引入之后,我们自定义的Promise依然存在。

const configurator = require("core-js/configurator");
configurator({
  useNative: ["Promise"],
});
const jzplp = {};
Promise = jzplp;
require("core-js/actual");
console.log(Promise, Promise === jzplp);

/* 输出结果
{} true
*/

usePolyfill强制引入

想要验证usePolyfill的效果,需要找一个环境中本来存在的特性,core-js即使引入也不会修改的特性。Promise不行,因为core-js引入时会对这个Promise增加子特性。Promise.try也不行,因为原来环境中不存在。这里试一下Promise.any,这是环境中本来就存在的特性:

console.log(Promise.any);
const jzplp = () => {};
Promise.any = jzplp;
console.log(Promise.any, Promise.any === jzplp);

/* 输出结果
[Function: any]
[Function: jzplp] true
*/

可以看到,Promise.any原来就存在,但是被我们修改成了新函数。再引入core-js试试:

console.log(Promise.any);
const jzplp = () => {};
Promise.any = jzplp;
require('core-js');
console.log(Promise.any, Promise.any === jzplp);

/* 输出结果
[Function: any]
[Function: jzplp] true
*/

引入了core-js之后,结果没有变化。这说明core-js并不会修改我们自定义的函数。这时候就可以试一下usePolyfill的效果了:

const configurator = require("core-js/configurator");
configurator({
  usePolyfill: ["Promise.any"],
});
console.log(Promise.any);
const jzplp = () => {};
Promise.any = jzplp;
require('core-js');
console.log(Promise.any, Promise.any === jzplp);

/* 输出结果
[Function: any]
[Function: any] false
*/

可以看到,Promise.any又被改为了真正起效果的函数,这说明usePolyfill的强制引入特性是有效的。

core-js中的特性选择

前面我们体验了Babel根据浏览器兼容性,选择不同的core-js特性引入,那么不同浏览器兼容哪些特性的数据是从哪里获取呢?core-js本身就提供了这个功能。

core-js-compat

core-js-compat提供了不同浏览器对应特性的兼容性数据。它有好几个参数,这里先列举一下含义:

  • targets: Browserslist格式的浏览器兼容配置
  • modules: 需要设置兼容性配置的模块,可以是core-js/full,也可以是某个特性,甚至是正则
  • exclude: 需要排除的模块
  • version: 使用的core-js版本
  • inverse: 反向输出,即输出不需要兼容的特性列表

这里举几个例子试一下:

const compat = require("core-js-compat");
const data = compat({
  targets: "> 10%",
  modules: ["core-js/actual"],
  version: "3.47",
});
console.log(data);

/* 输出结果
{
  list: [
    'es.iterator.concat',
    'es.math.sum-precise',
    'es.async-iterator.async-dispose',
    'esnext.array.group',
    'esnext.array.group-by',
    ...其它特性
  ],
  targets: {
    'es.iterator.concat': { 'chrome-android': '143' },
    'es.math.sum-precise': { 'chrome-android': '143' },
    'es.async-iterator.async-dispose': { 'chrome-android': '143' },
    'esnext.array.group': { 'chrome-android': '143' },
    'esnext.array.group-by': { 'chrome-android': '143' },
    ...其它特性
  }
}
*/

compat会根据我们设置的浏览器兼容性配置,输出特性列表,包含两个字段:list是一个特性名称列表;targets是一个Map结构,key为特性名,值为可以兼容的浏览器。假设我们把上面的 targets改成 > 50%,此时会输出空值:

const compat = require("core-js-compat");
const data = compat({
  targets: "> 50%",
  modules: ["core-js/actual"],
  version: "3.47",
});
console.log(data);

/* 输出结果
{ list: [], targets: {} }
*/

我们增加exclude,排除部分属性,可以看到特性数量大大减少:

const compat = require("core-js-compat");
const data = compat({
  targets: "> 10%",
  modules: ["core-js/actual"],
  exclude: ["esnext"],
  version: "3.47",
});
console.log(data);

/* 输出结果
{
  list: [
    'es.iterator.concat',
    'es.math.sum-precise',
    'es.async-iterator.async-dispose',
    'web.dom-exception.stack',
    'web.immediate',
    'web.structured-clone'
  ],
  targets: {
    'es.iterator.concat': { 'chrome-android': '143' },
    'es.math.sum-precise': { 'chrome-android': '143' },
    'es.async-iterator.async-dispose': { 'chrome-android': '143' },
    'web.dom-exception.stack': { 'chrome-android': '143' },
    'web.immediate': { 'chrome-android': '143' },
    'web.structured-clone': { 'chrome-android': '143' }
  }
}
*/

再试一下inverse的效果:

const compat = require("core-js-compat");
const data = compat({
  targets: "> 10%",
  modules: ["core-js/actual"],
  version: "3.47",
  inverse: true
});
console.log(data);

/* 输出结果
{
  list: [
    'es.symbol',
    'es.symbol.description',
    ...其它特性
  ],
  targets: {
    'es.symbol': {},
    'es.symbol.description': {},
    ...其它特性
  }
}
*/

因为输出的是不需要引入core-js兼容的特性,所以特性数量非常多,而且targets中没有列出支持的浏览器版本。

core-js-builder

前面介绍的core-js-compat是接收参数之后,输出core-js的特性列表数组。而core-js-builder接收类似的参数,直接输出引用core-js的代码。我们首先列举一下参数:

  • targets: Browserslist格式的浏览器兼容配置
  • modules: 需要设置兼容性配置的模块,可以是core-js/full,也可以是某个特性,甚至是正则
  • exclude: 需要排除的模块
  • format: 'bundle'输出打包后的源码;'cjs'和'esm'输出对应格式的引用代码
  • filename: 输出的文件名

我们先试一下例子。首先是format格式的:

const builder = require("core-js-builder");
async function funJzplp() {
  const data = await builder({
    targets: "> 30%",
    modules: ["core-js/actual"],
    format: 'bundle',
  });
  console.log(data);
}
funJzplp();

/* 输出结果
...代码很长,这里节选部分
 (function(module, exports, __webpack_require__) {
"use strict";
var NATIVE_BIND = __webpack_require__(8);
var FunctionPrototype = Function.prototype;
*/

可以看到,builder函数输出了非常长的代码,内容实际为输出的特性经过打包之后的结果代码。再试一下'cjs'和'esm',输出的是对应木块的引用代码:

// format: 'cjs' 输出结果
...代码很长,这里节选部分
require('core-js/modules/es.iterator.concat');
require('core-js/modules/es.math.sum-precise');
require('core-js/modules/es.async-iterator.async-dispose');
*/

// format: 'esm' 输出结果
...代码很长,这里节选部分
import 'core-js/modules/es.iterator.concat.js';
import 'core-js/modules/es.math.sum-precise.js';
import 'core-js/modules/es.async-iterator.async-dispose.js';
*/

如果设置了filename,core-js-builder会创建该名称的文件,并将代码写入到文件中。

总结

这篇文章描述了core-js相关包的代码内容和使用方式。core-js实际上就是提供了JavaScript中一些API特性的兼容实现方式。它与实现语法兼容的Babel一起,可以做到大部分JavaScript的兼容性。当然core-js和Babel也不是万能的,它们都有各自无法转义和兼容的语法和特性。

core-js这个包名字起的非常好,一听就是JavaScript的“核心”包。由于它实现了很多API特性,而且引用数量非常非常大,因此叫“核心”也不为过。虽然这个包引用量很大,但不如React/Vue或者一些其它包出名。因为这个包是处理的更底层的兼容性有问题,因此用户感知不强。core-js包的作者还因为没有钱而遇到很多问题,这个包并没有让他变的富有。

参考

昨天 — 2026年1月20日首页

解绑宁王,天赐底气何在?

2026年1月20日 21:44

作者|Eastland

头图|视觉中国


在锂电产业链的博弈棋局中,没有永恒的绑定,只有动态的平衡。

 

一年半前,天赐材料(SZ:002709)通过全资子公司(宁德凯欣)与宁德时代签订《物料供货协议》。协议约定:在本协议有效期内(自生效至2025年12月31日止),凯欣向宁德时代供应固体六氟磷酸锂使用量为5.86万吨的对应数量电解液产品。


粗略推算,协议涉及的电解液可供生产410GWh~470GWh锂电池产品,约为宁德时代一年半产量的60%(假设2024年H2+2025年,宁德时代出货量750GWh)。


一个事实是,六氟磷酸锂电解液占动力电池成本的40%~50%,天赐材料无疑是宁德时代的核心供应商之一。


按理说,宁德时代、天赐材料早该签订长期供货协议,宁德锁定关键物料、天赐获得业绩保底。但双方2024年6月签的协议即将到期,新协议却迟迟没有动静,原因是天赐的选择变多了。

 

解绑“宁王”,底气何在?


1)与宁德时代紧密合作

 

2015年,天赐材料在宁德市建立全资子公司宁德凯欣,为宁德时代提供“贴身服务”:


2018年,来自宁德时代的收入突破10亿元、占天赐锂电池材料销售收入的35.7%;

……

……

2021年,来自宁德时代的收入达56亿元、占天赐锂电池材料销售收入的57.5%;

2022年,来自宁德时代的收入达122亿元、占天赐锂电池材料销售收入的58.4%;

2023年,来自宁德时代的收入回落至81亿元、占天赐锂电池材料销售收入的57.6%;

2024年,来自宁德时代的收入进一步降至50亿元、占天赐锂电池材料销售收入的45.6%;


  

两家的合作关系可概括为:天赐锂电池材料营收五成来自宁德时代,宁德时代电解液六成来自天赐。

 

2)降低对彼此的依赖

 

时至2025年末,天赐材料与宁德时代的“供货协议”即将到期,却迟迟不见续签消息。

 

另一方面,天赐材料陆续披露重大供货合同。


  • 第一轮:


2025年7月15日,与楚能新能源签订“合作协议”——2030年前供应不少于55万吨电解液产品;


签约时点耐人寻味——与宁德时代签订供货协议一周年之际。


  • 第二轮:


2025年9月22日,与瑞蒲兰钧签订“合作协议”——2030年前供给不小于80万吨电解液产品;


这是对宁德时代进一步施加压力——我的货不愁卖。


  • 第三轮:


2025年11月6日,与中航新能签订“保供框架协议”——2026~2028年供给72.5万吨电解液产品;

同日,与国轩高科签订“采购合同”——2026~2028年供给87万吨电解液产品;


这轮签订的客户,“卡位”大于前两轮,而且放在同一天。显示出天赐的决绝。


三轮签约(四份合同)涉及的总供货量达294.5万吨,2026年~2028年平均供货超过80万吨/年,而2024年,天赐材料电解液出货量仅50万吨。


预计2026年,天赐材料电解液产能达到100万吨(2027年120万吨)。


通常供应商不会把100%产能都拿去签长单。主要有两个原因:一是现货价有可能高于长协价,谁不想多赚点;二是为开发新客户、维护老客户,留些机动产能。


看到宁德时代“油盐不进”,天赐材料把80%产能“预售”出去,剩下20%不太可能与宁德签长单了(不排除现货交易)。


其实双方都在赌,赌的是“不依赖你,我过得更好”。


目前看来,天赐的“钱途”更明朗——未来三年,产量是2022年(33万吨)的三倍,销路不愁(80%产能已落实下家)、价格肯定比供宁德时代高。


宁德时代是行业巨头,老板是成熟的一流企业家,“不惯着”天赐材料,肯定另有打算。

 

最新消息:据韩国《ZDNET Korea》报道,12月24日Enchem与宁德时代签署合同。前者将在2026~2030年供给电解液35万吨,总价款约合72.3亿人民币。


年均7万吨的供应量超过Enchem产能(约5万吨/年),却与宁德时代需求(2026年约60~80万吨)相差甚远。


在推动供应电解液来源多元化的同时,宁德时代有可能自建产能降低对外部的依赖。


利润大起大落,基本面如何?

 

天赐“锂离子电池材料”主要产品包括:电解液、正极材料(磷酸铁锂)。除此之外,天赐材料还涉足资源/循环业务(锂辉石资源、磷酸铁锂电池回收等)。

 

天赐仅披露锂电池材料总出货量,没有完整明细。例如:


2023年出货61.9万吨,其中电解液约40万吨、磷酸铁锂约5万吨,其他不详;

2024年出货79.5万吨,其中电解液约50万吨、磷酸铁锂9.5万吨,其他不详;

 

锂电材料收入除以总出货量,得到综合平均价格,这个数字只能说明电池材料价格走势,无法算出具体产品(如电解液)单价:


2020年,每吨售价3万元;

2021年,每吨售价4.3万元;

2022年,每吨售价4.8万元;

2023年,每吨售价2.3万元;

2024年,每吨售价1.38万元;

 


天赐电池材料出货量稳步上升,但毛利润却冲高回落:

 

2022年,综合单价4.8万元、毛利润80亿、利润率38.6%,三个数据同时创历史新高;

 

2024年,出货量较2022年高84.5%,但出货价格仅为2022年的28.6%;毛利润率降至17.5%,毛利润金额19亿,为2022年的23.9%;



整体来看,天赐盈利能力经受住了考验:


  • 高峰


2022年,天赐毛利润达88亿、毛利润率39.6%;总费用仅15.5亿、总费用率不到7%;


  • 低谷

 

2024年,天赐毛利润仅24亿、毛利润率18.9%;总费用被压缩到13.9亿、总费用率增至11.1%;



  • 扣非净利润 


2022年,天赐扣非净利润达55亿,2024年仅为3.8亿,利润率3%。


2025年H1,天赐扣非净利润提高到4亿,利润率3.7%;



对多数制造业企业而言,20%的毛利润率不算低。2025年前三季度,特斯拉整车销售毛利润率仅为13.6%。


至于天赐的费用率,过往几年的数据证明刚性很强,几乎没有压缩余地。


回过头看,2020~2024年是一个比较完整的周期。锂矿(锂辉石)、碳酸锂、六氟磷酸锂(电解液)、磷酸铁锂(正极材料)……都经历了一轮冲高回落。


考察周期股,最重要的是看它在低谷的表现。好比基金经理,在股灾时能不陪或少赔,比牛市里赚得多更重要。正所谓“善战者不败,善败者终胜”。


在这方面,天赐材料比其他“周期股”优秀,如天齐锂业、TCL中环。


2025年是新周期的开始,动力电池、储能领域的需求叠加,产业链各环节吃紧。


如果把锂电产业链比喻成一条河,天赐材料“把守”的正是河道最窄的地方。


天赐成了“讨厌的”周期股?


有个理念至今仍有市场——周期性行业没有好生意,以茅台为代表的消费股价值更高。

 

天赐材料是该理念的拥趸,将主攻方向定为“个人护理材料”。2010年相关收入占营收的61.6%;锂电池材料仅占营收的21.9%。

 

但新能源毕竟是朝阳产业,尽管供需动态平衡过程充满间歇式的产能不足和产能过剩,行业规模却保持高速增长。锂电材料收入在天赐总营收中的占比“无可救药”地增长:


2016年,电池材料收入占比突进到60%以上;

……

……

……

2021年,电池材料收入占比达到90%一线:

2021年,收入97亿、占营收的88%;

2022年,收入208亿、占营收的93%;

2023年,收入141亿、占营收的92%;

2024年,收入110亿、占营收的88%;

2025年H1,收入63亿、占营收的90%;


 

从发展历程看,成为“周期股”并非天赐的初衷,但“抗争”了十几年,终究还是成了“自己讨厌的样子”。


回顾天赐材料的发展轨迹,其在行业调整期主动拓宽客户矩阵,降低对单一客户的依赖,体现出对行业趋势的清醒认知——在商业战场上,没有永远的朋友。

 

在逐步搭建起“锂辉石—碳酸锂—六氟磷酸锂—电解液”全产业链框架后,天赐材料的韧性将成为穿越周期的关键。


*以上分析仅供参考,不构成任何投资建议!

下载虎嗅APP,第一时间获取深度独到的商业科技资讯,连接更多创新人群与线下活动

晶核能源融资数千万元,专攻固态电池“固固界面”难题丨36氪首发

2026年1月20日 21:07

36氪获悉,诞生于“星空计划”的全固态电池企业晶核能源,近日完成数千万元天使轮融资,本轮融资由天空工场创投基金领投。

晶核能源是一家全固态电池电芯与系统解决方案供应商,其创始人兼CEO李延涛曾是中航锂电及吉利动力电池创始骨干成员。李延涛告诉36氪,这轮融资所获得资金,将用于全固态电池技术商业化落地、核心团队人才引进,以及全球商业化版图快速拓展。

在新能源汽车领域,全固态电池因其高能量密度与安全冗余,长期被视为行业的圣杯。但宥于工艺复杂、成本高企,且核心技术长期未取得突破,全固态电池至今未能实现大规模量产。

聚焦全固态电池行业痛点,晶核能源提出了自己的解决方案。全固态电池量产的最大掣肘,是固固界面阻抗问题。对此,李延涛表示,晶核能源研发了正负极包覆技术,“在制备正负极材料时,在外面做一层硫化物包覆,这样一来,相同材料间的致密性会更好,其电导率较目前的全固态电池能够提升一个量级”。晶核能源计划,“在2027年底,将全固态电池的充放电倍率提升至2C”。

固态电池

除此之外,晶核能源的全固态电池正极,所采用的材料是富锂锰基,而非高镍原材料,据李延涛介绍,富锂锰基作为正极材料,“能够将电芯能量密度提升至800Wh/kg”。

为了让旗下产品被更好地应用,晶核能源还同步开发了AI BMS电池管理系统、CTP4.0架构设计这一PACK技术。

36氪了解到,晶核能源研发AI BMS电池管理系统的目的,是为了实现更高精度的电芯状态监测,提升电池健康度识别能力。而CTP4.0架构的PACK设计,则能够“提升电池包空间利用率、从而降低生产成本”。

李延涛解释,在CTP4.0设计下,电池包内没有纵横梁,从而同样体积的电池包内,可以堆更多电芯。

同时,晶核能源设计了龙骨架构,“每个电芯之间也会保持两毫米的间隙,以应对充放电时的呼吸效应。电芯中间又加了六合一功能的材料,这种材料可呼吸、可压缩,从而能保障电池包的结构强度、起到隔热作用,并提升电池包内空间利用率”。

在技术攻坚之余,晶核能源也强调短期的商业化落地。目前,企业已和3家头部新能源商用车客户、2家能源厂商达成合作意向,首批工程样件预计于2026年第四季度完成装车测试。产品的应用场景,覆盖智能汽车、电力储能与eVTOL三大领域。

晶核能源创始人

按照李延涛的规划,晶核能源要在2027年,实现全固态电池的规模化产能突破。

本次产业投资方董事总经理俞佳彬认为:“双碳目标与能源转型驱动下,固态电池赛道已站在万亿级市场爆发前夜,技术壁垒与商业化能力是突围关键。”他强调,晶核能源团队深耕固态电池领域多年,核心技术实现颠覆性突破,且产业化潜力明确,天空工场创投基金将持续在资本、产业资源等方面赋能,助力其成长为全球全固态电池领军企业。

爱奇艺国际版2025年播放量同比增长114.5%

2026年1月20日 20:56
36氪获悉,1月20日,《爱奇艺国际版2025全球内容热播榜》正式发布。其中显示,2025年爱奇艺国际版内容播放量持续强劲增长,同比增长114.5%,华语内容成为驱动这一增长的核心动力。

海特生物:筹划发行H股股票并在香港联交所上市

2026年1月20日 20:50
36氪获悉,海特生物公告,公司拟发行境外上市外资股(H股)股票并申请在香港联合交易所有限公司主板挂牌上市,以进一步提高公司综合竞争力,提升公司国际品牌形象,同时更好地利用国际资本市场,多元化融资渠道。该事项尚需提交公司股东会审议,并需取得相关政府机构、监管机构备案、批准和/或核准。

热门中概股美股盘前普跌,拼多多跌超4%

2026年1月20日 20:48
36氪获悉,热门中概股美股盘前普跌,截至发稿,拼多多跌超4%,B站跌超3%,小鹏汽车跌超2%,阿里巴巴、蔚来、理想汽车跌超1%,京东跌0.76%,爱奇艺跌0.52%,网易跌0.22%,百度跌0.09%。

北大医药:股东北大医疗拟减持不超3%股份

2026年1月20日 20:41
36氪获悉,北大医药公告,持股5%以上股东北大医疗管理有限责任公司因自身资金需求,计划自公告披露之日起15个交易日后的3个月内(即2026年2月11日至2026年5月10日),通过集中竞价和大宗交易方式减持公司股份不超过178.80万股(占公司总股本比例为3%)。其中,集中竞价交易在任意连续90个自然日内不超过公司总股本的1%,大宗交易不超过2%。

锋龙股份:如未来公司股票价格进一步异常上涨,公司可能再次申请停牌核查

2026年1月20日 20:29
36氪获悉,锋龙股份公告,公司已就近期股票交易异常波动情况停牌核查,并公告核查结果复牌。目前,公司股票价格已严重脱离公司基本面情况,存在市场情绪过热、非理性炒作风险。公司主营业务未发生变化,未来36个月内,深圳市优必选科技股份有限公司不存在通过上市公司重组上市的计划或安排,未来12个月内不存在资产重组计划。此外,公司提示投资者关注市场竞争环境变化、国际贸易风险以及园林机械电动转型替代等风险。

必和必拓上调2026财年铜产量指引

2026年1月20日 20:19
1月20日,必和必拓发布截至2025年12月31日的2026上半财年运营公报。基于主要矿山运营的优异表现,集团上调了2026财年铜矿产量的指导目标。埃斯康迪达(Escondida)铜矿选矿厂吞吐量创下历史新高,带动全年产量指导目标上调。安塔米纳(Antamina)同样提高了产量指导目标,而斯宾塞(Spence)和南澳铜矿(Copper SA)的运营按计划推进,其中南澳铜矿的精炼黄金产量更突破历史纪录。(界面)

奕帆传动:拟购买北京和利时87.07%股权,预计构成重大资产重组

2026年1月20日 20:12
36氪获悉,奕帆传动公告,公司拟以支付现金方式购买北京和利时电机技术有限公司87.07%的股权,本次交易预计构成重大资产重组。交易完成后,标的公司将成为公司的控股子公司,并纳入公司合并财务报表范围。本次交易有利于公司综合实力的提升,对公司未来业务发展及经营业绩提升将产生积极影响,提升公司整体资产质量和核心竞争力。

爱奇艺首席财务官汪骏因个人原因辞任

2026年1月20日 19:58
36氪获悉,爱奇艺公告,首席财务官汪骏因个人原因辞任,即日生效。同时,公司任命财务高级副总裁曾颖担任公司临时首席财务官。汪骏将继续担任爱奇艺顾问至2026年5月31日,以确保平稳过渡。公司即将启动新任首席财务官的遴选工作。

凯捷计划在法国裁员至多2400人

2026年1月20日 19:29
法国凯捷集团1月20日发布声明称,计划在法国裁员至多2400人,约占其法国员工总数的6%。该公司表示:“凯捷正在法国实行转型,以应对技术加速变革(包括人工智能)以及客户需求变化带来的挑战和机遇。目前法国经济增长缓慢,某些行业面临重大挑战。”凯捷称,将为员工提供自愿内部再培训或离职方案。(财联社)
❌
❌