本篇内容介绍了“用了babel还需不需要polyfill”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
啥是Babel
甩出中文官方文档的定义
Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。 下面列出的是 Babel 能为你做的事情:
语法转换
通过 Polyfill 方式在目标环境中添加缺失的特性 (通过 @babel/polyfill 模块)
源码转换 (codemods)
更多! (查看这些 视频 获得启发)
看完官方定义之后大家是不是觉得babel一个人就能把向后兼容的事情都做完了(不得不说确实有点误导),其实根本不是这样的。
真实的情况是babel只是提供了一个“平台”,让更多有能力的plugins入驻我的平台,是这些plugins提供了将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法的能力。
那么他是咋做到的呢?这就不得不提大名鼎鼎的AST了。
Babel 工作原理
babel的工作过程分为三个阶段:parsing(解析)、transforming(转化)、printing(生成)
parsing阶段babel内部的 babylon 负责将es6代码进行语法分析和词法分析后转换成抽象语法树
transforming阶段内部的 babel-traverse 负责对抽象语法树进行变换操作
printing阶段内部的 babel-generator 负责生成对应的代码
其中第二步的转化是重中之重,babel的插件机制也是在这一步发挥作用的,plugins在这里进行操作,转化成新的AST,再交给第三步的babel-generator。 所以像我们上面说的如果没有这些plugins进驻平台,那么babel这个“平台”是不具备任何能力的。就好像这样:
const babel = code => code;
因此我们可以有信心的说出那个答案“需要”。不仅需要polyfill,还需要大量的plugins。
下面我们通过例子来说明,以及还需要哪些plugins才能将 ECMAScript 2015+ 版本的代码完美的转换为向后兼容的 JavaScript 语法。
preset-env, polyfill, plugin-transform-runtime 区别
现在我们通过 npm init -y 来创建一个例子,然后安装 @babel/cli 和 @babel/core。 通过命令 babel index.js --out-file compiled.js
把 index 文件用 babel 编译成compiled.js
// index.jsconst fn = () => { console.log("wens");};const p = new Promise((resolve, reject) => { resolve("wens");});const list = [1, 2, 3, 4].map(item => item * 2);
不加任何plugins
为了印证上面的说法,我们首先测试不加任何plugins的情况,结果如下
//compiled.jsconst fn = () => { console.log("wens");};const p = new Promise((resolve, reject) => { resolve("wens");});const list = [1, 2, 3, 4].map(item => item * 2);
编译好的文件没有任何变化,印证了我们上面的说法。接下来我们加入 plugins。
在加入plugins测试之前我们需要知道一些前置知识,babel将ECMAScript 2015+ 版本的代码分为了两种情况处理:
语法层: let、const、class、箭头函数等,这些需要在构建时进行转译,是指在语法层面上的转译
api方法层:Promise、includes、map等,这些是在全局或者Object、Array等的原型上新增的方法,它们可以由相应es5的方式重新定义
babel对这两种情况的转译是不一样的,我们需要给出相应的配置。
加入preset-env
上面的例子种const,箭头函数属于语法层面的,而promise和map属于api方法层面的,现在我们加入 preset-env 看看效果
// babel.config.jsmodule.exports = { presets: ["@babel/env"], plugins: []};
babel 官方定义的 presets 配置项表示的是一堆plugins的集合,省的我们一个个的写plugins,他直接定义好了类似处理react,typescript等的preset
//compiled.js"use strict";var fn = function fn() { console.log("wens");};var p = new Promise(function (resolve, reject) { resolve("wens");});var list = [1, 2, 3, 4].map(function (item) { return item * 2;});
果然从语法层面都降级了。那么api层面要如何处理呢? 下面我们加入 @bable/polyfill
加入polyfill
对于 polyfill 的定义,相信经常逛 mdn 的同学一定不会陌生, 他就是把当前浏览器不支持的方法通过用支持的方法重写来获得支持。
在项目中安装 @bable/polyfill ,然后 index.js 文件中引入
// index.jsimport "@bable/polyfill";const fn = () => { console.log("wens");};const p = new Promise((resolve, reject) => { resolve("wens");});const list = [1, 2, 3, 4].map(item => item * 2);
再次编译我们看看结果
// compiled.js"use strict";require("@bable/polyfill");var fn = function fn() { console.log("wens");};var p = new Promise(function (resolve, reject) { resolve("wens");});var list = [1, 2, 3, 4].map(function (item) { return item * 2;});
没有别的变化,就多了一行require("@bable/polyfill"),其实这里就把这个库中的所有的polyfill都引入进来了,就好比我们项目中一股脑引入了全部的lodash方法。这样就支持了Promise和map方法。
细心的同学一定会发现这样有点“蠢”啊,lodash都提供了按需加载,你这个一下都引入进来了,可我只需要Promise和map啊。别慌,我们接着往下看。
配置 useBuiltIns
上面我们通过 import "@bable/polyfill"
的方式来实现针对api层面的“抹平”。然而从 babel v7.4.0开始官方就不建议采取这样的方式了。 因为引入 @bable/polyfill 就相当于在代码中引入下面两个库
import "core-js/stable"; import "regenerator-runtime/runtime";
这意味着不仅不能按需加载还有全局空间被污染的问题。因为他是通过向全局对象和内置对象的prototype上添加方法来实现的。
因此 babel 决定把这两个人的工作一并交给上面我们提到的@babel/env,不仅不会全局污染还支持按需加载,岂不是妙哉。现在我们再来看看改造后的配置
// index.js// 去掉了polyfillconst fn = () => { console.log("wens");};const p = new Promise((resolve, reject) => { resolve("wens");});const list = [1, 2, 3, 4].map(item => item * 2);
// webpack.config.jsmodule.exports = { presets: [ [ "@babel/env", { useBuiltIns: "usage", // 实现按需加载 corejs: { version: 3, proposals: true } } ] ], plugins: []};
通过给 @babel/env 配置 useBuiltIns 和 corejs 属性,我们实现了polyfill方法的按需加载。关于全部配置项,参加官方文档
// compiled.js"use strict";require("core-js/modules/es.array.map");require("core-js/modules/es.object.to-string");require("core-js/modules/es.promise");var fn = function fn() { console.log("wens");};var p = new Promise(function (resolve, reject) { resolve("wens");});var list = [1, 2, 3, 4].map(function (item) { return item * 2;});
编译后的js文件只require了需要的方法,完美。那么我们还有可以优化的空间吗?有的有的,我们接着往下看。
加入 @babel/plugin-transform-runtime
改造上面的例子
// index.jsclass Person { constructor(name) { this.name = name; } say() { console.log(this.name); }}
只转换一个 Person 类,我们看看转换后的文件长啥样
// compiled.js "use strict";require("core-js/modules/es.function.name");require("core-js/modules/es.object.define-property");function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }var Person = function () { function Person(name) { _classCallCheck(this, Person); this.name = name; } _createClass(Person, [{ key: "say", value: function say() { console.log(this.name); } }]); return Person;}();
除了require的部分,还多了好多自定义的函数。同学们想一想,现在只有一个index文件需要转换,然而实际项目开发中会有大量的需要转换的文件,如果每一个转换后的文件中都存在相同的函数,那岂不是浪费了,怎么才能把重复的函数去掉呢?
plugin-transform-runtime 闪亮登场。
上面出现的_classCallCheck,_defineProperties,_createClass三个函数叫做辅助函数,是在编译阶段辅助 Babel 的函数。
当使用了plugin-transform-runtime插件后,就可以将babel转译时添加到文件中的内联辅助函数统一隔离到babel-runtime提供的helper模块中,编译时,直接从helper模块加载,不在每个文件中重复的定义辅助函数,从而减少包的尺寸,下面我们看下效果:
// webpack.config.jsmodule.exports = { presets: [ [ "@babel/env", { useBuiltIns: "usage", corejs: { version: 3, proposals: true } } ] ], plugins: ["@babel/plugin-transform-runtime"]};
// compiled.js"use strict";var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");require("core-js/modules/es.function.name");var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));var Person = function () { function Person(name) { (0, _classCallCheck2["default"])(this, Person); this.name = name; } (0, _createClass2["default"])(Person, [{ key: "say", value: function say() { console.log(this.name); } }]); return Person;}();
完美的解决了代码冗余的问题。 你们以为这就结束了吗,还没有。仔细看到这里的同学应该注意到了虽然上面使用 useBuiltIns
配置项实现了poilyfill的按需引用,可是他还存在全局变量污染的情况,就好比这句代码,重写了array的prototype方法,造成了全局污染。
require("core-js/modules/es.array.map");
最后再改造一次babel的配置文件
// webpack.config.jsmodule.exports = { presets: ["@babel/env"], plugins: [ [ "@babel/plugin-transform-runtime", { corejs: { version: 3 } } ] ]};
我们看到去掉了 @babel/env 的相关参数,而给 plugin-transform-runtime 添加了corejs参数,最终转换后的文件不会再出现polyfill的require的方法了。
// compiled.js"use strict";var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));var Person = function () { function Person(name) { (0, _classCallCheck2["default"])(this, Person); this.name = name; } (0, _createClass2["default"])(Person, [{ key: "say", value: function say() { console.log(this.name); } }]); return Person;}();
综上所述,plugin-transform-runtime 插件借助babel-runtime实现了下面两个重要的功能
对辅助函数的复用,解决转译语法层时出现的代码冗余
解决转译api层出现的全局变量污染
“用了babel还需不需要polyfill”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!