kd-cloud-web / Blog

一群人, 关于前端, 做一些有趣的事儿
13 stars 1 forks source link

关于Babel #38

Open Luin-Li opened 4 years ago

Luin-Li commented 4 years ago

关于Babel

Babel 编译的三个阶段

Babel 的编译过程可以分为三个阶段:

1. 解析(Parsing)

使用 babylon 解析器对输入的源代码字符串进行解析并生成初始 AST。整个解析过程分为两个步骤:1)词法分析(lexical analyzer或scanner):将整个代码字符串分割成语法单元数组在线分词工具。2)语法分析(syntax analyzer):它会将词法分析出来的数组转化成树形的表达形式。同时,验证语法,语法如果有错的话,抛出语法错误。

babel 内部使用的解析类库叫做 babylon,并非 babel 自行开发。

简单来说语法分析是对语句和表达式识别,这是个递归过程,在解析中,Babel 会在解析每个语句和表达式的过程中设置一个暂存器,用来暂存当前读取到的语法单元,如果解析失败,就会返回之前的暂存点,再按照另一种方式进行解析,如果解析成功,则将暂存点销毁,不断重复以上操作,直到最后生成对应的语法树。

*可以通过astexplorer.net 网站在线生成抽象语法树AST*

2. 转换(Transformation)

babel中最核心的是 babel-core ,它向外暴露出 babel.transform 接口,遍历 AST 树并应用各 transformers(plugin)。

const babel = require('babel-core');
let result = babel.transform(code, {
    presets: ['env'],
    plugins: ['transform-runtime']
})

3. 生成(Code Generation) 利用 babel-generator 将 AST 树生成转码后的代码

Babel使用

将插件的名字增加到配置文件中 (根目录下创建.babelrc或者 package.json的babel中),然后npm install babel-plugin-xxx进行安装。 示例:

{
  "presets": [
    ["env", // 带了配置项,自己变成数组。第一个元素依然是名字
      {  // 第二个元素是对象,列出配置项
        "modules": false,
        "targets": {
          "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
        }
      }
    ],
    // 不带配置项,直接列出名字
    "stage-2"
  ],
  "plugins": [
    "transform-vue-jsx", 
    "transform-runtime",
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ],
  "env": {
    // 基于环境来配置 Babel 
    "test": {
      "presets": ["env", "stage-2"],
      "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
    }
  }
}

env:Babel 将根据当前环境来开启 env 下的配置。当前环境可以使用 process.env.BABEL_ENV 来获得。 如果 BABEL_ENV 不可用,将会替换成 NODE_ENV,并且如果后者也没有设置,那么缺省值是development

Presets

因为一套类似es2015的规范,包含了很多个转译插件。为了每次要开发者一个个添加并安装转译插件问题,babel提供了一组插件的集合。理解为单点和套餐的差别

preset分为以下几种:

env配置参数

语法可以参考browserslist

执行顺序

其他工具

其他相关工具

polyfill和runtime差别

背景: Babel 把 Javascript 语法 分为 syntax 和 api:

api指那些我们可以通过 函数重新覆盖的语法 ,类似 includes,map,includes,Promise,凡是我们能想到重写的都可以归属到 api

syntax像箭头函数,let,const,class, 依赖注入Decorators,等等这些,我们在 Javascript 在运行是无法重写的,想象下,在不支持的浏览器里不管怎么样,你都用不了 let 这个关键字

babel 默认只转换syntax层语法,而不转换新的 API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转码。对于API, Babel 把这个放在了 单独放在了 polyfill 这个模块处理。

polyfill(垫片)

使用时,在所有代码运行之前增加 require('babel-polyfill')。或者更常规的操作是在 webpack.config.js 中将 babel-polyfill 作为第一个 entry。

babel-polyfill主要有两个缺点:

改进方案: 使用useBuiltIns设置:取值可以是usage(按需引入), entry(全部引入,会根据browserlist 过滤出需要的polyfill)和false。默认值为falseBabel 7该配置才会生效

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage"
      }
    ]
  ]
}

使用上面的配置,打包后如下: polyfill打包

runtime

为了解决babel-polyfill会污染全局空间的问题,有了babel-plugin-transform-runtimebabel-plugin-transform-runtime的目的是为您的代码创建沙盒环境,不会污染全局空间和内置对象原型。它适用于开发供其他人使用的库,或者如果您无法准确控制代码运行的环境。

{
  "presets": [
    [
      "env"
    ]
  ],
  "plugins": ["transform-runtime"]
}

babel配置如上,打包后的结果: runtime例子

例如,你的代码中用了Promise,babel-plugin-transform-runtime会自动重写你使用Promise的代码,转换为使用babel-runtime导出(export)的Promise-like对象。 所以plugin-transform-runtime一般用于开发(devDependencies),而runtime自身用于部署的代码(dependencies),两者配合来一起工作。

babel-runtime内部集成了:

babel-runtime缺点: babel-plugin-transform-runtime不支持实例方法 (例如 [1,2,3].includes(1)),这时还是需要使用babel-polyfill

*总结:Babel 只是转换syntax层语法,所有需要 @babel/polyfill 来处理API兼容,又因为 polyfill 体积太大,所以通过 preset的 useBuiltIns 来实现按需加载,再接着为了满足 npm 组件开发的需要 出现了@babel/runtime 来做隔离。*

Babel插件开发流程

开发流程

Babel的插件模块需要你暴露一个function,function内返回visitor,visitor是对各类型的AST节点做处理的地方。类似这样:

module.export = function(babel){
  return {
    visitor: {
    //   ...你的插件代码
    }
  }
}

通过AST explorer知道要处理的AST节点,如下: ast示例

基本流程:

  1. 定义需要转换的节点
    BinaryExpression(path) {
    ...
    }
  2. 在node节点上找到替换的节点(通过babel-types)
    var t = require('babel-types');
    if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) {
    ...
    }
  3. 创建用来替换的节点(通过babel-types),replaceWith(替换)
    var t = require('babel-types');
    ...
    path.replaceWith(t.numericLiteral(result));

【参考】 前端工程师需要了解的 Babel 知识 一口(很长的)气了解 babel Babel学习系列4-polyfill和runtime差别(必看) @babel/polyfill 与 @babel/plugin-transform-runtime 详解 你真的会用 Babel 吗? 了解 Babel 6 & 7 生态 深入浅出的webpack构建工具---babel之配置文件.babelrc(三)