首页 > 其他分享 >babel使用及分析

babel使用及分析

时间:2023-03-02 09:44:05浏览次数:55  
标签:分析 node ast babel value 使用 type 节点

参考资料
1、ast查看链接(opens new window)
2、bable官网(opens new window)
3、AST详解与运用(opens new window)
4、babel插件说明
babel是一个js编译器,是一个工具链,用于将es2015+版本的代码转换为向后兼容的js语法,可以做:

  • 语法转换
  • 添加目标环境中缺少的polyfill功能
  • 源代码转换

提供了插件功能,一切功能都可以以插件来实现,方便使用和弃用。

1、工具包

  • @babel/parser:转化为 AST 抽象语法树;
  • @babel/traverse:对 AST 节点进行递归遍历;
  • @babel/generator:AST抽象语法树生成为新的代码
  • @babel/core:内部核心的编译和生成代码的方法,上面三个集合,一般使用这个包
  • @babel/types:判断 AST 节点类型以及创建新节点的工具类
  • @babel/cli:babel命令行工具内部解析相关方法
  • @babel/preset-env: babel编译结果预设值,使用can i use网站作为基设
  • @babel/polyfill:es6语法的补丁,安装了所有符合规范的 polyfill之后,我们需要在组件引用这个模块,就能正常的使用规范中定义的方法了。

2、使用

  • 安装@babel/core和@babel/cli即可使用命令行解析工具
  • 输出编译代码compiler:babel index.js -o output.js
  • 使用preset预设,配置.babelrc中presets属性,适用于语法层面范畴
  • 使用polyfill,需要在代码中引入polyfill模块,给所有方法打补丁,保证运行正常,适用于方法层面,polyfill通常需要 --save,其他使用--save-dev即可
  • babel执行顺序:plugins先执行、再执行预设presets

几个重要概念:

  • preset:预设,是一组用于支持特定语言功能的插件,主要用于对语法进行转换
  • polyfill:给方法打补丁,保证运行正常,适用于方法层面
  • transform-runtime:将api进行私有化,防止引入外部库冲突,eg:_promise\
// presets预设使用
// index.js
var func = () => console.log("hello es6");
var { a, b = 1 } = { a: "this is a" }

// .babelrc配置,presets预设1
var babelrc = {
  "presets": [
    "@babel/preset-env"
  ]
}

// 输出
"use strict"
var func = function func() {
  return console.log("hello es6");
}

var _a = {
  a: "this is a"
},
a = _a.a,
_a$b = _a.b,
b = _a$b === void 0 ? 1 : _a$b

// .babelrc配置,presets预设2
var babelrc = {
  "presets": [
    ["@babel/preset-env", {
      "targets": ">1.5%"
    }]
  ]
}

// 输出:箭头函数和解构未转换
"use strict"
const func = () => console.log("hello es6");
const {
  a,
  b = 1
} = {
  a: "this is a"
}
import "@babel/polyfill";
var array = [1, 2, 3];
console.log(array.includes(2));

// 输出
"use strict"
require("@babel/polyfill") // 加载了全部polyfill
var array = [1, 2, 3]
console.log(array.includes(2));

// 按需加载
// .babelrc配置
var babelrc = {
  "presets": [
    ["@babel/preset-env", {
      "targets": ">1.5%",
      "useBuiltIns": "usage", // 按需加载
      "corejs": 3 // 指定corejs版本
    }]
  ]
}
// index.js,去除import
var array = [1, 2, 3]
console.log(array.includes(2));

// 输出
"use strict"
require("core-js/modules/es.array.includes.js")
var array = [1, 2, 3]
console.log(array.includes(2));

解析:@babel/preset-env中useBuiltIns 说明

  • false:此时不对 polyfill 做操作。如果引入 @babel/polyfill,则无视配置的浏览器兼容,引入所有的 polyfill,默认选项
  • entry:根据配置的浏览器兼容,引入浏览器不兼容的 polyfill。需要在入口文件手动添加 import '@babel/polyfill',会自动根据 browserslist 替换成浏览器不兼容的所有 polyfill,这里需要指定 **core-js **的版本
  • usage:会根据配置的浏览器兼容,以及你代码中用到的 API 来进行 polyfill,实现了按需添加

3、babel 处理步骤

  • 解析:接收代码并输出AST(抽象语法树)
    • 词法分析:把字符串形式的代码转换为令牌(tokens)流,令牌看作是一个扁平的语法片段数组
    • 语法分析:把 一个令牌流转换为AST,使用令牌中的 信息把它们转换成一个 AST 的表述解构
  • 转换:接收 AST 并对其 遍历,在此过程中对节点进行添加、更新和移除等操作。这是Babel或是其他编译器中最复杂的 过程,同时也是插件将要介入工作的部分
  • 生成:把最终的AST转换成字符串形式的代码,同时创建源码映射(source maps)。代码 生成过程:深度优先遍历整个AST,然后构建可以表示转化后代码的字符串

4、手写babel原理

(add 2 (subtract 40 2)) 编译成 add(2, subtract(40, 2))
静态编译:字符串 -> 字符串
思路:正则匹配、状态机、编译器处理流程(解析、转换、生成)

  • 分词:将表达式分词,水平状态
/*
[
  { type: 'paren', value: '(' },
  { type: 'name', value: 'add' },
  { type: 'number', value: '2' },
  { type: 'paren', value: '(' },
  { type: 'name', value: 'subtract' },
  { type: 'number', value: '40' },
  { type: 'number', value: '2' },
  { type: 'paren', value: ')' },
  { type: 'paren', value: ')' },
]
*/
function generateToken(str) {
  let current = 0; // 下标
  let tokens = []; // 记录分词列表

  while (current < str.length) {
    let char = str[current];

    // 括号分词:记录为词语
    if (char === "(") {
      tokens.push({
        // 末尾添加对象返回长度,pop删除数组最后一项,返回元素,栈方法FILO
        type: "paren",
        value: "("
      });
      current++;
      continue;
    }

    // 括号分词:记录为词语
    if (char === ")") {
      tokens.push({
        type: "paren",
        value: ")"
      });
      current++;
      continue;
    }

    // 空格分词:直接跳过
    if (/\s/.test(char)) {
      current++;
      continue;
    }

    // 数字分词:二次遍历
    if (/[0-9]/.test(char)) {
      let numberValue = "";
      while (/[0-9]/.test(char)) {
        numberValue += char;
        char = str[++current];
      }
      tokens.push({
        type: "number",
        value: numberValue
      });
      continue;
    }

    // 字符串分词
    if (/[a-z]/.test(char)) {
      let strValue = "";
      while (/[a-z]/.test(char)) {
        strValue += char;
        char = str[++current];
      }
      tokens.push({
        type: "name",
        value: strValue
      });
      continue;
    }

    throw new TypeError("type error");
  }

  return tokens;
}
  • 生成ast:垂直结构,estree规范
/*
json.cn 可查看json信息
{
  "type": "Program",
  "body": [
    {
      "type": "CallExpression",
      "name": "add",
      "params": [
        {
          "type": "NumberLiteral",
          "value": 2
        },
        {
          "type": "CallExpression",
          "name": "subtract",
          "params": [
            {
              "type": "NumberLiteral",
              "value": "40"
            },
            {
              "type": "NumberLiteral",
              "value": "2"
            }
          ]
        }
      ]
    }
  ]
}
*/
function generateAST(tokens) {
  let current = 0;
  let ast = {
    type: "Program",
    body: []
  };
  // 闭包处理
  function walk() {
    let token = tokens[current];
    if (token.type === "number") {
      current++;
      return {
        type: "NumberLiteral",
        value: token.value
      };
    }
    // 左括号为层级开始,为执行语句
    if (token.type === "paren" && token.value === "(") {
      token = tokens[++current];
      let node = {
        type: "CallExpression",
        name: token.value,
        params: []
      };
      token = tokens[++current];
      while (
        token.type !== "paren" ||
        (token.type === "paren" && token.value !== ")")
      ) {
        node.params.push(walk()); // 递归调用
        token = tokens[current]; // 取当前值即可,walk()里完成指针移动
      }
      current++;
      return node;
    }
  }

  while (current < tokens.length) {
    ast.body.push(walk());
  }

  return ast;
}
  • 遍历ast,转化为新的ast
/*
{
  "type": "Program",
  "body": [
    {
      "type": "ExpressionStatement",
      "expression": {
        "type": "CallExpression",
        "callee": {
          "type": "Identifier",
          "name": "add"
        },
        "arguments": [
          {
            "type": "NumberLiteral",
            "value": "2"
          },
          {
            "type": "CallExpression",
            "callee": {
              "type": "Identifier",
              "name": "subtract"
            },
            "arguments": [
              {
                "type": "NumberLiteral",
                "value": "40"
              },
              {
                "type": "NumberLiteral",
                "value": "2"
              }
            ]
          }
        ]
      }
    }
  ]
}
*/
function transformer(ast) {
  let newAst = {
    type: "Program",
    body: []
  };
  ast._context = newAst.body; // ast子元素挂载
  // 类似于babel插件功能
  DFS(ast, {
    // 生命周期,enter、exit
    NumberLiteral: {
      enter(node, parent) {
        // 父元素记录子元素值,为父元素CallExpression做准备
        parent._context.push({
          type: "NumberLiteral",
          value: node.value
        });
      }
    },
    // NumberLiteral的父元素为CallExpression
    CallExpression: {
      enter(node, parent) {
        let expression = {
          type: "CallExpression",
          callee: {
            type: "Identifier",
            name: node.name
          },
          arguments: []
        };
        // a.子元素值赋值到父元素的arguments去
        node._context = expression.arguments;

        // b.二次操作
        if (parent.type !== "CallExpression") {
          expression = {
            type: "ExpressionStatement",
            expression: expression
          };
        }
        parent._context.push(expression);
      }
    }
  });
  return newAst;
}

function DFS(ast, visitor) {
  // 遍历子元素数组
  function traverseArray(children, parent) {
    children.forEach(child => tranverseNode(child, parent));
  }

  function tranverseNode(node, parent) {
    let methods = visitor[(node, type)];
    if (methods && methods.enter) {
      methods.enter(node, parent);
    }
    switch (node.type) {
      case "Program": {
        // 子元素body,父元素node
        traverseArray(node.body, node);
        break;
      }
      case "CallExpression": {
        // 子元素params,父元素node
        traverseArray(node.params, node);
        break;
      }
      case "NumberLiteral": {
        break;
      }
      default: {
        break;
      }
    }
    if (methods && methods.exit) {
      methods.exit(node, parent);
    }
  }

  return tranverseNode(ast, null);
}
  • 基于ast,生成代码
// add(2, subtract(40, 2))
function generate(ast) {
  switch(ast.type) {
    case "Identifier": return ast.name
    case "NumberLiteral": return ast.value
    // 每个子元素一行展示
    case "Program": return ast.body.map(subAst => generate(subAst)).join('\n')
    case "ExpressionStatement": return generate(ast.expression) + ";"
    // 函数调用形式 add(参数, 参数, 参数)
    case " CallExpression": return generate(ast.callee) + "(" + ast.arguments.map(arg => generate(arg)).join(', ') + ")"
    default: break
  }
}

5、插件添加及使用

1、类型

babel插件分为语法插件和转换插件:

  • 语法插件:syntax plugin,在@babel/parser中加载,在parser过程中执行的插件,例如:@babel/plugin-syntax-jsx
  • 转换插件:transform plugin,在@babel/transform中加载,在transform过程中执行的插件

2、插件思路

  • 做什么插件:自己做什么事情以及受益
  • 分析ast:比对原始数据与最终转换为的数据两个ast的不同,来找到所需操作的transform方法
  • 参考手册进行开发:babel官网插件开发手册、@babel/types手册、estree规范手册

3、相关概念

babel插件为一个函数或者对象,若为函数:入参使用types对象,出参一个对象,输出对象中有visitor属性。

  • types对象:每个单一类型节点的定义,包括节点的属性、遍历等信息。
  • visitor:插件的主要访问者,visitor是一个对象,包含各种类型节点 的访问函数,接收 state和path参数
  • path:表示两个节点之间连接的对象,这个对象包含当前节点和父节点的信息以及添加、修改、删除节点有关的方法
    • 属性
      • node:当前节点
      • parent:父节点
      • parentPath:父path
      • scope:作用域
      • context:上下文
    • 方法:
      • get:获取当前节点
      • getSibling:获取兄弟节点
      • findParent:向父节点搜寻节点
      • replaceWith:用ast节点替换该节点
      • repalceWithMultiple:用多个ast节点替换该节点
      • insetBefore:在节点前插入节点
      • insetAfter:在节点后插入节点
      • remove:删除节点
  • state:visitor对象中每次访问节点方法时传入的第二个参数。包含当前plugin的信息、scope作用域信息、plugin传入的配置参数信息 ,当前节点的path信息。可以把babel插件处理过程中的自定义状态存储到state对象中。
  • Scope:与js中作用域类似,如函数内外的同名变量需要区分开来。
  • Bindings:所有引用属于特定的作用域,引用和作用域的这种关系称作为绑定。

4、实战

  1. 将字符串中的+转换为-操作符号:
// input.js
1 + 1;

// output.js
1 - 1;

// plugin.js
export default function({types: t}) {
  return {
    visitor: {
      BinaryExpression(path) {
        path.node.operator = "-"
      }
    }
  }
}

// .babelrc
var babelrc = {
  "plugins": [
    ["./plugin"]
  ]
}

2、去除代码中的console函数调用

const { transform } = require("@babel/core");
const test = "const a = 1; console.log('woshi');let b = 2; console.log('haha');let c=3";
const myPlugins = {
  name: "myPlugins",
  visitor: {
    CallExpression(path) {
      if(path.get('cellee').isMemberExpression()) {
        if(path.get('callee').get('object').isIndentifier()) {
          if(path.get('callee').get('object').get('name') == 'console') {
            path.remove
          }
        }
      }
    }
  }
};
var newCode = transform(test, {
  plugins: [myPlugins]
})
console.log(newCode.code);

3、扩展场景:组件按需引用,提升LCP

import { button, nav } from "elementUi";
// 转换为:import button from 具体路径

标签:分析,node,ast,babel,value,使用,type,节点
From: https://www.cnblogs.com/DTCLOUD/p/17170726.html

相关文章

  • zustand react ts使用
    一款redux替代品的状态管理 实现:react状态管理ts支持数据持久化储存store模块封装三种引入使用的方法 1、installnpminstallzustand#oryarnaddzusta......
  • Git介绍下载安装以及基本使用
    目录一、git介绍二、下载安装git软件三、基本使用四、制作忽略文件五、Git、Gitee、GitHub、Gitlab、bitbucket的区别六、基础代码操作分类一、git介绍git代码管理软件,和......
  • Groovy 使用EasyExcel操作Excel
    示例一:读取Excel,打印拼接的Sqlimportcom.alibaba.excel.EasyExcelclassObj{Integernum;Stringname;}defreaderBuilder=EasyExcel.read(newFile(......
  • dotnet-cnblogs-tool 工具使用
    工具的作用在下面第一部分原作者的GitHub说明和博客中有介绍,此文中就不在说明。目录一、DotNet博客园图片上传工具二、dotnet-cnblogs安装与使用三、快捷方式的分享点四......
  • 使用venv创建虚拟环境
    使用venv创建虚拟环境python3.3之后venv已经作为标准库嵌入到了python中,而之前的版本需要借助virtualenv这个第三方库来实现。在终端中使用python-mvenv-h可以显示ve......
  • 图集的合理使用
    图集是什么将多张图片打包到一张纹理上的技术叫图集(Atlas)。原本图片一张张送到GPU渲染,如果打包到一张纹理上,就有机会将多个物件的渲染在一次DrawCall中同时进行。(不同材质......
  • Android病毒分析基础(二)—ChatGPT提问技巧
    今天我们来用最近比较火的“ChatGPT”学习一下Android病毒分析基础,看看和我们之前学的有什么不同,又能学习到什么新的东西,同时了解一下“ChatGPT”提问的一些精髓。和我们......
  • R语言自然语言处理NLP:情感分析上市公司文本信息知识发现可视化
    全文链接:http://tecdat.cn/?p=31702原文出处:拓端数据部落公众号情感分析,就是根据一段文本,分析其表达情感的技术。比较简单的情感分析,能够辨别文本内容是积极的还是消极的......
  • 马尔可夫Markov区制转移模型分析基金利率|附代码数据
    全文下载链接:http://tecdat.cn/?p=19611最近我们被客户要求撰写关于马尔可夫Markov区制转移模型的研究报告,包括一些图形和统计输出。过程会随着时间的推移而发展,结果会发......
  • 使用iconv命令批量原地转码文件
    目录一、iconv简述二、iconv原地转码命令三、我工作中遇到的问题问题场景:解决方案:一、iconv简述​ 日常工作中我们需要将windows生成的文件上传到Linux系统,有时候会因......