babel插件入门-AST

目次

  • Babel简介
  • Babel运转道理
  • AST剖析
  • AST转换
  • 写一个Babel插件

Babel简介

Babel 是一个 JavaScript 编译器,它能将es2015,react等低端浏览器没法辨认的言语,举行编译。

《babel插件入门-AST》

上图的左侧代码中有箭头函数,Babel将举行了源码转换,下面我们来看Babel的运转道理。

Babel运转道理

Babel 的三个重要处置惩罚步骤分别是:

剖析(parse),转换(transform),天生(generate)。.

其历程剖析用言语形貌的话,就是下面如许:

剖析

运用 <font color=Chocolate>babylon</font> 剖析器对输入的源代码字符串举行剖析并天生初始 AST(File.prototype.parse)

运用 <font color=Chocolate>babel-traverse</font> 这个自力的包对 AST 举行<font color=Chocolate>遍历</font>,并剖析出全部树的 <font color=Chocolate>path</font>,经由过程挂载的 metadataVisitor 读取对应的元信息,这一步叫 set AST 历程

转换

transform 历程:遍历 AST 树并运用各 <font color=Chocolate>transformers(plugin)</font> 天生变更后的 AST 树

babel 中最中心的是 <font color=Chocolate>babel-core</font>,它向外暴露出 babel.transform 接口。

let result = babel.transform(code, {
    plugins: [
        arrayPlugin
    ]
})

天生

运用 <font color=Chocolate>babel-generator</font> 将 <font color=Chocolate>AST</font> 树输出为转码后的代码字符串

AST剖析

AST剖析会把拿到的语法,举行树形遍历,对语法的每一个节点举行相应的变化和革新再生产新的代码字符串

节点(node)

AST将开首提到的箭头函数转依据节点换为节点树

ES2015箭头函数

codes.map(code=>{
    return code.toUpperCase()
})

AST树形遍历转换后的构造

{
    type:"ExpressionStatement",
    expression:{
        type:"CallExpression"
        callee:{
            type:"MemberExpression",
            computed:false
            object:{
                type:"Identifier",
                name:"codes"
            }
            property:{
                type:"Identifier",
                name:"map"
            }
            range:[]
        }
        arguments:{
            {
                type:"ArrowFunctionExpression",
                id:null,
                params:{
                    type:"Identifier",
                    name:"code",
                    range:[]
                }
                body:{
                    type:"BlockStatement"
                    body:{
                        type:"ReturnStatement",
                        argument:{
                            type:"CallExpression",
                            callee:{
                                type:"MemberExpression"
                                computed:false
                                object:{
                                    type:"Identifier"
                                    name:"code"
                                    range:[]
                                }
                                property:{
                                    type:"Identifier"
                                    name:"toUpperCase"
                                }
                                range:[]
                            }
                            range:[]
                        }
                    }
                    range:[]
                }
                generator:false
                expression:false
                async:false
                range:[]
            }
        }
    }
}

我们从 ExpressionStatement最先往树形构造内里走,看到它的内部属性有callee,type,arguments,所以我们再顺次接见每一个属性及它们的子节点。

因而就有了以下的递次

进入  ExpressionStatement
进入  CallExpression
进入  MemberExpression
进入  Identifier
脱离  Identifier
进入  Identifier
脱离  Identifier
脱离  MemberExpression
进入  ArrowFunctionExpression
进入  Identifier
脱离  Identifier
进入  BlockStatement
进入  ReturnStatement
进入  CallExpression
进入  MemberExpression
进入  Identifier
脱离  Identifier
进入  Identifier
脱离  Identifier
脱离  MemberExpression
脱离  CallExpression
脱离  ReturnStatement
脱离  BlockStatement
脱离  ArrowFunctionExpression
脱离  CallExpression
脱离  ExpressionStatement
脱离  Program

Babel 的转换步骤全都是如许的遍历历程。(有点像koa的洋葱模子??)

AST转换

剖析好树构造后,我们手动对箭头函数举行转换。

对照两张图,发明不一样的处所就是两个函数的arguments.type

《babel插件入门-AST》

《babel插件入门-AST》

剖析代码
let babel = require('babel-core');//babel中心库
let types = require('babel-types');
let code = `codes.map(code=>{return code.toUpperCase()})`;//转换语句

let visitor = {
    ArrowFunctionExpression(path) {//定义须要转换的节点
        let params = path.node.params
        let blockStatement = path.node.body
        let func = types.functionExpression(null, params, blockStatement, false, false)
        path.replaceWith(func) //
    }
}

let arrayPlugin = { visitor }
let result = babel.transform(code, {
    plugins: [
        arrayPlugin
    ]
})
console.log(result.code)

注重: ArrowFunctionExpression() { … } 是 ArrowFunctionExpression: { enter() { … } } 的简写情势。

<font color=Chocolate>Path 是一个对象,它示意两个节点之间的衔接。</font>

剖析步骤
  • 定义须要转换的节点
    ArrowFunctionExpression(path) {
        ......
    }
  • 建立用来替代的节点
types.functionExpression(null, params, blockStatement, false, false)

babel-types文档链接

《babel插件入门-AST》

  • 在node节点上找到须要的参数
  • replaceWith(替代)

写一个Babel插件

从一个接收了 babel 对象作为参数的 function 最先。

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

接着返回一个对象,其 visitor 属性是这个插件的重要节点接见者。

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

我们一样平常引入依靠的时刻,会将全部包引入,致使打包后的代码太冗余,到场了很多不须要的模块,比方index.js三行代码,打包后的文件大小就达到了483 KiB,

index.js

import { flatten, join } from "lodash";
let arr = [1, [2, 3], [4, [5]]];
let result = _.flatten(arr);

《babel插件入门-AST》

所以我们此次的目标是将

import { flatten, join } from "lodash";

转换为从而只引入两个lodash模块,削减打包体积

import flatten from "lodash/flatten";
import join from "lodash/join";

完成步骤以下:

  1. 在项面前目今的node_module中新建文件夹 <font color=Chocolate>babel-plugin-extraxt</font>

注重:babel插件文件夹的定义体式格局是 babel-plugin-插件名

我们能够在.babelrc的plugin中引入自定义插件 或许在webpack.config.js的loader options中到场自定义插件

  1. 在babel-plugin-extraxt新建index.js
module.exports = function ({types:t}) {
    return {
        // 对import转码
        visitor:{
            ImportDeclaration(path, _ref = { opts: {} }) {
                const specifiers = path.node.specifiers;
                const source = path.node.source;
                // 只要libraryName满足才会转码
                if (_ref.opts.library == source.value && (!t.isImportDefaultSpecifier(specifiers[0]))) { //_ref.opts是传进来的参数
                    var declarations = specifiers.map((specifier) => {      //遍历  uniq extend flatten cloneDeep
                        return t.ImportDeclaration(                         //建立importImportDeclaration节点
                            [t.importDefaultSpecifier(specifier.local)],
                            t.StringLiteral(`${source.value}/${specifier.local.name}`)
                        )
                    })
                    path.replaceWithMultiple(declarations)
                }
            }
        }
    };
}
  1. 修正<font color=Chocolate>webpack.prod.config.js</font>中babel-loader的设置项,在plugins中增加自定义的插件名
rules: [{
    test: /\.js$/,
    loader: 'babel-loader',
    options: {
        presets: ["env",'stage-0'],
        plugins: [
            ["extract", { "library":"lodash"}],
            ["transform-runtime", {}]
        ]
    }
}]

注重:plugins 的插件运用递次是递次的,而 preset 则是逆序的。所以上面的实行体式格局是extract>transform-runtime>env>stage-0

  1. 运转引入了自定义插件的webpack.config.js

《babel插件入门-AST》

打包文件现在为21.4KiB,显著减小,自定义插件胜利!~

插件文件目次

YUAN-PLUGINS
|
| - node_modules
|   |
|   | - babel-plugins-extract
|           |
|           index.js
|   
| - src
|   | - index.js
|
| - webpack.config.js

以为好玩就关注一下~迎接人人珍藏写批评~~~

    原文作者:wallaceyuan
    原文地址: https://segmentfault.com/a/1190000014094283
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞