阅读 307

babel简单了解

文章基于babel7.4.0及以上版本(目前最新7.5.0),大概记录描述对babel的流程分析,使用了解。

文章概要:

  • 1.需要了解的基本概念:babel, Plugins, Presets
  • 2.简单了解 core-js
  • 3.简单了解@babel/preset-env 与core-js@3
  • 4.简单了解@babel/plugin-transform-runtime与core-js@3
  • 5.思考,总结。

1.需要了解的基本概念:babel, Plugins, Presets, And Config

1.1 什么是babel

资料:babel官网Babel Plugin Handbook

(1)概述:

Babel是JavaScript的通用多用途编译器,特别是源到源编译器,通常称为“转换器”。 这意味着为Babel提供了一些JavaScript代码,Babel修改了代码,并生成了新代码。主要的用途是将ECMAScript 2015+代码转换为当前和旧版浏览器环境中向后兼容JavaScript版本。

(2)功能:

Babel的三个主要阶段是parse(解析代码),Transform(转换节点),generate(生成新代码)。

input string ->parser(@babel/parser )-> AST -> transformer[s] -> AST ->generate(@babel/generator )-> output string 

  • 解析 (parse):解析阶段,获取代码并输出AST树(ast对象)。 在Babel中有两个解析阶段:词法分析和句法分析。
  • 转换 (Transform):  转换阶段获取AST遍历AST,添加,更新和删除AST节点,这也是插件plugins作用运行的阶段。(The transformer[s]: All the plugins/presets ,它们都使用@babel/traverse 提供遍历ast节点的功能。)(@ babel / types 包含着ast中的所有类型,用于验证,构建和更改AST节点,从而改变ast的内容,@ babel / template是一个辅助函数,允许从代码的字符串表示中构造AST节点。)
  • 生成( generate):代码生成阶段采用最终的AST并将其转换回一串代码,同时创建源映射。


相关模块

@babel/parser 将源代码解析成 AST。

@babel/template : 主要用途是为parser提供模板引擎,更加快速的转化成AST

@babel/traverse : 用于对 AST 的遍历,维护了整棵树的状态,并且负责替换、移除和添加节点。

@babel-types: 用于 AST 节点的 Lodash 式工具库, 它包含了构造、验证以及变换 AST 节点的方法,对编写处理 AST 逻辑非常有用

@babel/generator : 代码生成阶段采用最终的AST并将其转换回一串代码,同时创建源映射。


(3)补充说明 AST:

查看AST Explorer更好地了解AST节点。(Babel Plugin Handbook  有对AST,Babel 处理代码 input 到output流程做介绍)

源码:

function square(n) {
  return n * n;
}复制代码

转换后ast:

{
  "type": "Program",
  "start": 0,
  "end": 39,
  "body": [
    {
      "type": "FunctionDeclaration",
      "start": 0,
      "end": 38,
      "id": {
        "type": "Identifier",
        "start": 9,
        "end": 15,
        "name": "square"
      },
      "expression": false,
      "generator": false,
      "async": false,
      "params": [
        {
          "type": "Identifier",
          "start": 16,
          "end": 17,
          "name": "n"
        }
      ],
      "body": {
        "type": "BlockStatement",
        "start": 19,
        "end": 38,
        "body": [
          {
            "type": "ReturnStatement",
            "start": 23,
            "end": 36,
            "argument": {
              "type": "BinaryExpression",
              "start": 30,
              "end": 35,
              "left": {
                "type": "Identifier",
                "start": 30,
                "end": 31,
                "name": "n"
              },
              "operator": "*",
              "right": {
                "type": "Identifier",
                "start": 34,
                "end": 35,
                "name": "n"
              }
            }
          }
        ]
      }
    }
  ],
  "sourceType": "module"
}复制代码

1.2 什么是Plugins

资料:plugins

Babel is a compiler (source code => output code). Like many other compilers it runs in 3 stages: parsing, transforming, and printing.

Now, out of the box Babel doesn't do anything. It basically acts like const babel = code => code; by parsing the code and then generating the same code back out again. You will need to add plugins for Babel to do anything.

(1)概述:

Babel是一个编译器(源代码=>输出代码)。 像许多其他编译器一样,它分三个阶段运行:解析,转换和打印。 现在开箱即用的Babel什么都不做。 它基本上像const babel = code => code; 通过解析代码,然后再次生成相同的代码。 此时需要为babel添加plugins去transformer[s]节点,生成需要的代码 。

插件运行代码,您的代码会转换为新代码,如下: const babel = code => babelPluginB(babelPluginA(code))

(2)使用配置:

{
  "plugins": ["pluginA", "pluginB", ["pluginC", descriptionC],"pluginD"]
   //["pluginC", descriptionC] descriptionC 是pluginC 的配置选项potions
}复制代码

  • 插件在预设(presets)之前运行。 
  • 插件排序是从左到右。(pluginA,pluginB,pluginC, pluginD)

1.3 什么是Presets

资料:Presets

(1)概述

Don't want to assemble your own set of plugins? No problem! Presets can act as an array of Babel plugins or even a sharable options config.

presets 是Babel插件(plugin)的数组集合,甚至可以共享的选项配置。

(2)使用配置:

{
  "presets": ["presetsA", ["presetsB"], ["presetsC", descriptionC],"presetsD"]   
   //["presetsC", descriptionC] descriptionC 是presetsC 的配置选项potions
}复制代码

presets 的顺序是从右到左(presetsD,presetsC,presetsB,presetsA

1.4 repl 调试

repl调试熟悉代码不同present,plugin 的功能作用,以及browserslist 对代码转换的影响,在最左边界面勾选present,plugin,浏览器环境,中间部分输入源码,右边即可得到转换后的代码。


2.简单了解 core-js

资料:core-js 文档

2.1什么是core.js了? 


大概翻译

它是JavaScript标准库的polyfill,它支持: 

  • 最新的ECMAScript标准。 
  • ECMAScript标准库提案。
  •  一些WHATWG / W3C标准(跨平台或密切相关的ECMAScript)。
它是最大程度上模块化的,你可以轻松选择仅加载你将使用的功能。

可以在不污染全局命名空间的情况下使用它。 

它与babel紧密集成:这允许对core-js导入进行许多优化。

2.2 core-js@3

为啥要讨论core-js@3?


core-js:即使需要反映提案中的变更,core-js也只会在主要版本中发生重大变化。

core-js @ 2:2015年前进入功能冻结; 所有新功能仅添加到core-js @ 3分支。

core-js @ 3:的详细变化查看core-js @ 3有什么变化?

对于本文,目前我们清楚core-js 是一个JavaScript标准库的polyfill就够了。

2.3core-js 与@babel/polyfill

资料:@babel/polyfill,官网@babel/polyfill

@babel/polyfill IS just the import of stable core-js features and regenerator-runtime for generators and async functions, so if you load @babel/polyfill - you load the global version of core-js without ES proposals. Now it's deprecated in favour of separate inclusion of required parts of core-js and regenerator-runtime and, for preventing breaking changes, left on core-js@2.

所以如加载@ babel / polyfill 就等于

import 'core-js/stable';
import 'regenerator-runtime/runtime';复制代码

7.4.0 后@ babel / polyfill 废除升级(新版本不建议如此使用,做好使用@babel/preset-env的配置来引入 polyfill)


3.简单了解@babel/preset-env 与core-js@3

3.1 @babel/preset-env 是啥?

@babel/preset-env is a smart preset that allows you to use the latest JavaScript without needing to micromanage which syntax transforms (and optionally, browser polyfills) are needed by your target environment(s). This both makes your life easier and JavaScript bundles smaller!

 @babel/preset-env是Babel预设插件的集合,允许您使用最新的JavaScript,而无需微观管理您的目标环境需要哪些语法转换(以及可选的浏览器polyfill)

意思就是:可以根据配置的浏览器环境,转换语法,以及按需要增加polyfill

3.2 @babel/preset-env 使用(非常有意思)

安装

npm install --save-dev @babel/core @babel/cli @babel/preset-env
npm install --save core-js@3复制代码

配置("corejs","useBuiltIns",的配置可以细看文档,十分重要也挺有意思的,@ babel / preset-env完全依赖于compat-table数据(类似于一个列表,标志什么功能在对应的浏览器的兼容性)来确定需要为特定环境加载哪些polyfill,所以浏览器环境参数很重要

{
  "presets": [
    [
      "@babel/preset-env", {
        "debug": true, // 用于调试模式 方便展示
        "corejs": 3, // 使用 core-js@3 作为polyfill 库        
        "useBuiltIns": "usage", // 此选项配置@ babel / preset-env处理polyfill的方式。
        targets: { // 此处浏览器环境 最好配置到文件 browserslist 中共享。            
          edge: "17",
          ie: "10",
          firefox: "60",
          chrome: "65",
          safari: "9.1",
        }
      }
    ]
  ],
  plugins: ['@babel/plugin-proposal-class-properties']
}复制代码

a.js

class B {  b = 1}
console.log(123)export default '123'复制代码

index.js

let str1 = require('./a.js').default
class A {  a=1;}
var s = new Promise((resolve,reject)=> {  resolve(1)})
let a = new A()
var foobar = '12312313foo'foobar.includes("foo")
console.log(a)
console.log(str1)复制代码

运行结果:

"debug": true, // 用于调试模式 方便展示,控制台会输出用了哪些plugin,每个文件加了哪些polyfills。


编译后的代码:


可以根据配置的浏览器环境,转换语法,以及按需要增加polyfill,@babel/preset-env使用起来非常棒了。

问题①:

Babel uses very small helpers for common functions such as _extend. By default this will be added to every file that requires it. This duplication is sometimes unnecessary, especially when your application is spread out over multiple files.

Babel使用非常小的帮助函数来执行常见功能,例如_extend。 默认情况下,这将添加到需要它的每个文件中。 这种重复有时是不必要的,尤其是当应用程序分布在多个文件上时。(图中蓝色部分,展示的效果),在每个文件重复注入帮助函数_classCallCheck,_defineProperty。

问题②:因为引用的core-js且没有别名(图中粉色部分) 会污染全局。(查看图片详细Packages, entry points and modules names


4.简单了解@babel/plugin-transform-runtime与core-js@3

4.1 @babel/plugin-transform-runtime 是啥?

资料:@babel/plugin-transform-runtime

A plugin that enables the re-use of Babel's injected helper code to save on codesize.

一个插件,可以将重复使用的babel代码转换为包模块引用从而节省代码。(Regenerator aliasing, core-js aliasing,Helper aliasing)

资料:Technical details

The transform-runtime transformer plugin does three things:

  • Automatically requires @babel/runtime/regenerator when you use generators/async functions (toggleable with the regenerator option).
  • Can use core-js for helpers if necessary instead of assuming it will be polyfilled by the user (toggleable with the corejs option)
  • Automatically removes the inline Babel helpers and uses the module @babel/runtime/helpers instead (toggleable with the helpers option).

What does this actually mean though? Basically, you can use built-ins such as Promise, Set, Symbol, etc., as well use all the Babel features that require a polyfill seamlessly, without global pollution, making it extremely suitable for libraries.

Make sure you include @babel/runtime as a dependency.


  • 使用generator / async函数时自动按需加载@ babel / runtime / regenerator(可以使用regenerator选项进行切换
  • 如果需要polyfill函数,可以将core-js用于提供polyfill函数,而不是假设polyfill将由用户提供(使用corejs选项进行切换)
  • 自动删除内联Babel帮助函数并使用模块@ babel / runtime / helpers(使用helpers选项进行切换)。
  • 可以使用Promise,Set,Symbol等内置函数,以及使用所有需要polyfill的Babel功能,而不会造成全局污染,使其非常适合工具库。(如果之前遇到过 无法使用某些实例方法比如"foobar".includes("foo"),看到这个功能变化应该很激动)

4.2 @babel/plugin-transform-runtime 使用

在3.1安装基础上继续安装

npm install --save-dev @babel/core @babel/cli @babel/plugin-transform-runtime
npm install --save @babel/runtime(可选择其它,下文会说明)复制代码

配置修改:

{
  test: /\.js$/,
  use: {
    loader: 'babel-loader',
    options: {
      presets: [
        ['@babel/preset-env', {
          targets: {
            edge: "17",
            ie: "10",
            firefox: "60",
            chrome: "65",
            safari: "9.1",
          },
          "corejs": 3,
          useBuiltIns: "usage",
          "debug": true,
          modules: false
        }]
      ],
      plugins: ['@babel/plugin-proposal-class-properties', 
      ['@babel/plugin-transform-runtime', { // options 配置可查看文档
        "absoluteRuntime": false,
        "corejs": false,
        "helpers": true,
        "regenerator": false,
        "useESModules": false,
      }]]
    }
  },
  include: path.resolve(__dirname, 'src'),
  exclude: /node_modules/
}复制代码

代码编译结果:


如图中画圈部分,自动删除内联Babel帮助函数并使用模块@ babel / runtime / helpers

4.3@babel/plugin-transform-runtime与@babel/runtime和core-js@3的使用

(1)@babel/runtime 

@ babel / runtime是一个包含Babel模块化运行时帮助函数和一个regenerator-runtime的库。

简单描述,@babel/plugin-transform-runtime删除内联Babel帮助函数并使用模块引入,提供模块的包就是@babel/runtime 

(2)使用
安装:

npm install --save-dev @babel/core @babel/cli @babel/plugin-transform-runtime
npm install --save @babel/runtime-corejs3复制代码

修改配置:此时将core-js@3的使用配置放在了@babel/plugin-transform-runtime 的option中

 {
   test: /\.js$/,
   use: {
     loader: 'babel-loader',
     options: {
       presets: [
         ['@babel/preset-env', {
           targets: {
             edge: "17",
             ie: "10",
             firefox: "60",
             chrome: "65",
             safari: "9.1",
           },
           modules: false
         }]
       ],
       plugins: ['@babel/plugin-proposal-class-properties', 
       ['@babel/plugin-transform-runtime', {
         "absoluteRuntime": false,
         "corejs": 3, // 注意此处配置
         "helpers": true,
         "regenerator": false,
         "useESModules": false,
       }]]
     }
   },
   include: path.resolve(__dirname, 'src'),
   exclude: /node_modules/
 }复制代码

代码结果:


此时发现代码中也增加了需要的polyfill,而且还是 aliasing没有全局命名空间污染(core-js aliasing和Helper aliasing 功能,同时也可以比较下3.2的代码截图是有所不同的)

截取了图中_babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_0___default的引用来源./node_modules/@babel/runtime-corejs3/core-js-stable/instance/includes.js

/* harmony import */ var _babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime-corejs3/core-js-stable/instance/includes */ "./node_modules/@babel/runtime-corejs3/core-js-stable/instance/includes.js");
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_0__);
var foobar = '12312313foo';
_babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_0___default()(foobar).call(foobar, "foo");复制代码

在向上./node_modules/@babel/runtime-corejs3/core-js-stable/instance/includes.js 引用了./node_modules/core-js-pure/stable/instance/includes.js 如图实际引用的是core-js-pure


@babel/runtime with corejs3 option 能导入不污染全局的polyfill函数 功能也十分强大了。

它存在的问题是@babel/runtime for target environment

Currently, we can't set the target environment as @babel/runtime like we can do for @babel/preset-env. That means that @babel/runtime injects all possible polyfills even when targeting modern engines: it unnecessarily increases the size of the final bundle.

Since core-js-compat contains all the necessary data, in the future, it will be possible to add support for compiling for a target environment to @babel/runtime and to add a useBuiltIns: runtime option to @babel/preset-env.

意思一加载就会加载所有的polyfill 不会参考浏览器兼容性。

5.思考,总结

@babel/preset-env 和@babel/plugin-transform-runtime 的option配置参数只提及部分例子比较局限粗糙。大部分内容主要是梳理文档总结,理解不准确之处,还请教正。

资料:babel官网Babel Plugin Handbookcore-js

工具:AST Explorerrepl