手把手教你在webpack中,配置两种常用的polyfills配置,以ie11为例,配置babel-loader
使用@babel/preset-env时useBuiltIns取值:false、usage、entry之间的差别;和使用@babel/plugin-transform-runtime实现built-in的区别
首先,配置babel-loader
babel-loader使用babel来转译js,例如把es6转为es5,即对代码进行polyfills(垫片),让旧浏览器也能被“垫平”到与新浏览器一样好的兼容性支持能力。
-
安装相关依赖
npm i babel-loader @babel/core @babel/preset-env -Dbabel-loader: 让babel能够在webpack里工作;
@babel/core:babel核心模块;
@babel/preset-env:转换 ES2015+的babel预设(一组babel插件集合)。 -
在module.rules中,给js资源配置babel-loader,并添加预设。
module.exports = { // ... module: { rules: [ { test: /\.js$/, exclude: /node_modules/, // 排除依赖里的代码 use: { loader: 'babel-loader', // loader参数分为:1.babel库的参数 2.babel-loader的参数 // 可把babel库的参数抽离到配置文件babel.config.js中 options: { // babel库的参数:预设,一组插件集合 // @babel/preset-react: 编译react jsx语法预设 // @babel/preset-typescript: 编译ts预设 presets: ['@babel/preset-env'] // 常用的babel-loader的参数 // 开启babel编译缓存 // 默认缓存目录node_modules/.cache/babel-loader cacheDirectory: true, // 缓存文件不要压缩 cacheCompression: false, } } } ] }, } -
配置目标浏览器。
// package.json { // 其它package配置省略 "browserslist": [ "ie 11" // 目标浏览器为ie 11 ] }
配置完这里,已经可以转换像箭头函数之类的语法了,但有一些语法的转换需要进一步优化:
async转译之regeneratorRuntim函数
安装相对旧版本的babel搭配
@babel/preset-env使用时,如果转译了代码中的async、await语法,会在浏览器报regeneratorRuntime is not definded的错误,这时需要配@babel/plugin-transform-runtime插件
regeneratorRuntime是babel生成的一个用来兼容async/await语法的函数。
babel@7+转换语法时,会把regeneratorRuntime函数和辅助函数,以内联方式重复注入到每处需要用到的代码中,不利于代码复用。
@babel/plugin-transform-runtime插件默认可将@babel/runtime模块引入项目,@babel/runtime包含babel辅助函数、regeneratorRuntime函数,经过babel转换的代码需要用到这些函数时,可共用它们:
-
在上一步的基础上,安装项目依赖
npm i @babel/runtime -
安装开发依赖
npm i @babel/plugin-transform-runtime -D -
配置module.rules
module.exports = { // ... module: { rules: [ { test: /\.js$/, exclude: /node_modules/, // 排除依赖里的代码 use: { loader: 'babel-loader', // loader参数 options: { presets: ['@babel/preset-env'], // 记得安装项目依赖@babel/runtime plugins: ['@babel/plugin-transform-runtime'] } } } ] }, }
这里,项目中的新语法基本已经可以完美转译了,但是较新的api依旧可能有问题,如Promise、WeakMap等,在ie11中仍无法使用。
@babel/polyfill与core-js
js兼容性转换分为两种:
- 语法类的转译,如const、箭头函数、可选链操作符,babel称为syntax类型(通过babel插件转换)
- api类的覆写,如Promise、WeakMap、startsWith、includes等原生api,称为built-in类型(需要在业务代码里插入兼容代码,如Promise的es5实现)
上一步只实现了syntax的处理,built-in的还没处理:
@babel/polyfill或core-js依赖功能上是一样的:用来解决built-in类型的覆写(垫平),它包含了es6+新增的原生api的实现。
通俗来讲就是,用es5实现了es6+的一些新的api,代码里引入后,它们可以修改Object等原生对象的prototype,以实现api的扩展。
因为@babel/polyfill或core-js都是要被打包到最终代码里的,所以应该安装为项目依赖,而非开发依赖
// 入口文件
// 必须在有兼容性代码执行前执行polyfill代码,所以一般在入口文件引入
import '@babel/polyfill';
// import 'core-js'; // 或者使用core-js
console.log('dfdfdf'.replaceAll('d','e'));
使用core-js替代@babel/polyfill
引入@babel/polyfill后,代码体积会变大,因为里面包含了全部兼容性代码。它被babel@7淘汰,而改用core-js,core-js把不同的兼容语法进行了精细划分,可按需引入部分兼容性代码。
core-js既可以全量兼容,也可以只兼容指定的语法,还可以做到无副作用兼容(不修改原生对象的prototype)
// 官方示例
import 'core-js/actual'; // 全部兼容
/* 只导入用到了的新语法
import 'core-js/actual/promise';
import 'core-js/actual/set';
import 'core-js/actual/iterator';
import 'core-js/actual/array/from';
import 'core-js/actual/array/flat-map';
import 'core-js/actual/structured-clone';
*/
// 被兼容的新语法↓
Promise.resolve(42).then(it => console.log(it)); // => 42
Array.from(new Set([1, 2, 3]).union(new Set([3, 4, 5]))); // => [1, 2, 3, 4, 5]
[1, 2].flatMap(it => [it, it]); // => [1, 1, 2, 2]
(function * (i) { while (true) yield i++; })(1)
.drop(1).take(5)
.filter(it => it % 2)
.map(it => it ** 2)
.toArray(); // => [9, 25]
structuredClone(new Set([1, 2, 3])); // => new Set([1, 2, 3])
不修改全局的对象:
import Promise from 'core-js-pure/actual/promise';
import Set from 'core-js-pure/actual/set';
import Iterator from 'core-js-pure/actual/iterator';
import from from 'core-js-pure/actual/array/from';
import flatMap from 'core-js-pure/actual/array/flat-map';
import structuredClone from 'core-js-pure/actual/structured-clone';
Promise.resolve(42).then(it => console.log(it)); // => 42
from(new Set([1, 2, 3]).union(new Set([3, 4, 5]))); // => [1, 2, 3, 4, 5]
flatMap([1, 2], it => [it, it]); // => [1, 1, 2, 2]
Iterator.from(function * (i) { while (true) yield i++; }(1))
.drop(1).take(5)
.filter(it => it % 2)
.map(it => it ** 2)
.toArray(); // => [9, 25]
structuredClone(new Set([1, 2, 3])); // => new Set([1, 2, 3])
到这里,就已经可以在项目里处理所有的兼容性代码了,但是我们可以进一步配置,让built-in类型(新api)无需像上面这样手动在代码里引入,而是像syntax类型那样,被babel-loader自动识别,并处理。
如果使用上面这种方式引入兼容性代码的弊端还有:
- 打包体积过大,可能把目标浏览器不需要的兼容性api也导入进去了。
- 需要手动引入,如果目标浏览器修改了,还要可能要修改代码,可维护性差。
- syntax和built-in两种兼容内容管理混乱,前者用预设自动处理,后者手动处理。
下面的介绍就是,为了解决这些问题:
webpack项目中配置polyfills(完整)
core-js可灵活引入只需要兼容的那部分兼容性代码,例如可以细化到是否全量引入Promise上的兼容性api。如果需要人工引入会很麻烦。
@babel/preset-env预设、@babel/plugin-transform-runtime插件都附加自动引入core-js代码的功能。(开启哪个看项目而定)。
区别:
@babel/preset-env会修改全局对象上的prototype,适合业务系统。@babel/plugin-transform-runtime则不会(无副作用),适合开发第三方库。
首先,安装依赖,并配置browserslist来指定兼容的浏览器。
@babel/plugin-transform-runtime需要依赖@babel/runtime-corejs3插件,@babel/runtime-corejs3包含了上面@babel/runtime插件的功能,还可以用来引入core-js
npm i babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime -D
npm i core-js@3 @babel/runtime-corejs3 @babel/runtime
// package.json
{
// 其它package配置省略
"browserslist": [
"ie 11" // 目标浏览器为ie 11
]
}
-
方式一:使用
@babel/preset-env预设,处理新语法+引入core-js:配置预设参数useBuiltIns(是否兼容built-in类型),决定如何引入core-js模块。然后,配置corejs参数为对应core-js的版本号;同时使用@babel/plugin-transform-runtime插件处理辅助函数// webpack.config.js module.exports = { // ... module: { rules: [ { test: /.js$/i, exclude: /node_module/, use: { loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { useBuiltIns: 'usage', // targets: ['> 1%, last 1 version'], corejs: '3.30.2' } ] ], // 这里只要@babel/runtime,不需要@babel/runtime-corejs3 plugins: ['@babel/plugin-transform-runtime'] } } } ] } } // useBuiltIns取值:false、usage、entry // false: 不处理core-js(默认值) // usage、entry: 针对目标浏览器情况,引入需要垫平的部分代码,也就是说, // 如果只要兼容promise,目标浏览器为chorme或ie引入的core-js是不同的。 // 它们的区别: // usage: 自动识别项目中用到的新api,自动按需引入core-js // entry: 需要手动在业务代码里引入core-js的指定兼容模块, // entry和纯手动自己引入区别在于,会根据目标浏览器情况,剔除不需要的部分 /* entry模式下,需要在入口文件添加导入core-js的语句: 如果没有添加,entry模式下就不会做任何built-in处理 兼容目标浏览器所有ES6+ API,包括提案阶段 import 'core-js'; 或者,只兼容针对目标浏览器,promise部分的API import 'core-js/es/promise'; */ -
方式二:使用
@babel/plugin-transform-runtime插件处理辅助函数+引入core-js,只需要配置corejs参数即可;同时使用@babel/preset-env预设,处理新语法。这种方式会自动识别项目中用到的新api,进行代码转换,类似方式一的usage方式。区别在于,引入core-js的方式是,上面说的:不修改全局的对象。
// webpack.config.js module.exports = { // ... module: { rules: [ { test: /.js$/i, exclude: /node_module/, use: { loader: 'babel-loader', options: { presets: [ '@babel/preset-env' // 只处理语法 ], plugins: [ ["@babel/plugin-transform-runtime", { // @babel/runtime-corejs3包含@babel/runtime的功能 // 自动识别项目中用到的新api,进行代码转换 corejs: { version: 3, // 需要安装 @babel/runtime-corejs3 proposals: true // 使用提案阶段语法 } }] ] } } } ] } }
需要注意的是,因为babel-loader配置exclude属性,排除掉了第三方依赖node_modules中的代码,这些第三方依赖可能存在不兼容ie11的代码,且不会被babel-loader转译。
另外,项目中,最好还是把options里的babel参数放到项目根目录的babel.config.js中:
// babel.config.js
module.exports = {
presets: [
'@babel/preset-env' // 只处理语法
],
plugins: [
["@babel/plugin-transform-runtime",
{
// @babel/runtime-corejs3包含@babel/runtime的功能
corejs: {
version: 3, // 需要安装 @babel/runtime-corejs3
proposals: true // 使用提案阶段语法
}
}]
]
}
更多推荐


所有评论(0)