前言

置信很多人对Babel都理解,然而差不多是只知其一;不知其二,很多中央预计是懵懵懂懂的感觉。配置的时候,在网上搜寻,而后复制粘贴,能跑通就好,但偶然会呈现一些稀奇古怪的问题。前面想深刻理解学习Babel,又发现官网读起来艰涩难懂,或者照着官网敲Demo,又发现理论后果不是官网说的那样(其实是因为咱们装置的依赖版本有问题,装置成最新的了,所以输入的成果跟官网的不一样)。这样就造成咱们对Babel更加的困惑。

因为Babel内容切实太多了,所以这篇文章不讲原理,也不讲怎么配置(配置后续会出专门的文章说),但会带着大家一起了解让人感觉“艰涩难懂”的官网,而后梳理、理解,咱们平时接触Babel用到的次要几个包,搞清楚Babel是什么、作用又是什么。

为了让大家更好的感触Babel对咱们日常我的项目的作用,有些例子会联合Webpack,毕竟咱们平时的我的项目,根本都会通过Webpack等打包工具跟Babel相结合输入最初的包,而后在浏览器中运行。

章节中的案例,代码都放到Github上了,倡议大家边浏览,边跟着案例看。如果大家感觉有帮忙到,欢送StarFork学习。

备注:

  • 以后@babel/core最新版本是:7.20.12
  • 以后@babel/preset-env最新版本是:7.20.2

Babel

官网解释:Babel是一个工具链,次要用于将采纳ECMAScript 2015+语法编写的代码转换为向后兼容的 JavaScript语法,以便可能运行在以后和旧版本的浏览器或其余环境中。

咱们能够这么了解,Babel就是一个工具。它是一个能够将ES6+等新个性,转换成低版本浏览器或其余环境能反对并失常运行的一个工具。

构造

很多人认为Babel只有pluginspresets等几个配置。其实不止,咱们看看Babel配置文件大抵架构:

// babel.config.jsmodule.exports = {    ...,    envName: "development",    plugins: [],    presets: [],    passPerPreset: false,    targets: {},    browserslistConfigFile: true,    browserslistEnv: undefined,    inputSourceMap: true    ...}

咱们个别次要用到的就是pluginspresets这两个

性能

从大体上看,Babel提供以下两个性能组成:

  • 编译ES6+最新语法(letclass() => {}等)
  • 实现旧版本浏览器不反对的ES6+APIPromiseSymbolArray.prototype.includes等)

参考文章:

  • What is Babel?
  • Options

@babel/core

core能够看出,它是Babel实现编译的外围。所以咱们如果要应用Babel@babel/core这个包肯定是必不可少的。另外咱们平时说的Babel 6Babel 7指的就是@babele/core的版本

参考文章:@babel/core

@bable/cli

官网解释:Babel自带了一个内置的CLI命令行工具,可通过命令行编译文件

简略地说就是,让咱们能够在终端里应用命令来编译(这样能够更好的调试打印信息):

npx babel index.js

装置的话,咱们最好装置到咱们我的项目的本地目录下,尽量不要装置到全局(影响全局的货色,都很可怕)

参考文章:@babel/cli

@bable/preset-env

官网解释:@babel/preset-env是一个智能预设,它容许您应用最新的JavaScript,而无需宏观治理指标环境须要哪些语法转换(以及可选的浏览器polyfill)。这既让你的生存更轻松,也让JavaScript包更小!

了解

@bable/preset-env这个名字,咱们能够拆开两局部来看,这样不便了解:

  • preset预设
  • env环境

preset

Babel编译ES6+语法,是通过一个个插件plugin去实现的。每年都会有不同新的提案、新的语法,但咱们不可能一个个插件去配置,所以就有了preset这个货色。因而咱们能够了解成preset就是一个语法插件汇合包,这样咱们只用装置这一个包,不须要一个个配插件,就能够很不便的编译最新的语法了。

咱们通过一个不必预设的案例 no-preset ,感受一下如果不必preset有多麻烦。

//  入口文件 index.jsconst senses = ['eye', 'nose', 'ear', 'mouth'];const lMC = {    senses,    like: ['eat', 'drink', 'play', 'fun'],    information: {        sex: 'male',        age: '18+'    },    play: (sport = 'badminton') => {        console.log(`play ${sport}`);    }};const { like, information } = lMC;

这段代码,咱们用了几个ES6新语法:

  • const申明
  • 属性的简洁表示法
  • 箭头函数
  • 函数默认值
  • 模板字符串
  • 解构

如果不必preset咱们Babel配置如下:

// Babel配置文件 babel.config.jsconst plugins = [    '@babel/plugin-transform-arrow-functions',    '@babel/plugin-transform-block-scoping',    '@babel/plugin-transform-destructuring',    '@babel/plugin-transform-parameters',    '@babel/plugin-transform-shorthand-properties',    '@babel/plugin-transform-template-literals'];module.exports = {plugins};

编译后的文件:

// 编译后的文件 compile.jsvar senses = ['eye', 'nose', 'ear', 'mouth'];var lMC = {  senses: senses,  like: ['eat', 'drink', 'play', 'fun'],  information: {    sex: 'male',    age: '18+'  },  play: function () {    var sport = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'badminton';    console.log("play ".concat(sport));  }};var like = lMC.like,  information = lMC.information;

在不必preset的状况下,实现上述编译的过程,我根本是用一个ES6新语法,我就要去查一个插件,首先我不记得那么多插件,其次一个个插件找真的很累。

ok,那咱们再用一个应用了预设的案例 use-preset ,感受一下预设到底有多不便。
咱们npm i @babel/preset-env -D,批改babel.config.js应用preset预设:

// 批改babel.jsonconst presets = [    '@babel/preset-env'];module.exports = {presets};

编译后的文件:

// 编译后的文件 compile.js"use strict";var senses = ['eye', 'nose', 'ear', 'mouth'];var lMC = {  senses: senses,  like: ['eat', 'drink', 'play', 'fun'],  information: {    sex: 'male',    age: '18+'  },  play: function play() {    var sport = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'badminton';    console.log("play ".concat(sport));  }};var like = lMC.like,  information = lMC.information;

咱们会发现,用preset(预设)形式输入的代码,跟plugins(不必预设)形式输入的代码是简直是截然不同的。然而presetbabel.config.js更简洁,我也不须要一个个插件去找,也不须要装置那么多插件,只用装置@babel/preset-env这一个包,就能够很欢快的写ES6+

env

env指的是环境。因为@babel/preset-env还有一个配置性能,咱们能够通过配置咱们代码运行的指标环境,来管制polyfill(一个提供低版本浏览器缺失的ES6+新个性的办法与实现的汇合 ,前面会有更具体的解说)的导入跟语法编译,从而使ES6+新的个性能够在咱们想要的指标环境中顺利运行。

备注:@babel/preset-env还有一个配置性能,本文不讲配置,对于配置后续会有文章阐明

性能

通过上面对presetenv的了解跟案例感触,咱们能总结出@babel/preset-env次要提供以下性能:

  • 编译ES6+语法(上述案例只应用了ES6+的语法,并没有用ES6+API)
  • 它并不提供polyfill,然而能够通过配置咱们代码运行的指标环境,从而管制polyfill的导入跟语法编译,使ES6+的新个性能够在咱们想要的指标环境中顺利运行

留神

咱们先看看TC39提案分为几个阶段:

  • 阶段0  (stage-0)——草根(Strawman):只是一个想法,可能是Babel插件。
  • 第一阶段(stage-1)——提案(Proposal):这是值得钻研的。
  • 第二阶段(stage-2)——草案(Draft):初步标准。
  • 第三阶段(stage-3)——候选(Candidate):残缺的标准和最后的浏览器实现。
  • 第四阶段(stage-4)——实现(Finished):将被增加到下一年度的版本中。

再看看官网中这段话:

Note: @babel/preset-env won't include any JavaScript syntax proposals less than Stage 3 because at that stage in the TC39 process, it wouldn't be implemented by any browsers anyway. Those would need to be included manually.

大抵意思是:

  • Babel 7当前,@bable/preset-env舍弃了Stage presets@babel/preset-stage-x)这种预设
  • @bable/preset-env只提供TC39大于stage-3的提案(即只蕴含stage-4阶段),因而如果要用小于stage 4的提案语法,则必须先装置再手动引入对应插件

第一点置信大家都很好了解,咱们来了解一下第二点是什么意思。

意思是,如果咱们想用一些小于stage-4阶段的语法的话,光装置@babel/preset-env这一个包是没有用的,因为这个包里只蕴含编译stage-4的预设,所以咱们就得装置并配置相应的plugin去编译。

在写这篇文章的时候,有一个新的语法 do expressions ,它以后是处于stage-1阶段的语法,用插件@babel/plugin-proposal-do-expressions能够编译这个语法。

官网解释:do { .. } 表达式执行一个块(其中有一个或多个语句),块内的最终语句实现值成为 do 表达式的实现值。

咱们借助官网,整顿成这个案例 compile-stage-1 来看看怎么应用小于stage-4的语法。

咱们先只用@babel/preset-env,看看能不能编译do {...}这个语法。

// do expressions stage-1语法let x = 100;let y = 20;let a = do {    if (x > 10) {        if (y > 20) {            ("big x, big y");        } else {            ("big x, small y");        }    } else {        if (y > 10) {            ("small x, big y");        } else {            ("small x, small y");        }    }};

Babel.config.js配置:

const presets = [    '@babel/preset-env'];// const plugins = [    // '@babel/plugin-proposal-do-expressions'// ];// module.exports = {plugins, presets};module.exports = {presets};

咱们会发现,终端会报错:

大抵意思是:@babel/preset-env以后未启用对试验语法doExpressions的反对(因为doExpressions以后是stage-1的语法,@babel/preset-env只蕴含必编译stage-4的语法插件),须要咱们退出@babel/plugin-proposal-do-expressions插件去编译。

那咱们npm i @babel/plugin-proposal-do-expressions -D,批改一下babel.config.js

const presets = [    '@babel/preset-env'];const plugins = [    '@babel/plugin-proposal-do-expressions'];module.exports = {plugins, presets};// module.exports = {presets};

咱们能够看到,能够失常输入编译后的文件:

"use strict";var x = 100;var y = 20;var a = x > 10 ? y > 20 ? "big x, big y" : "big x, small y" : y > 10 ? "small x, big y" : "small x, small y";

所以,当咱们想应用小于stage-4阶段的语法时,咱们要先找到其对应的编译插件装置,而后在plugins外面配置就好了。

参考文章:

  • TC39 proposals
  • proposal-do-expressions
  • @babel/plugin-proposal-do-expressions

补充

有时咱们也可能须要晓得咱们以后的preset(预设)蕴含了哪些插件,那咱们怎么查看以后@babel/preset-env蕴含了哪些预设呢?

咱们能够通过查看@babel/preset-env --> package.json --> dependencies外面能够找到。我目前装置的@babel/preset-env版本为7.20.2,它蕴含了以下预设:

// @babel/preset-env@7.20.2预设"dependencies": {    "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6",    "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9",    ...,    "@babel/plugin-transform-sticky-regex": "^7.18.6",    "@babel/plugin-transform-template-literals": "^7.18.9",    "@babel/plugin-transform-typeof-symbol": "^7.18.9",    "@babel/plugin-transform-unicode-escapes": "^7.18.10",    "@babel/plugin-transform-unicode-regex": "^7.18.6",  },

polyfill

性能

ES6+除了提供很多简洁的语法(letclass() => {}等)外,还为咱们提供了很多便捷的APIPromiseSymbolArray.prototype.includes等)。但旧版本浏览器是不反对这些API,而polyfill寄存了这些API的办法与实现,所以它能够使得这些不反对的浏览器,反对这些API

了解

咱们能够把所有这种寄存了ES6+ API办法与实现的汇合叫做polyfill,也就是咱们常常说的垫片

polyfill也分很多种,像core-js是会提供旧版本浏览器缺失的所有API;还有一些只提供缺失API某块,例如 promise-polyfill、proxy-polyfill 等。

Babel配置polyfill的过程,就是实现旧版本浏览器对这些API反对的过程。

@babel/polyfill

下面咱们解释了polyfill是什么,从包名@babel/polyfill就晓得,它就是一个polyfill(其外围是依附core-js@2.x.x实现)。尽管这个包曾经被废除了,但咱们还是略微理解一下它。

官网解释:
从Babel 7.4.0开始,这个包曾经被弃用,转而间接蕴含core-js/stable(用于polyfill ECMAScript性能)

应用:

import "core-js/stable";

初识

咱们通过这个例子 know-babel-polyfill,来理解一下@babel/polyfill的组成。
know-babel-polyfill 什么都没装置,只装置了@bable/polyfill这个依赖,咱们能够很分明看到,@bable/polyfill由以下两个包组成:

<img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8ff1acb6c6e2463aad5798f805f3efd8~tplv-k3u1fbpfcp-watermark.image?" alt="babel-polyfill.jpeg" width="100%" />

  • core-js版本为2
  • regenerator-runtime

咱们来大抵了解一下这两包是什么:

core-js

这个包就是咱们上述polyfill模块所说的,外面寄存了很多ES6+ API的办法与实现。如果要在旧浏览用到PromiseSymbolArray.prototype.includes等办法时,这个包会为咱们提供。它能够使那些不反对API的浏览器,反对这些API,它就是一种垫片。

特地留神:由上图可知,@babel/polyfill是与2版本的core-js绑定的,2版本的core-js并不蕴含stable这个文件夹的。因而官网说的import "core-js/stable",实际上是要咱们装置core-js@3.x.x版本来代替@babel/polyfill,因为从3版本开始,才有stable这个文件夹

regenerator-runtime

咱们的源码外面应用了async function() {}等异步函数,或者fuction* myGenerator() {}这种Generator函数的话,就会须要用到这个包来编译。

总结

所以对于@babel/polyfill,咱们有以下总结:

  • 这个包由core-js版本为2.x.x)与regenerator-runtime两个包组成
  • 这个包在Babel 7.4.0当前就被废除了,所以在Babel 7.4.0当前,咱们想让一些不反对ES6+ API的浏览器反对这些API,就不应该装置这个包,应该装置core-js@3.x.x的包(不要装置2.x.x的版本,曾经不保护了,目前最新版本为3.x.x

参考文章:@babel/polyfill

core-js

概述

通过下面polyfill@babel/polyfill两个模块,咱们能够晓得@babel/polyfill曾经不再应用,而且@babel/polyfill实现的外围就是core-js,所以如果咱们想要在旧浏览器用到PromiseSymbolArray.prototype.includes等办法时,咱们间接装置core-js@3.x.x这个包。

通过 官网的介绍,咱们能够晓得:

import '@babel/polyfill';

等同于

// core-js必须是3.x.x版本,因为2.x.x版本,不蕴含stable文件夹import "core-js/stable";import "regenerator-runtime/runtime";

Babel >= 7.18.0等同于

// core-js必须是3.x.x版本,因为2.x.x版本,不蕴含stable文件夹// Babel >= 7.18.0后 不须要再 import "regenerator-runtime/runtime";import "core-js/stable";

留神

咱们针对不须要再import "regenerator-runtime/runtime"这块,略微解释一下,加深一下咱们对Babel跟官网文档的了解。

咱们看官网这段话:

If you are compiling generators or async function to ES5, and you are using a version of @babel/core or @babel/plugin-transform-regenerator older than 7.18.0, you must also load the regenerator-runtime package

大家看这句话的时候可能有点纳闷,其实它的意思就是:

如果咱们要把async function() {}等异步函数,或者fuction* myGenerator() {}这种Generator函数编译成ES5,并且@babel/core@babel/plugin-transform-regenerator小于7.18.0,咱们就须要手动import "regenerator-runtime/runtime"这个包。

但在Babel 7.18.0或者@babel/plugin-transform-regenerator 7.18.0及其当前的版本,regenerator-runtime包外面的内容会被内联编译到咱们的代码中,所以咱们只用引入import "core-js/stable"这一个包就能够了。

咱们来用两个例子联合Webpack打包进去,在浏览器运行,这样更直观的了解感受一下。

Babel < 7.18.0

咱们用这个例子 import-regenerator-runtime 看看在Babel 7.18.0之前为什么要手动引入regenerator-runtime这个包。

特地阐明: 咱们例子装置Babel的版本为7.16.7@babel/plugin-transform-regenerator这个插件必须手动装置为小于7.18.0的版本(因为咱们装置依赖的时候,即便指定了依赖的版本,但依赖的依赖装置时,可能会是最新的,这样可能会看不出成果。所以为什么有时咱们对着官网敲Demo理论进去的后果不一样,因为版本没对上)。能够通过package-lock.json查看各个依赖版本

ok,来看看咱们的入口文件(index.js):

// 先不引入regenerator-runtime/runtime// import 'regenerator-runtime/runtime';const sleep = async function() {    setTimeout(() => console.log('get up'), 1000);}sleep();

接着咱们打包(Webpack打包进去的文件在dist/dist.js)在浏览器运行。失常状况下,浏览器应该会过一秒后输入get up。但理论状况如下,咱们会发现之前网友们经常出现的一个问题——regeneratorRuntime is not defined

阐明缺失了regeneratorRuntime,咱们再看看Babel编译后的文件(compile.js):

咱们发现在全局中,regeneratorRuntime基本没有定义,所以才报了regeneratorRuntime is not defined的错。

如果咱们再手动引入一下import "regenerator-runtime/runtime"

import 'regenerator-runtime/runtime';const sleep = async function() {    setTimeout(() => console.log('get up'), 1000);}sleep();

此时浏览器输入:

当咱们手动引入当前,浏览器能够失常运行了。

这阐明,在@babel/core@babel/plugin-transform-regenerator的版本小于7.18.0的时候,应用了异步函数(async function() {}),或者Generator这种函数(fuction* myGenerator() {})的话,是须要咱们手动引入regenerator-runtime这个包的,因为regenerator-runtime这个包会为咱们提供regeneratorRuntime这个全局对象

Babel >= 7.18.0

咱们用这个例子 no-import-regenerator-runtime 看看在Babel 7.18.0之后为什么不须要手动引入regenerator-runtime这个包。(@babel/core版本为7.20.12

ok,来看看咱们的入口文件,这时不再手动引入regenerator-runtime这个包:

const sleep = async function() {    setTimeout(() => console.log('get up'), 1000);}sleep();

编译出包当前在浏览器运行,失去跟上述手动引入regenerator-runtime这个包截然不同的成果:

咱们再看看Babel编译后的文件:

咱们会发现,regenerator-runtime包里的内容,会以局部变量的形式内联注入到咱们的代码中,这样咱们就不须要全局提供一个regeneratorRuntime对象了。

所以,在Babel >= 7.18.0当前,咱们间接import "core-js/stable";就好

参考文章:

  • core-js
  • @babel/polyfill
  • Inline regenerator-runtime helper

@babel/runtime

官网解释:@babel/runtime是一个蕴含Babel模块化运行时助手的库

Babel编译的时候,会有一些辅助函数,这些函数就是ES6+一些语法糖的实现,咱们用这个案例 helper-functions 看看辅助函数是什么。

咱们用Babel编译一下class这个语法糖:

class People {    constructor() {    }}const person  = new Person();

编译当前:

咱们先看红色框,它是Babel编译后的代码。咱们会发现,编译当前生成很多函数,并且会以内联的形式插入到咱们的代码中,这些函数就是咱们说的辅助函数

咱们再看蓝色框,它是@babel/runtime的内容,它在node_modules/@babel/runtime/helpers

咱们最初来看看红色框,会发现Babel编译后的辅助函数,都能够在@bable/runtime外面找到,所以@babel/runtime寄存了Babel辅助函数的一个汇合包

参考文章:@babel/runtime

@babel/plugin-transform-runtime

官网解释:一个插件,能够重用Babel注入的帮忙程序代码以节俭代码大小

通过下面@babel/runtime模块的理解,咱们晓得当咱们应用了一些ES6+的语法糖时,Babel会生成一些辅助函数来编译这些语法糖,并以内联的形式插入到代码中。

那如果咱们有10个文件都用到了语法糖,那这些辅助函数,是不是会生成10次,并内联插入10次呢?咱们用这个案例 no-use-transform-runtime 来感受一下。

咱们定义了三个文件,每个文件都用了class这个语法糖。

// babel.config.js 配置文件const presets = [    '@babel/preset-env'];module.exports = {presets};// Animal.js 文件export default class Animal {    constructor() {}};// Country.js 文件export default class Country {    constructor() {}};// index.js 文件import Animal from "./class/Animal";import Country from "./class/Country";class People {    constructor() {    }};const lMC = new People();const cat = new Animal();const usa = new Country();

最初打包进去文件:

看看红色的框框,咱们会发现实现的办法都是一样的,所以在每个应用到class语法糖的文件中,辅助函数都被生成并插入了一次,这些根本反复的代码,无疑是会大大增加咱们的打包体积的。目前打包进去的体积是:6KB

为了解决上述的弊病,咱们就得应用@babel/plugin-transform-runtime插件。从@babel/runtime模块咱们晓得,它外面寄存了Babel辅助函数的汇合,@babel/plugin-transform-runtime会将咱们用到的辅助函数,从@babel/runtime中以require或者import的形式,引入到咱们的文件中,实现复用,从而减小咱们最终输入包的体积。

所以@babel/runtime@babel/plugin-transform-runtime两者通常是配合一起应用。

备注:@babel/plugin-transform-runtime还有一个配置性能,本文不讲配置,对于配置后续会有文章阐明

咱们用这个案例 use-transform-runtime 看看应用了@babel/plugin-transform-runtime插件当前有什么变动。

咱们的案例代码跟上述一样,只是在babel.config.js减少了@babel/plugin-transform-runtime配置

// babel.config.js 配置文件// 减少了@babel/plugin-transform-runtime 配置const plugins = [    '@babel/plugin-transform-runtime']const presets = [    '@babel/preset-env'];module.exports = {plugins, presets};// Animal.js 文件export default class Animal {    constructor() {}};// Country.js 文件export default class Country {    constructor() {}};// index.js 文件import Animal from "./class/Animal";import Country from "./class/Country";class People {    constructor() {    }};const lMC = new People();const cat = new Animal();const usa = new Country();

编译跟打包后的文件:

咱们会发现:

  • 辅助函数会以require援用的形式加到咱们的代码中
  • 打包后,辅助函数只用了一次,而且不是插入三次,很好的实现了复用
  • 打包进去的体积也变成了3KB,很好的放大了最初包的体积(不要小看放大了3KB,只是因为我用最简略的形式写了ES6+语法,理论中咱们我的项目必定没那么简略)

最初

因为Babel的常识体系切实太大了,所以咱们应该先把Babel次要的几个包弄清楚,再深刻配置。对于Babel的配置,会后续再出文章。

咱们平时我的项目中Babel用到的包,根本就是这篇文章中解说的几个包,这篇文章算是非常具体的介绍了这几个包了。如果大家能把这几个包弄得很分明,Babel的大部分常识也理解的差不多了。

如果之前对Babel还有点懵懵的你,心愿读完这篇文章后,能够很好的了解Babel大抵是个什么货色,也能更分明的看懂官网写的内容。

两头有用到Webpack,我本人用Webpack5搭了个脚手架 webpack5-boilerplate,如果你也想理解Webpack的常识,也能够看看之前我写的这篇文章—— webpack5优化的中央及搭建的一些领会。

如果感觉真的有帮忙到,欢送点赞珍藏;如果有异同点,欢送在评论区探讨