Babel插件原来这样写

139 阅读4分钟

babel

Babel介绍

Babel是一个JavaScript编译器。

Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。

  • 语法转换
  • 通过 Polyfill 方式在目标环境中添加缺失的特性
  • 源码转换

Babel在编译时通过一系列配置实现代码转换。

{
	"presets":[...], // 一组Babel插件或者配置组合的模块
	"plugins":[...], // 一组用于转换代码的插件
	"parserOpts":{
		"plugins": [...] // 用于传递给插件的参数
   }
}

执行顺序

  • 插件在Presets前运行
  • 插件顺序从前往后排列
  • Presets 顺序是从后往前

Babel处理

Babel的三个主要处理步骤分别是: 解析、转换、生成

解析

解析步骤接收代码并输出 AST,这个步骤氛围两个阶段:词法分析 和 语法分析。

  • 词法分析 ⇒ 把字符串形式的代码转换为tokens流
{
  type: "FunctionDeclaration",
  id: {
    type: "Identifier",
    name: "square"
  },
  params: [{
    type: "Identifier",
    name: "n"
  }],
  body: {
    type: "BlockStatement",
    body: [{
      type: "ReturnStatement",
      argument: {
        type: "BinaryExpression",
        operator: "*",
        left: {
          type: "Identifier",
          name: "n"
        },
        right: {
          type: "Identifier",
          name: "n"
        }
      }
    }]
  }
}
  • 语法分析 ⇒ 把tokens流转换为AST的形式,转换tokens流中的信息为一个AST的表述结构,方便后续操作
{
  "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
}

Babylon 是 Babel的解析器,是在 Acorn项目的基础上进行更新迭代。

import * as babylon from 'babylon'

const code = `function square(n){
  return n * n 
}`
const ast = babylon.parse(code)

转换

接收AST并对其进行遍历,在此过程中对节点进行添加、更新、删除等操作。这是Babel中最为复杂的过程,同时也是插件介入工作的部分。

babel-traverse 遍历AST维护AST的状态,对其进行替换、移除和添加节点。

import traverse from 'babel-traverse'

traverse(ast,{
	enter(path){
		...
	}
})
  • 节点⇒ Node , AST操作的基础对象,各节点存在类似的结构
  • 路径⇒ Path, 两个节点之间连接的对象, 提供 节点的访问和增、删、改、查
  • 状态⇒ State ,插件状态管理
  • 作用域⇒Scope,在代码转换时,注意函数作用域,不能贸然转换破坏其他的代码

生成

把经过转换后的AST结构转换成字符串形式的代码,同时还会创建source map

babel-generator 是Babel的代码生成器,它读取AST并将其转换为代码和sourceMap

import generate from 'babel-generator'

generate(ast,{},code)

babel插件

在转换过程中,通过遍历AST,对其实现节点的添加、更新、删除等操作。而在遍历ast的节点时我们通过一种访问者模式(vistor)的概念,就是定义了一个在遍历过程中获取节点进行处理的方法。

import traverse from 'babel-traverse'

const myPlugin = {
	Identifier(){...},
	FunctionDeclaration(){...}
}
path.traverse(myPlugin)

插件开发

初始化项目

npm install -g yo generator-babel-plugin
mkdir babel-hello
cd babel-hello
yo babel-plugin
? Plugin Name => 插件名称
? Description => 插件描述
? GitHub username or organization => 开发者
? Author`s Name => 默认开发者
?Author's email => 开发者邮箱
? Key your keywords => 关键词

注意:yo版本3.1.1,否则会出现初始化工程失败

目录结构

- lib 生成的插件目录
- src 插件开发目录
- test 测试目录
- .bablerc 默认babel配置
- package.json

开发介绍

export default function({types:t}){
	return {
		pre(state){...}, // 插件运行之前
		visitor:{...},
		post(state){...}, // 插件运行之后
	}
}
  • types 作为babel对象中集合多种AST节点类型的操作方法

    • 验证节点是否属于某种类型

      BinaryExpression(path){
      	if(t.isIdentifier(path.node.left)){
      		...
      	}
      	if(t.isBinaryExpression(path.node)){..}
      }
      
    • 创建某种类型的节点

      BinaryExpression(path){
      	path.node.left = t.identifier('x')
        path.node.right = t.expressionStatement(t.stringLiteral('This is a test'))
      	path.replaceWith(t.binaryExpression('**',path.node.left,t.numberLiteral(2)))
      }
      
    • 创建断言, 断言会直接抛出异常

      t.assertBinaryExpression
      
  • visitor 集合你的插件所要包含的操作, 这些操作接收path和state作为参数

    visitor:{
    	FunctionDeclaration(){ ... }, // 函数 => 函数名称 + 函数参数
    	BinaryExpression(){ ... }, // 表达式 => 操作符 + 变量名
    	Identifier(){ ... }, // 标识符
    	ReturnStatement(){ ... }, // 返回值
    }
    
  • path 集合了针对节点的操作

    • 节点获取

      path.node // 获取节点 => path.node.param/path.node.left
      path.inList // 判断路径是否有同级节点
      path.getSibling(index) 获取同级路径
      path.key // 获取路径所在容器的索引
      path.container // 获取路径所在容器
      path.listKey // 获取容器的key
      path.findParent // 获取父节点
      path.find // 遍历当前节点
      path.getFunctionParent // 获取最接近父函数
      path.getStatementParent // 向上遍历语法树,直到找到在列表中的父节点路径
      
    • 节点替换

      path.replaceWith // 节点替换
      path.replaceWith('**',path.node.left,t.numberLiteral(2)) 
      path.replaceWithMultiple // 多节点替换单节点
      path.replaceWithSourceString // 源码替换节点
      
    • 节点插入

      path.insertBefore // 在节点前插入
      path.insertAfter // 在节点后插入
      path.unshiftContainer // 插入到容器首部
      path.pushContainer // 插入到容器尾部
      
    • 节点域操作

      path.scope.hasBinding // 检查本地变量是否被绑定
      path.scope.generateUidIdentifier // 创建域名uid
      path.scope.rename // 重命名绑定及引用
      
    • 节点其他

      path.remove // 删除节点
      path.skip // 跳过当前路径下节点的遍历
      path.stop // 停止遍历
      path.buildCodeFrameError  // 抛出错误
      
  • State, 传递插件配置的参数

    {
      plugins: [
        ["my-plugin", {
          "option1": true,
          "option2": false
        }]
      ]
    }
    {
    	visitor:{
    		FunctionDeclaration(path,state){
    			console.log(state.opts)
    		}
    	}
    } 
    

GitHub - zjwgank/babel-demo: This is a demo for babel plugin

引用


babel-handbook/plugin-handbook.md at master · jamiebuilds/babel-handbook

Babel 中文网 · Babel - 下一代 JavaScript 语法的编译器

AST explorer