babel

https://juejin.cn/post/6844903956905197576

https://juejin.cn/post/6844903743121522701

把 JavaScript 中 es2015/2016/2017/2046 的新语法转化为 es5,让低端运行环境(如浏览器和 node )能够认识并执行

严格来说,babel 也可以转化为更低的规范。但以目前情况来说,es5 规范已经足以覆盖绝大部分浏览器,因此常规来说转到 es5 是一个安全且流行的做法。


使用方法

总共存在三种方式:

-使用单体文件 (standalone script)

-命令行 (cli)

-构建工具的插件 (webpack 的 babel-loader, rollup 的 rollup-plugin-babel)。

其中后面两种比较常见。第二种多见于 package.json 中的scripts段落中的某条命令;第三种就直接集成到构建工具中。

这三种方式只有入口不同而已,调用的 babel 内核,处理方式都是一样的,所以我们先不纠结入口的问题。


(1)babel的转译过程分为三个阶段:parsing、transforming、generating,以ES6代码转译为ES5代码为例,babel转译的具体过程如下:

parsing、transforming、generating

•ES6代码输入

•babylon 进行解析得到 AST

•plugin 用 babel-traverse 对 AST 树进行遍历转译,得到新的AST树

•用 babel-generator 通过 AST 树生成 ES5 代码

转译过程

运行方式和插件

babel 总共分为三个阶段:解析,转换,生成。

babel 本身不具有任何转化功能,它把转化的功能都分解到一个个 plugin 里面。因此当我们不配置任何插件时,经过 babel 的代码和输入是相同的。

Babel 的插件分为两种: 语法插件和转换插件

1. 当我们添加语法插件之后,在解析这一步就使得 babel 能够解析更多的语法。(顺带一提,babel 内部使用的解析类库叫做 babylon,并非 babel 自行开发)

举个简单的例子,当我们定义或者调用方法时,最后一个参数之后是不允许增加逗号的,如callFoo(param1, param2,)就是非法的。如果源码是这种写法,经过 babel 之后就会提示语法错误。

但最近的 JS 提案中已经允许了这种新的写法(让代码 diff 更加清晰)。为了避免 babel 报错,就需要增加语法插件babel-plugin-syntax-trailing-function-commas

2. 当我们添加转译插件之后,在转换这一步把源码转换并输出。这也是我们使用 babel 最本质的需求。

比起语法插件,转译插件其实更好理解,比如箭头函数(a) => a就会转化为function (a) {return a}。完成这个工作的插件叫做babel-plugin-transform-es2015-arrow-functions。

同一类语法可能同时存在语法插件版本和转译插件版本。如果我们使用了转译插件,就不用再使用语法插件了。


配置插件

既然插件是 babel 的根本,那如何使用呢?总共分为 2 个步骤:

1. 将插件的名字增加到配置文件中 (根目录下创建 .babelrc 或者 package.json 的babel里面,格式相同)

2. 使用npm install babel-plugin-xxx进行安装


preset

比如 es2015 是一套规范,包含大概十几二十个转译插件。如果每次要开发者一个个添加并安装,配置文件很长不说,npm install的时间也会很长,更不谈我们可能还要同时使用其他规范呢。

为了解决这个问题,babel 还提供了一组插件的集合。因为常用,所以不必重复定义 & 安装。(单点和套餐的差别,套餐省下了巨多的时间和配置的精力)

preset 分为以下几种:

1. 官方内容,目前包括 env, react, flow, typescript等。这里最重要的是 env,后面会详细介绍。

2. stage-x,这里面包含的都是当年最新规范的草案,每年更新。

这里面还细分为

Stage 0 - 稻草人: 只是一个想法,经过 TC39 成员提出即可。

Stage 1 - 提案: 初步尝试。

Stage 2 - 初稿: 完成初步规范。

Stage 3 - 候选: 完成规范和浏览器初步实现。

Stage 4 - 完成: 将被添加到下一年度发布。

例如syntax-dynamic-import就是 stage-2 的内容,transform-object-rest-spread就是 stage-3 的内容。

此外,低一级的 stage 会包含所有高级 stage 的内容,例如 stage-1 会包含 stage-2, stage-3 的所有内容。

stage-4 在下一年更新会直接放到 env 中,所以没有单独的 stage-4 可供使用。

3. es201x, latest

这些是已经纳入到标准规范的语法。例如 es2015 包含arrow-functions,es2017 包含syntax-trailing-function-commas。但因为 env 的出现,使得 es2016 和 es2017 都已经废弃。所以我们经常可以看到 es2015 被单独列出来,但极少看到其他两个。

latest 是 env 的雏形,它是一个每年更新的 preset,目的是包含所有 es201x。但也是因为更加灵活的 env 的出现,已经废弃


执行顺序

很简单的几条原则:

-Plugin 会运行在 Preset 之前。

-Plugin 会从前到后顺序执行。

-Preset 的顺序则刚好相反(从后向前)。

preset 的逆向顺序主要是为了保证向后兼容,因为大多数用户的编写顺序是['es2015', 'stage-0']。这样必须先执行stage-0才能确保 babel 不报错。因此我们编排 preset 的时候,也要注意顺序,其实只要按照规范的时间顺序列出即可。


插件和 preset 的配置项

简略情况下,插件和 preset 只要列出字符串格式的名字即可。但如果某个 preset 或者插件需要一些配置项(或者说参数),就需要把自己先变成数组。第一个元素依然是字符串,表示自己的名字;第二个元素是一个对象,即配置对象。

最需要配置的当属 env,如下:

"presets": [

    // 带了配置项,自己变成数组

    [

        // 第一个元素依然是名字

        "env",

        // 第二个元素是对象,列出配置项

        {

          "module": false

        }

    ],

    // 不带配置项,直接列出名字

    "stage-2"

]


babel将es6+(指es6及以上版本)分为

语法层: let、const、class、箭头函数等,这些需要在构建时进行转译,是指在语法层面上的转译,(比如class...将来会被转译成var function...)

api层:Promise、includes、map等,这些是在全局或者Object、Array等的原型上新增的方法,它们可以由相应es5的方式重新定义

插件(用于处理语法层)

polyfill(用于处理api层)


polyfill(用于处理api层)

polyfill的中文意思是垫片,顾名思义就是垫平不同浏览器或者不同环境下的差异,让新的内置函数、实例方法等在低版本浏览器中也可以使用。

如何使用:

首先,安装@babel/polyfill依赖。注意这是一个运行时依赖,所以不要加-dev

npm install --save @babel/polyfill

@babel/polyfill模块包括core-js和一个自定义的regenerator runtime模块,可以模拟完整的 ES2015+ 环境。

polyfill

虽然看起来Promise还是没有转译,但是我们引入的 polyfill 中已经包含了对Promise的es5的定义,所以这时候代码便可以在低版本浏览器中运行了。


useBuiltIns属性(deprecated)

代码里边只用到了几个es6,却需要引入所有的垫片,要优化这一点,就需要用到useBuiltIns这个属性了。

useBuiltIns这一配置项,它的值有三种:

false: 不对polyfills做任何操作

entry: 根据target中浏览器版本的支持,将polyfills拆分引入,仅引入有浏览器不支持的polyfill

usage(新):检测代码中ES6/7/8等的使用情况,仅仅加载代码中用到的polyfills

//.babelrc

{

  "presets": [

      ["@babel/preset-env",{

            "useBuiltIns": "usage",

            "corejs":3

      }]

  ]

}


@babel/plugin-transform-runtime解决代码冗余

写一个语法层面的es6-->class(let,const这些对比不出问题,所以用class),看看babel会把class转化成什么

这种结果看起来没什么毛病,但事实上,如果其他文件中也使用了class,_classCallCheck在每个文件中都会出现,这就造成了代码冗余(示例中只使用了class,可能冗余的不明显,实际项目中这些函数可能是很长的)。

这时候需要用到另外一个插件了@babel/plugin-transform-runtime。该插件会开启对 Babel 注入的辅助函数(比如上边的_classCallCheck)的复用,以节省代码体积,这些辅助函数在@babel/runtime中,所以需要安装@babel/runtime,当然@babel/runtime也是运行时依赖。(在对一些语法进行编译的时候,babel需要借助一些辅助函数)

安装@babel/plugin-transform-runtime和@babel/runtime

npm install --save-dev @babel/plugin-transform-runtime

npm install --save @babel/runtime

可以发现,相关的辅助函数是以require的方式引入而不是被直接插入进来的,这样就不会冗余了。

除了解决代码冗余,@babel/plugin-transform-runtime还有另外一个重要的能力——解决全局污染。


解决全局污染

全局污染是出现在转译api层出现的问题

这次依然用Promise来做实验

可以看出preset-env在处理例如Promise这种的api时,只是引入了core-js中的相关的js库,这些库重新定义了Promise,然后将其挂载到了全局。

这里的问题就是:必然会造成全局变量污染,同理其他的例如Array.from等会修改这些全局对象的原型prototype,这也会造成全局对象的污染。

解决方式就是:将core-js交给transform-runtime处理。添加一个配置即可,非常简单

{

    "presets": [

        ["@babel/preset-env"]

    ],

    "plugins": [

        ["@babel/plugin-transform-runtime",{

            "corejs":3

        }]

    ]

}

可以看出,我们是将core-js这个属性添加到@babel/plugin-transform-runtime这个插件的配置下,让这个插件处理,同时也不需要配置useBuiltIns了,因为在babel7中已经将其设置为默认值(transform-runtime是利用plugin自动识别并替换代码中的新特性,检测到需要哪个就用哪个)

转译结果

可以看出,这时的代码并没有在全局直接添加一个Promse,而是定义了一个_promise["default"]方法,这样便不会出现全局变量污染的情况

所以综上可得出@babel/plugin-transform-runtime这个插件的强大之处有以下几点:

-实现对辅助函数的复用,解决转译语法层时出现的代码冗余

-解决转译api层出现的全局变量污染

但是transform-runtime也有缺点:

-每个特性都会经历检测和替换,随着应用增大,可能会造成转译效率不高

你可能感兴趣的:(babel)