likes
comments
collection
share

babel 我勇敢面对它了

作者站长头像
站长
· 阅读数 13

babel

bable好难! 看不懂! 涉及到AST! 太高级的东西我是不是学不会啊, 文档太乱(还都是英文的)不知道怎么开始学习,这是我在学习前的思想工作。 但我还是没有放弃,我得学习要加油!!! 相信你看完这篇文章也会对babel 有个基础的认识。

简介

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

  • 语法转换
  • 通过 Polyfill 方式在目标环境中添加缺失的特性
  • 源码转换
// Babel 输入: ES2015 箭头函数
[1, 2, 3].map((n) => n + 1);

// Babel 输出: ES5 语法实现的同等功能
[1, 2, 3].map(function(n) {
  return n + 1;
});

开始

新建babel-project-01文件夹, 然后执行npm init --yes生成项目package.json。 新建babel.config.js, 内容如下:

// babel.config.js
module.exports = {
  presets: ["@babel/env"],
  plugins: []
};

安装Babel官方依赖:

  1. @babel/cli(是Babel命令行转码工具,使用命令行进行Babel转码(npx babel *))
  2. @babel/core(@babel/cli依赖@babel/core,这个是babel的核心库,本质上就是一个解析器,这个库提供了很多api,可以将普通js解析成ast语法树)
  3. @babel/preset-env(提供了ES6转换ES5的语法转换规则, 不使用的话,也可以完成转码,但转码后的代码仍然是ES6的,相当于没有转码。)
yarn add @babel/cli @babel/core @babel/preset-env --dev

新建index.js文件, 内容如下:

// Babel 输入: ES2015 箭头函数
[1, 2, 3].map((n) => n + 1);

执行npx babel index.js, 得到以下输出:

$ npx babel index.js
"use strict";

// Babel 输入: ES2015 箭头函数
[1, 2, 3].map(function (n) {
  return n + 1;
});

常用命令行操作

  • npx babel index.js 没有任何参数的,会编译指定文件并输出到控制台;
  • npx babel index.js -o ./dist/index.js 将babel的输出内容输出到指定的一个文件里。(-o是--out-file的简写)
  • npx babel src_dir -d dest_dir 当 js 文件很多时,就需要直接转码整个文件夹。(-d是--out-dir的简写)

babel 本质

把这个转译代码分为三步:

  • parser:code=>ast
  • transform:ast=>修改过的ast
  • generate:修改过的ast=>编译后的code

第一步对应@babel/parser,第二步对应@babel/traverse,第三步对应@babel/generator。

@babel/generator @babel/parser 介绍使用

@babel/parser 转成ast @babel/generator 生成代码code

@babel/parser

把一段代码转成ast, 使用如下:

import { parse } from '@babel/core';

const code = `const a = 123;`

const parseCode = parse(code, {
  sourceType: 'unambiguous' // "script", "module", or "unambiguous". Defaults to "script".
})

console.log(parseCode);

输出:

Node {
  type: 'File',
  start: 0,
  end: 14,
  loc: SourceLocation {
    start: Position { line: 1, column: 0, index: 0 },
    end: Position { line: 1, column: 14, index: 14 },
    filename: undefined,
    identifierName: undefined
  },
  errors: [],
  program: Node {
    type: 'Program',
    start: 0,
    end: 14,
    loc: SourceLocation {
      start: [Position],
      end: [Position],
      filename: undefined,
      identifierName: undefined
    },
    sourceType: 'script',
    interpreter: null,
    body: [ [Node] ],
    directives: []
  },
  comments: []
}

可以去astexplorer 查看具体AST

@babel/generator

咱们继续接着上面写,把上面一段ast给转化成code,使用方式如下:

import { parse } from '@babel/core';
import generate from '@babel/generator';

const code = `const a = 123;`;

const parseCode = parse(code, {
  sourceType: 'unambiguous' // "script", "module", or "unambiguous". Defaults to "script".
})

const { code: generateCode } = generate(parseCode);
console.log(generateCode,'generateCode')

输出:

const a = 123; generateCode

@babel/types

@babel/types 相关简介 以及用法 types 主要用来创建、判断类型等 搭配@babel/generator来生成代码。

创建相关

下面是基础代码使用@babel/generator 生成代码

// index.js
import * as types from '@babel/types'
import gen from '@babel/generator'


const log = (node) => {
  console.log(node,'node')
  console.log(gen(node).code)
}
  • stringLiteral 创建字符传数据 返回值 { type: 'StringLiteral', value: 'string' }
  • numericLiteral 创建数值数据 ({ type: 'NumericLiteral', value: 100000 })
  • booleanLiteral 创建布尔数据({ type: 'BooleanLiteral', value: true })
  • regExpLiteral 创建正则表达式 ({ type: 'RegExpLiteral', pattern: '\.jsx?$', flags: 'g' })

code:

//  创建基础数据
const createString = types.stringLiteral('id');
const createNumber = types.numericLiteral(10e4);
const createBoolean = types.booleanLiteral(true);
const createRegExp = types.regExpLiteral('\\.jsx?$', 'g');
const createNull = types.nullLiteral();

log(createString);
log(createNumber);
log(createBoolean);
log(createRegExp);
log(createNull);

输出:

{ type: 'StringLiteral', value: 'id' } node
"id"
{ type: 'NumericLiteral', value: 100000 } node
100000
{ type: 'BooleanLiteral', value: true } node
true
{ type: 'RegExpLiteral', pattern: '\\.jsx?$', flags: 'g' } node
/\.jsx?$/g
{ type: 'NullLiteral' } node
null

数组、对象

创建数组对象

  • arrayExpression([]) 数组里面可以是 types.stringLiteral....
  • objectExpression([]) 数组俩面使用types.objectProperty() 创建每组对象
  • objectMethod() 创建对象方法
  • types.objectProperty(key, value, computed, shorthand, decorators)
    • key 可以是types.stringLiteral('id')/types.identifier('id')
    • value 可以是types.stringLiteral('id')/types.identifier('id')
    • computed: boolean 默认false。 true下会生成{[id]: 123} key变成动态的了。
    • shorthand: boolean 默认false。true会简写对象 {id}样

code:

// 创建数组
const createArray = types.arrayExpression([
  createString, // 上面的变量
  createNumber, // 上面的变量
]);
log(createArray);


// 创建对象
// t.objectProperty(key, value, computed, shorthand, decorators);
/**
 * key: if computed then Expression else Identifier | Literal (required)
    value: Expression | PatternLike (required)
    computed: boolean (default: false)
    shorthand: boolean (default: false)
    decorators: Array<Decorator> (default: null)
 */
const createObject = types.objectExpression([
  types.objectProperty(createString, createNumber, true, false),
  types.objectProperty(types.identifier('id'), types.identifier('id'), false, true),
]);


//  types.stringLiteral('id') / types.identifier('id') 区别是:"id" / id
log(createObject);

输出:

{
  type: 'ArrayExpression',
  elements: [
    { type: 'StringLiteral', value: 'id' },
    { type: 'NumericLiteral', value: 100000 }
  ]
} node
["id", 100000]
{
  type: 'ObjectExpression',
  properties: [
    {
      type: 'ObjectProperty',
      key: [Object],
      value: [Object],
      computed: true,
      shorthand: false,
      decorators: null
    },
    {
      type: 'ObjectProperty',
      key: [Object],
      value: [Object],
      computed: false,
      shorthand: true,
      decorators: null
    }
  ]
} node
{
  [id]: 100000,
  id
}

创建函数

创建具名函数和箭头函数

  • functionDeclaration() 具名函数: function test() {}
  • arrowFunctionExpression() 箭头表达式函数: () => {}
  • blockStatement() 块语句: {}
  • expressionStatement() 表达式语句
  • callExpression() 调用表达式/函数 第一个参数函数名 第二个参数函数入参: console.log(a,b,c)
  • returnStatement() return 语句
  • binaryExpression() 二元表达式 参数1 操作符, 参数2 左侧 参数3 右侧 : a + b

用法如下

// 创建具名函数
const createFunc1 = types.functionDeclaration(
    types.identifier('test'), // 函数名
    [types.identifier('arg1'), types.identifier('arg2')], // 函数参数
    types.blockStatement([ // 块语句
        types.expressionStatement(
            types.callExpression(
                types.identifier('console.log'),  // 被调用的函数名
                [types.identifier('arg1'), types.identifier('arg2')] // 入参数
            )
        ),

        types.returnStatement( // return 语句
          types.binaryExpression('+', types.identifier('arg1'), types.identifier('arg2')) // 二元表达式
        ),
    ]),
    false, // 是否async
)
log(createFunc1)


// 创建箭头函数表达式
const createFunc2 = types.arrowFunctionExpression(
    [types.identifier('arg1'), types.identifier('arg2')], // 参数
    types.callExpression(
        types.identifier('console.log'),  // 被调用的函数名
        [types.identifier('arg1'), types.identifier('arg2')] // 入参数
    ),
    true, // 是否async
)

log(createFunc2)

输出:

function test(arg1, arg2) {
  console.log(arg1, arg2);
}

# 此处可以思考:为什么没有箭头函数声明,以及Declaration和Expression的区别
async (arg1, arg2) => console.log(arg1, arg2)

变量声明(函数表达式、箭头函数表达式使用)

变量声明 以及带你理解表达式(Expression)声明(Declaration)区别

  • variableDeclaration() 变量声明参数1是count/let/var, 参数2是variableDeclarator集合或者单个
  • variableDeclarator() 变量声明者参数1是变量名types.identifier('a1'), 参数2是默认值a1 = 123 || a1; 变量声明用法如下:
const createA1 = types.variableDeclaration('const', [
    types.variableDeclarator(
      types.identifier('a1'),
    ),
]);
const createA2 = types.variableDeclaration('const', [
    types.variableDeclarator(
      types.identifier('a2'),
      types.numericLiteral(123), // 初始值 const a = 1; 
    ),
]);

log(createA1)
log(createA2)

输出:

const a1;
const a2 = 123;

函数表达式、箭头函数表达式结合变量声明如下:

  • functionExpression() const a = function(){}
  • arrowFunctionExpression() const a = () => {}
// 箭头函数表达式
const createFunc2 = types.arrowFunctionExpression(
    [types.identifier('arg1'), types.identifier('arg2')], // 参数
    types.callExpression(
        types.identifier('console.log'),  // 被调用的函数名
        [types.identifier('arg1'), types.identifier('arg2')] // 入参数
    ),
    true, // 是否async
)

// 函数表达式
const createFunc3 = types.functionExpression(
  types.identifier('test3'),
  [types.identifier('arg1'), types.identifier('arg2')],
  types.blockStatement([
    types.expressionStatement(
        types.callExpression(
            types.identifier('console.log'),  // 被调用的函数名
            [types.identifier('arg1'), types.identifier('arg2')] // 入参数
        )
    )
  ]),
  false, // generator
  true, // async
)


const createA3 = types.variableDeclaration('const', [
    types.variableDeclarator(
      types.identifier('a1'),
      createFunc2, // 箭头函数表达式
    ),
]);
const createA4 = types.variableDeclaration('const', [
    types.variableDeclarator(
      types.identifier('a2'),
      createFunc3, // 普通函数表达式
    ),
]);

log(createA3)
log(createA4)

输出:

const a1 = async (arg1, arg2) => console.log(arg1, arg2);
const a2 = async function test3(arg1, arg2) {
  console.log(arg1, arg2);
};

结论:函数表达式是可以赋值给变量使用的

await 表达式(awaitExpression)、 成员表达式(memberExpression)

介绍awaitExpressionmemberExpression 表达式的具体用法实践

  • awaitExpression await a()
  • memberExpression a.b.c

awaitExpression 使用

// awaitExpression
const createFunc4 = types.functionDeclaration(
    types.identifier('test2'),
    [types.identifier('arg1'), types.identifier('arg2')], // 函数参数
    types.blockStatement([
        types.variableDeclaration('const', [
            types.variableDeclarator(
              types.identifier('a3'),
              types.awaitExpression(
                types.binaryExpression('+', types.numericLiteral(2), types.numericLiteral(3))
              ),
            ),
        ])
    ]),
    false, // generator
    true, // async
)


log(createFunc4)

输出:

async function test2(arg1, arg2) {
  const a3 = await (2 + 3);
}

memberExpression 用法(补充objectMethod用法)

const createObject2 = types.objectExpression([
    types.objectProperty(
        types.identifier('a'),
        types.stringLiteral('123'),
    ),

    types.objectMethod(
      'method', 
      types.identifier('fn'), 
      [types.identifier('args1')],
      types.blockStatement([
        types.expressionStatement(
            types.callExpression(
                types.identifier('console.log'),  // 被调用的函数名
                [types.identifier('arg1'), types.identifier('arg2')] // 入参数
            )
        )
      ])
    )
])

const b1 = types.memberExpression(createObject2,types.identifier('a'));
log(b1)

输出:

{
  a: "123",
  fn(args1) {
    console.log(arg1, arg2);
  }
}.a

扩展运算符(spreadElement)

介绍使用扩展运算符

const createArray = types.arrayExpression([types.stringLiteral('id'), types.stringLiteral('id')]);
const b2 = types.arrayExpression([
    types.spreadElement(createArray)
]);
log(b2);

输出:

[...["id", "id"]]

try catch

使用try catch包裹

  • tryStatement
  • catchClause
 const createTryCatch = types.tryStatement(types.blockStatement([
    types.variableDeclaration('const', [
        types.variableDeclarator(
          types.identifier('a1'),
        ),
    ])
]), types.catchClause(
    types.identifier('error'),
    types.blockStatement([
        types.expressionStatement(
            types.callExpression(
                types.identifier('console.log'),  // 被调用的函数名
                [types.identifier('error'),types.stringLiteral('error')] // 入参数
            )
        )
    ])
))

log(createTryCatch)

输出:

try {
  const a1;
} catch (error) {
  console.log(error, "error");
}

其他常用

  • ifStatement(if)if () {}
  • forStatement(for)for (;;){}
  • forInStatement(for in)or (a in b) {}
  • forOfStatement(for of)for (a of b) {}
  • importDeclaration(import声明)import 'a'
  • importDefaultSpecifier(import default说明符)import a from 'a'
  • ImportSpecifier(import说明符)import {a} from 'a'
  • newExpression(new表达式)new A()
  • classDeclaration(class声明)class A {}
  • classBody(class body)class A {}(类的内部)

@babel/template

我们再@babel/types使用发现再编写复杂的模板的时候较为复杂,@babel/template就能辅助尼编写ast

例如我们要编写一个如下的ast:

const myModule = require("my-module");

我们使用@babel/template 就可以这个样:

第一种:语法占位符

import { types } from '@babel/core';
import generate from '@babel/generator';
import template from "@babel/template";

const buildRequire = template(`
  const %%importName%% = require(%%source%%);
`);
const ast = buildRequire({
  importName: types.identifier("myModule"),
  source: types.stringLiteral("my-module"),
});

console.log(generate(ast).code);

输出:

const myModule = require("my-module");

第二种:标识符占位符

import { types } from '@babel/core';
import generate from '@babel/generator';
import template from "@babel/template";

const buildRequire2 = template(`
  var IMPORT_NAME = require(SOURCE);
`);

const ast2 = buildRequire2({
  IMPORT_NAME: types.identifier("myModule"),
  SOURCE: types.stringLiteral("my-module"),
});

console.log(generate(ast2).code);

输出:

var myModule = require("my-module");

第三种:模板文字用法

import { types } from '@babel/core';
import generate from '@babel/generator';
import template from "@babel/template";

const fn = template`
  var IMPORT_NAME = require(SOURCE);
`;

const ast3 = fn({
  IMPORT_NAME: types.identifier("myModule"),
  SOURCE: types.stringLiteral("my-module"),
});

console.log(generate(ast3).code);

输出:

var myModule = require("my-module");

总结

本文主要总结了 @babel/generator、@babel/parser、@babel/types、@babel/template相关用法 相信你看完后对babel 也有了个基础认识。

结束语

大家好 我是三原,期待您的点赞关注😁😁😁