【babel】plugin

举一个例子,以写一个自动生成API文档为主

plugin

调用插件

// index.js
const { transformFromAstSync } = require('@babel/core');
const  parser = require('@babel/parser');
const autoDocumentPlugin = require('./plugin/auto-document-plugin');
const fs = require('fs');
const path = require('path');

// 读取文件
const sourceCode = fs.readFileSync(path.join(__dirname, './sourceCode.ts'), {
    encoding: 'utf-8'
});

// 转成AST
const ast = parser.parse(sourceCode, {
    sourceType: 'unambiguous',
    plugins: ['typescript']
});

// 插件使用
const { code } = transformFromAstSync(ast, sourceCode, {
    plugins: [[autoDocumentPlugin, {
        outputDir: path.resolve(__dirname, './docs'),
        format: 'markdown'// html / json
    }]]
});

复制代码

plugin模板

plugin 有两种形式

一个函数返回一个对象的格式

// xx-plugin.js
const { declare } = require('@babel/helper-plugin-utils');
// api:  babel 的 api,
// options: 传给plugin的参数
// dirname: 目录名字
const xxPlugin = declare((api, options, dirname) => {
    api.assertVersion(7);
    const xxPlugin = declare((api, options, dirname) => {
        api.assertVersion(7);
        // name 插件的名字
        // inherits 指定继承某个插件,和当前插件的 options 合并,通过 Object.assign 的方式。
        // pre 遍历前调用
        // visitor 指定 traverse 时调用的函数。
        // post 遍历后调用
        // manipulateOptions 用于修改 options,是在插件里面修改配置的方式,比如 babel-plugin-syntax-xx, 一般都会修改 parser options
        return {
            name: "proposal-export-namespace-from",
            inherits: syntaxExportNamespaceFrom.default,
            pre(file) {
                // 代码逻辑
            },
            visitor: {
                FunctionDeclaration(path, state) {
                    // 代码逻辑
                },
            },
            manipulateOptions(opts, parserOpts) {   
                // 告诉 parser 要 parse xxx这个语法,如jsx 
                 parserOpts.plugins.push("xxx");
            },
            post(file) {
                // 代码逻辑
            }
        }
    })
})
复制代码

在babel官网找了几个插件的写法

babel-plugin-proposal-export-default-from

babel-plugin-proposal-function-bind

babel-plugin-proposal-numeric-separator

babel-plugin-syntax-export-default-from

直接返回一个对象

这种方式的插件形式用于不需要处理参数的情况,这种我在官网还没有找到

export default plugin = {
pre(state) {
  this.cache = new Map();
},
visitor: {
  StringLiteral(path, state) {
    this.cache.set(path.node.value, 1);
  },
},
post(state) {
  console.log(this.cache);
},
};
复制代码

visitor模式

visitor模式(访问者模式),作为设计模式的其中一种。访问者模式解决的是数据数据的操作方法之间的耦合,将数据的操作方法独立于数据,使其可以自由演变。因此访问这更适合于那些数据稳定,但是数据的操作方法一遍的环境下。因此当操作方环境改变时,可以自由修改操作方法以适应操作环境,而不用修改原数据,实现操作方法的扩展。

对应到 babel traverse 的实现,就是 AST(数据) 和 visitor(有很多操作方法) 分离,在 traverse(遍历)AST 的时候,调用注册的 visitor 来对其进行处理

路径(path)和作用域(scope)

path:NodePath
复制代码

babel AST 中只包含源码的一些信息,但是操作 AST 时要拿到父节点的信息,并且也需要对 AST 增删改的方法,这些都在 path 对象里。

scope 是作用域信息,javascript 中能生成作用域的就是模块、函数、块等,而且作用域之间会形成嵌套关系,也就是作用域链。babel 在遍历的过程中会生成作用域链保存在 path.scope 中。

path api太强大, 要多用才熟悉

@babel/types

@babel/types

总结

大致了解怎么去写一个babel插件,在转成AST后,在transform阶段运用设计模式的访问者模式(数据和数据的操作方法解耦),然后通过path相关api去实现逻辑,path的类型是NodePath,这个里面包含了AST的path 和scope(作用域链),通过对api的各种操作,实现我们要的结果

visitor: { 
ExportNamedDeclaration(path) { 
  const { node, scope } = path;
}
复制代码

参考

掘金小册babel插件通关秘籍

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享