babel插件的相关知识

时间: 2019-09-24阅读: 183标签: 插件

一、babel介绍

Babel 是 JavaScript 编译器,更确切地说是源码到源码的编译器,通常也叫做“转换编译器(transpiler)”。 意思是说你为 Babel 提供一些 JavaScript 代码,Babel 更改这些代码,然后返回给你新生成的代码。在这个源码到源码的转换过程当中,抽象语法树,即AST,起到了重要的作用。


二、抽象语法树(AST)

抽象语法树(Abstract Syntax Tree)也称为AST语法树,指的是源代码语法所对应的树状结构。也就是说,一种编程语言的源代码,通过构建语法树的形式将源代码中的语句映射到树中的每一个节点上。和我们的常说的虚拟DOM有点像,虚拟dom是用json的格式把DOM结构抽象成js对象,用于描述DOM的结构,每个节点的类型、属性等等,类似的AST是把js的源代码抽象为js对象的格式,以方便描述这段代码的语法。程序代码本身被映射成为一棵语法树,通过操纵语法树,我们能够精准的获得程序代码中的每一个精确的节点。例如声明语句,赋值语句等。


三、babel的处理步骤

Babel 的三个主要处理步骤分别是: 解析(parse)转换(transform)生成(generate)

解析步骤接收代码并输出 AST

转换步骤接收 AST 并对其进行遍历,在此过程中对节点进行添加、更新及移除等操作。 这是 Babel 或是其他编译器中最复杂的过程,同时也是插件将要介入工作的部分。在babel-loader中有两种方式可以配置babel插件,我们经常会配置:

rules: [
  {
    test: /\.(js|jsx)$/,
    loader: 'babel-loader',
    exclude: /node_modules/,
    options: {
      presets: ["env", 'stage-0'],
      plugins: [
        ["extract", { "library": "lodash" }],
        ["transform-runtime", {}]
      ]
    },
  }
]

注意:plugins 的插件使用顺序是顺序的,而 preset 则是逆序的。所以上面的执行方式是extract>transform-runtime>stage-0>env

代码生成步骤把最终(经过一系列转换之后)的 AST 转换成字符串形式的代码,同时还会创建源码映射(source maps)。代码生成比较简单,就是深度优先遍历整个 AST,然后构建可以表示转换后代码的字符串。

有了以上的基础知识之后我们看看babel插件是怎么编写的。


四、编写一个简单的babel插件

1、插件格式

先从一个接收了当前babel对象作为参数的function开始。

export default function(babel) {
  // plugin contents
}

我们经常会这样写:

export default function({ types: t }) {
    //
}

接着返回一个对象,其 visitor 属性是这个插件的主要访问者。

export default function({ types: t }) {
  return {
    visitor: {
      // visitor contents
    }
  };
};

Visitor 中的每个函数接收2个参数:path 和 state;

export default function({ types: t }) {
  return {
    visitor: {
      Identifier(path, state) {},
      ASTNodeTypeHere(path, state) {}
    }
  };
};

Path 是表示两个节点之间连接的对象。表示AST中节点之间的相互关联关系。例如,如果有下面这样一个节点及其子节点︰

{
  type: "FunctionDeclaration",
  id: {
    type: "Identifier",
    name: "square"
  },
  ...
}

将子节点 Identifier 表示为一个路径(Path)的话,看起来是这样的:

{
  "parent": {
    "type": "FunctionDeclaration",
    "id": {...},
    ....
  },
  "node": {
    "type": "Identifier",
    "name": "square"
  }
}

我们发现path除了含有节点之间的关系之外,同时它还包含关于该路径的其他元数据:

{
  "parent": {...},
  "node": {...},
  "hub": {...},
  "contexts": [],
  "data": {},
  "shouldSkip": false,
  "shouldStop": false,
  "removed": false,
  "state": null,
  "opts": null,
  "skipKeys": null,
  "parentPath": null,
  "context": null,
  "container": null,
  "listKey": null,
  "inList": false,
  "parentKey": null,
  "key": null,
  "scope": null,
  "type": null,
  "typeAnnotation": null
}

2、写一个简单的插件

我们写一个简单的插件,把所有定义变量名为a的换成b, 先从astexplorer看下var a = 1的 AST

{
  "type": "Program",
  "start": 0,
  "end": 10,
  "body": [
    {
      "type": "VariableDeclaration",
      "start": 0,
      "end": 9,
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 4,
          "end": 9,
          "id": {
            "type": "Identifier",
            "start": 4,
            "end": 5,
            "name": "a"
          },
          "init": {
            "type": "Literal",
            "start": 8,
            "end": 9,
            "value": 1,
            "raw": "1"
          }
        }
      ],
      "kind": "var"
    }
  ],
  "sourceType": "module"
}

从上面可以看出我们要找的节点类型是VariableDeclarator,以下是我们写出的插件代码:

export default function({ types: t }) {
  return {
    visitor: {
      VariableDeclarator(path, state) {
        if (path.node.id.name == 'a') {
          path.node.id = t.identifier('b')
        }
      }
    }
  }
}

我们要把id属性是 a 的替换成 b 就好了。但是这里不能直接path.node.id.name = 'b'。如果操作的是object,就没问题,但是这里是 AST 语法树,所以想改变某个值,就是用对应的 AST 来替换,现在我们用新的标识符来替换这个属性。

我们用@babel/core中的transform方法对我们写的插件进行测试一下:

import * as babel from '@babel/core';
const c = `var a = 1`;


const { code } = babel.transform(c, {
  plugins: [
    function({ types: t }) {
      return {
        visitor: {
          VariableDeclarator(path) {
            if (path.node.id.name == 'a') {
              path.node.id = t.identifier('b')
            }
          }
        }
      }
    }
  ]
})


console.log(code); // var b =

3、实现一个简单的按需打包的功能

例如我们要实现把import { Button } from 'antd'转成import Button from 'antd/lib/button'

通过对比 AST 发现,specifiers里的type和source不同。

// import { Button } from 'antd'
"specifiers": [
    {
        "type": "ImportSpecifier",
        ...
    }
],
"source": {
  "type": "Literal",
  "start": 23,
  "end": 29,
  "value": "antd",
  "raw": "'antd'"
}

 

// import Button from 'antd/lib/button'
"specifiers": [
    {
        "type": "ImportDefaultSpecifier",
        ...
    }
],
"source": {
  "type": "Literal",
  "start": 19,
  "end": 36,
  "value": "antd/lib/button",
  "raw": "'antd/lib/button'"
}

 

import * as babel from '@babel/core';
const c = `import { Button } from 'antd'`;


const { code } = babel.transform(c, {
  plugins: [
    function({ types: t }) {
      return {
        visitor: {
          ImportDeclaration(path) {
            const { node: { specifiers, source } } = path;
            if (!t.isImportDefaultSpecifier(specifiers[0])) { // 对 specifiers 进行判断
              const newImport = specifiers.map(specifier => (
                t.importDeclaration(
                  [t.ImportDefaultSpecifier(specifier.local)],
                  t.stringLiteral(`${source.value}/lib/${specifier.local.name}`)
                )
              ))
              path.replaceWithMultiple(newImport)
            }
          }
        }
      }
    }
  ]
})
console.log(code); // import Button from "antd/lib/Button"


五、总结

我们介绍了babel的功能、babel工作的三个阶段以及怎么写一个简单的插件,写插件主要用到了transform这个函数,插件的的函数返回一个对象,visitor是这个插件的访问者,通过它来访问AST,在visitor里定义一些函数做相关的操作。如果想了解更多有关babel插件的知识可访问babel插件手册

吐血推荐

1.站长广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入...

2.休闲娱乐: 直播/交友    优惠券领取   网页游戏   H5游戏

链接: http://www.fly63.com/article/detial/6115

揭秘webpack plugin

Plugin(插件) 是 webpack 生态的的一个关键部分。它为社区提供了一种强大的方法来扩展 webpack 和开发 webpack 的编译过程。这篇文章将尝试探索 webpack plugin

jquery.validate验证

jquery validate插件为表单提供了强大的验证功能,让客户端表单验证变得更简单,同时提供了大量的定制选项,满足应用程序各种需求

Vue中的JS与Velocity.js的结合

JS动画效果,注意事件函数中所传递的传递的参数及某些事件函数返回的函数1.进入动画钩子:before-enter,enter,after-enter;2.离开动画钩子:before-leave,leave,after-leave;3.在enter钩子中的函数调用done

jQuery图片剪裁插件Cropper.js的使用

1.引入必要的js和css核心文件;2.构建html,可以将图片或canvas直接包裹到一个块级元素中。small时预览框;3.初始化插件及相关参数

Vue添加引入jquery插件

先安装jquer插件,命令运行: npm i jquery --save-dev ;利用 expose-loader 模块 来引用jquery插件 ,命令运行: npm i expose-loade --save-dev;配置webpack.base.conf

Webpack插件开发简要

如今‘大前端’这个概念在前端界大热,说‘大前端’,我们就要提到‘前后端分离’,‘前后端分离’又离不开‘本地开发构建’,‘本地开发构建’自然离不开webpack,webpack想要工作,那它就需要各种插件的支持,O(∩_∩)O哈哈~,逗了一大圈,终于引出了主题

DefinePlugin插件用法理解

DefinePlugin 允许创建一个在编译时可以配置的全局常量。这可能会对开发模式和生产模式的构建允许不同的行为非常有用。如果在开发构建中,而不在发布构建中执行日志记录,则可以使用全局常量来决定是否记录日志。这就是 DefinePlugin 的用处

10款好用到爆的Vim插件

我使用Vim编辑器已经差不多20年了,不过,我是在前两年才开始使用Vim插件,我会定时清理插件,我认为这样做更有助于自己了解哪些插件才是最好的。此外,针对不同的语言和文件配置我会使用不同的插件

谷歌插件在测试的应用

随着SOA的普及,各大互联网公司都拥有自己较为成熟的分布式跟踪系统,比如Google的Dapper,Twitter的Zipkin,淘宝的鹰眼,京东的Hydra,eBay的CAL,大众点评的CAT等等:

vuedraggable:vue拖拽排序插件

最近做的项目要用到拖拽排序,我现在的项目是vue项目,所以我就屁颠屁颠的去百度有木有这样功能的插件,我就知道一定会有,那就是vuedraggable,这是一款很棒的拖拽插件,下面我来说一下怎么引入

fly63.com版权所有,内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权,请与小编联系!情况属实本人将予以删除!

文章投稿关于web前端网站点搜索站长推荐网站地图站长QQ:522607023

小程序专栏: 土味情话心理测试脑筋急转弯幽默笑话段子句子语录成语大全