Babel在目前前端领域类似一座山一样的存在,任何项目或多或少都有它的身影在浮现。
也许对于Babel绝大多数前端开发者都是处于一知半解的状态,但是无论是在实际业务开发中还是对于我们个人提升来说熟练掌握Babel一定是晋升高级前端工程师的必备之路。
文章中我们只讲“干货”,从原理出发结合深层次实践带你领略Babel之美。
我们会从Babel基础内容从而渐进到Babel插件开发者的世界,从此让你对于Babel得心应手。
首先我们会从基础的配置Babel及相关内容开始讲解。
首先我们来说说Plugin和Preset的区别和联系。
所谓Preset就是一些Plugin组成的合集,你可以将Preset理解称为就是一些的Plugin整合称为的一个包。
文章中列举了三个最常用的Preset,更多的Prest你可以在这里查阅。
babel-preset-env
@babel/preset-env是一个智能预设,它可以将我们的高版本JavaScript代码进行转译根据内置的规则转译成为低版本的javascript代码。
preset-env内部集成了绝大多数plugin(State > 3)的转译插件,它会根据对应的参数进行代码转译。
@babel/preset-env不会包含任何低于 Stage 3 的 JavaScript 语法提案。如果需要兼容低于Stage 3阶段的语法则需要额外引入对应的Plugin进行兼容。
需要额外注意的是babel-preset-env仅仅针对语法阶段的转译,比如转译箭头函数,const/let语法。针对一些Api或者Es 6内置模块的polyfill,preset-env是无法进行转译的。这块内容我们会在之后的polyfill中为大家进行详细讲解。
babel-preset-react
通常我们在使用React中的jsx时,相信大家都明白实质上jsx最终会被编译称为React.createElement()方法。
babel-preset-react这个预设起到的就是将jsx进行转译的作用。
babel-preset-typescript
对于TypeScript代码,我们有两种方式去编译TypeScript代码成为JavaScript代码。
使用tsc命令,结合cli命令行参数方式或者tsconfig配置文件进行编译ts代码。
使用babel,通过babel-preset-typescript代码进行编译ts代码。
Babel官网列举出了一份非常详尽的Plugin List。
关于常见的Plugin其实大多数都集成在了babel-preset-env中,当你发现你的项目中并不能支持最新的js语法时,此时我们可以查阅对应的Babel Plugin List找到对应的语法插件添加进入babel配置。
同时还有一些不常用的packages,比如@babel/register:它会改写require命令,为它加上一个钩子。此后,每当使用require加载.js、.jsx、.es和.es6后缀名的文件,就会先用Babel进行转码。
这些包日常中不是特别常用,如果有同学有相关编译相关需求完全可以去babel官网查阅。如果官网不存在现成的plugin/package,别担心!我们同时也会在之后手把手教大家babel插件的开发。
其中最常见的@babel/plugin-transform-runtime我们会在下面的Polyfill进行详细的讲解。
接下里我们聊聊前端项目构建中相关的babel相关配置。
关于前端构建工具,无路你使用的是webapack还是rollup又或是任何构建打包工具,内部都离不开Babel相关配置。
这里我们使用业务中最常用的webpack举例,其他构建工具在使用方面只是引入的包不同,Babel配置原理是相通的。
关于WebPack中我们日常使用的babel相关配置主要涉及以下三个相关插件:
babel-loader
babel-core
babel-preset-env
也许你经常在项目搭建过程中见到他们,这里我们将逐步使用一段伪代码来讲解他们之间的区别和联系。
首先我们需要清楚在 webpack中loader的本质就是一个函数,接受我们的源代码作为入参同时返回新的内容。
babel-loader
所以babel-loader的本质就是一个函数,我们匹配到对应的jsx?/tsx?的文件交给babel-loader:
/**
*
* @param sourceCode 源代码内容
* @param options babel-loader相关参数
* @returns 处理后的代码
*/
function babelLoader (sourceCode,options) {
// ..
return targetCode
}
关于options,babel-loader支持直接通过loader的参数形式注入,同时也在loader函数内部通过读取.babelrc/babel.config.js/babel.config.json等文件注入配置。
babel-core
我们讲到了babel-loader仅仅是识别匹配文件和接受对应参数的函数,那么babel在编译代码过程中核心的库就是@babel/core这个库。
babel-core是babel最核心的一个编译库,他可以将我们的代码进行词法分析--语法分析--语义分析过程从而生成AST抽象语法树,从而对于“这棵树”的操作之后再通过编译称为新的代码。
babel-core其实相当于@babel/parse和@babel/generator这两个包的合体,接触过js编译的同学可能有了解esprima和escodegen这两个库,你可以将babel-core的作用理解称为这两个库的合体。
babel-core通过transform方法将我们的代码进行编译。
关于babel-core中的编译方法其实有很多种,比如直接接受字符串形式的transform方法或者接受js文件路径的transformFile方法进行文件整体编译。
关于babel-core内部的编译使用规则,我们会在之后的插件章节中详细讲到。
接下来让我们完善对应的babel-loader函数:
const core = require('@babel/core')
/**
*
* @param sourceCode 源代码内容
* @param options babel-loader相关参数
* @returns 处理后的代码
*/
function babelLoader (sourceCode,options) {
// 通过transform方法编译传入的源代码
core.transform(sourceCode)
return targetCode
}
这里我们在babel-loader中调用了babel-core这个库进行了代码的编译作用。
babel-preset-env
上边我们说到babel-loader本质是一个函数,它在内部通过babel/core这个核心包进行JavaScript代码的转译。
但是针对代码的转译我们需要告诉babel以什么样的规则进行转化,比如我需要告诉babel:“嘿,babel。将我的这段代码转化称为EcmaScript 5版本的内容!”。
此时babel-preset-env在这里充当的就是这个作用:告诉babel我需要以为什么样的规则进行代码转移。
const core = require('@babel/core');
/**
*
* @param sourceCode 源代码内容
* @param options babel-loader相关参数
* @returns 处理后的代码
*/
function babelLoader(sourceCode, options) {
// 通过transform方法编译传入的源代码
core.transform(sourceCode, {
presets: ['babel-preset-env'],
plugins: [...]
});
return targetCode;
}
这里plugin和prest其实是同一个东西,所以我将plugin直接放在代码中了。同理一些其他的preset或者plugin也是发挥这样的作用。
关于babel的基础基建配置我相信讲到这里大家已经明白了他们对应的职责和基础原理.
关于polyfill,我们先来解释下何谓polyfill。
首先我们来理清楚这三个概念:
babel-prest-env仅仅只会转化最新的es语法,并不会转化对应的Api和实例方法,比如说ES 6中的Array.from静态方法。babel是不会转译这个方法的,如果想在低版本浏览器中识别并且运行Array.from方法达到我们的预期就需要额外引入polyfill进行在Array上添加实现这个方法。
其实可以稍微简单总结一下,语法层面的转化preset-env完全可以胜任。但是一些内置方法模块,仅仅通过preset-env的语法转化是无法进行识别转化的,所以就需要一系列类似”垫片“的工具进行补充实现这部分内容的低版本代码实现。这就是所谓的polyfill的作用,
针对于polyfill方法的内容,babel中涉及两个方面来解决:
我们理清了何谓polyfill以及polyfill的作用和含义后,让我们来逐个击破这两个babel包对应的使用方式和区别吧。
@babel/polyfill
首先我们来看看第一种实现polyfill的方式:
@babel/polyfill介绍
通过babelPolyfill通过往全局对象上添加属性以及直接修改内置对象的Prototype上添加方法实现polyfill。
比如说我们需要支持String.prototype.include,在引入babelPolyfill这个包之后,它会在全局String的原型对象上添加include方法从而支持我们的Js Api。
我们说到这种方式本质上是往全局对象/内置对象上挂载属性,所以这种方式难免会造成全局污染。
应用@babel/polyfill
在babel-preset-env中存在一个useBuiltIns参数,这个参数决定了如何在preset-env中使用@babel/polyfill。
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": false
}]
]
}
false
当我们使用preset-env传入useBuiltIns参数时候,默认为false。它表示仅仅会转化最新的ES语法,并不会转化任何Api和方法。
entry
当传入entry时,需要我们在项目入口文件中手动引入一次core-js,它会根据我们配置的浏览器兼容性列表(browserList)然后全量引入不兼容的polyfill。
Tips: 在Babel7.4。0之后,@babel/polyfill被废弃它变成另外两个包的集成。"core-js/stable"; "regenerator-runtime/runtime";。你可以在这里看到变化,但是他们的使用方式是一致的,只是在入口文件中引入的包不同了。
// 项目入口文件中需要额外引入polyfill
// core-js 2.0中是使用"@babel/polyfill" core-js3.0版本中变化成为了上边两个包
import "@babel/polyfill"
// babel
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "entry"
}]
]
}
同时需要注意的是,在我们使用useBuiltIns:entry/usage时,需要额外指定core-js这个参数。默认为使用core-js 2.0,所谓的core-js就是我们上文讲到的“垫片”的实现。它会实现一系列内置方法或者Promise等Api。
core-js 2.0版本是跟随preset-env一起安装的,不需要单独安装哦~
usage
上边我们说到配置为entry时,perset-env会基于我们的浏览器兼容列表进行全量引入polyfill。所谓的全量引入比如说我们代码中仅仅使用了Array.from这个方法。但是polyfill并不仅仅会引入Array.from,同时也会引入Promise、Array.prototype.include等其他并未使用到的方法。这就会造成包中引入的体积太大了。
此时就引入出了我们的useBuintIns:usage配置。
当我们配置useBuintIns:usage时,会根据配置的浏览器兼容,以及代码中 使用到的Api 进行引入polyfill按需添加。
当使用usage时,我们不需要额外在项目入口中引入polyfill了,它会根据我们项目中使用到的进行按需引入。
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "usage",
"core-js": 3
}]
]
}
关于usage和entry存在一个需要注意的本质上的区别。
我们以项目中引入Promise为例。
当我们配置useBuintInts:entry时,仅仅会在入口文件全量引入一次polyfill。你可以这样理解:
// 当使用entry配置时
...
// 一系列实现polyfill的方法
global.Promise = promise
// 其他文件使用时
const a = new Promise()
而当我们使用useBuintIns:usage时,preset-env只能基于各个模块去分析它们使用到的polyfill从而进入引入。
preset-env会帮助我们智能化的在需要的地方引入,比如:
// a. js 中
import "core-js/modules/es.promise";
...
// b.js中
import "core-js/modules/es.promise";
...
在usage情况下,如果我们存在很多个模块,那么无疑会多出很多冗余代码(import语法)。
同样在使用usage时因为是模块内部局部引入polyfill所以按需在模块内进行引入,而entry则会在代码入口中一次性引入。
usageBuintIns不同参数分别有不同场景的适应度,具体参数使用场景还需要大家结合自己的项目实际情况找到最佳方式。
@babel/runtime
上边我们讲到@babel/polyfill是存在污染全局变量的副作用,在实现polyfill时Babel还提供了另外一种方式去让我们实现这功能,那就是@babel/runtime。
简单来讲,@babel/runtime更像是一种按需加载的解决方案,比如哪里需要使用到Promise,@babel/runtime就会在他的文件顶部添加import promise from 'babel-runtime/core-js/promise'。
同时上边我们讲到对于preset-env的useBuintIns配置项,我们的polyfill是preset-env帮我们智能引入。
而babel-runtime则会将引入方式由智能完全交由我们自己,我们需要什么自己引入什么。
它的用法很简单,只要我们去安装npm install --save @babel/runtime后,在需要使用对应的polyfill的地方去单独引入就可以了。比如:
// a.js 中需要使用Promise 我们需要手动引入对应的运行时polyfill
import Promise from 'babel-runtime/core-js/promise'
const promsies = new Promise()
总而言之,babel/runtime你可以理解称为就是一个运行时“哪里需要引哪里”的工具库。
针对babel/runtime绝大多数情况下我们都会配合@babel/plugin-transfrom-runtime进行使用达到智能化runtime的polyfill引入。
@babel/plugin-transform-runtime
babel-runtime存在的问题
babel-runtime在我们手动引入一些polyfill的时候,它会给我们的代码中注入一些类似_extend(), classCallCheck()之类的工具函数,这些工具函数的代码会包含在编译后的每个文件中,比如:
class Circle {}
// babel-runtime 编译Class需要借助_classCallCheck这个工具函数
function _classCallCheck(instance, Constructor) { //... }
var Circle = function Circle() { _classCallCheck(this, Circle); };
如果我们项目中存在多个文件使用了class,那么无疑在每个文件中注入这样一段冗余重复的工具函数将是一种灾难。
所以针对上述提到的两个问题:
我们就要引入我们的主角@babel/plugin-transform-runtime。
@babel/plugin-transform-runtime插件的作用恰恰就是为了解决上述我们提到的run-time存在的问题而提出的插件。
babel-runtime无法做到智能化分析,需要我们手动引入。
@babel/plugin-transform-runtime插件会智能化的分析我们的项目中所使用到需要转译的js代码,从而实现模块化从babel-runtime中引入所需的polyfill实现。
babel-runtime编译过程中会重复生成冗余代码。
@babel/plugin-transform-runtime插件提供了一个helpers参数。具体你可以在这里查阅它的所有配置参数。
这个helpers参数开启后可以将上边提到编译阶段重复的工具函数,比如classCallCheck, extends等代码转化称为require语句。此时,这些工具函数就不会重复的出现在使用中的模块中了。比如这样:
// @babel/plugin-transform-runtime会将工具函数转化为require语句进行引入
// 而非runtime那样直接将工具模块代码注入到模块中
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");
var Circle = function Circle() { _classCallCheck(this, Circle); };
其实用法原理部分已经在上边分析的比较透彻了,配置这里还有疑问的同学可以评论区给我留言或者移步babel官网查看。
这里为列一份目前它的默认配置:
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": false,
"helpers": true,
"regenerator": true,
"version": "7.0.0-beta.0"
}
]
]
}
我们可以看到针对polyfill其实我耗费了不少去将它们之间的区别和联系,让我们来稍微总结一下吧。
在babel中实现polyfill主要有两种方式:
一种是通过@babel/polyfill配合preset-env去使用,这种方式可能会存在污染全局作用域。
一种是通过@babel/runtime配合@babel/plugin-transform-runtime去使用,这种方式并不会污染作用域。
全局引入会污染全局作用域,但是相对于局部引入来说。它会增加很多额外的引入语句,增加包体积。
在useBuintIns:usage情况下其实和@babel/plugin-transform-runtime情况下是类似的作用,
通常我个人选择是会在开发类库时遵守不污染全局为首先使用@babel/plugin-transform-runtime而在业务开发中使用@babel/polyfill。
babel-runtime 是为了减少重复代码而生的。babel生成的代码,可能会用到一些_extend(), classCallCheck() 之类的工具函数,默认情况下,这些工具函数的代码会包含在编译后的文件中。如果存在多个文件,那每个文件都有可能含有一份重复的代码。
babel-runtime插件能够将这些工具函数的代码转换成require语句,指向为对babel-runtime的引用,如 require('babel-runtime/helpers/classCallCheck'). 这样, classCallCheck的代码就不需要在每个文件中都存在了。
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/KXpDOcCnnetQvAMW2DGBBA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。