如何实现一个 Babel Macros?

来源:http://www.chinese-glasses.com 作者:Web前端 人气:162 发布时间:2020-03-31
摘要:时间: 2019-10-30阅读: 169标签: babel 时间: 2019-09-24阅读: 125标签: 插件 通过 babel插件,我们很容易的就在编译时将某些代码转换成其他代码以实现某些优化。例如babel-plugin-lodash可以帮我们将

时间: 2019-10-30阅读: 169标签: babel

时间: 2019-09-24阅读: 125标签: 插件

通过 babel 插件,我们很容易的就在编译时将某些代码转换成其他代码以实现某些优化。例如babel-plugin-lodash可以帮我们将直接 import 的 lodash 替换成能够进行 tree shaking 的代码;通过babel-plugin-preval在编译时执行脚本并使用返回值原位替换。

一、babel介绍

一切看起来都很美好,但实际上在使用 babel 插件时我们还需要对.babelrc或者babel.config.js进行配置。

Babel 是 JavaScript 编译器,更确切地说是源码到源码的编译器,通常也叫做“转换编译器(transpiler)”。 意思是说你为 Babel 提供一些 JavaScript 代码,Babel 更改这些代码,然后返回给你新生成的代码。在这个源码到源码的转换过程当中,抽象语法树,即AST,起到了重要的作用。

{ "plugins": ["preval"]}

二、抽象语法树(AST)

在暴露 babel 配置文件的项目下或许还能够接受,但在create-react-app下就不得不破坏原来的和谐, eject 一下配置再进行相关的配置了。

抽象语法树(Abstract Syntax Tree)也称为AST语法树,指的是源代码语法所对应的树状结构。也就是说,一种编程语言的源代码,通过构建语法树的形式将源代码中的语句映射到树中的每一个节点上。和我们的常说的虚拟DOM有点像,虚拟dom是用json的格式把DOM结构抽象成js对象,用于描述DOM的结构,每个节点的类型、属性等等,类似的AST是把js的源代码抽象为js对象的格式,以方便描述这段代码的语法。程序代码本身被映射成为一棵语法树,通过操纵语法树,我们能够精准的获得程序代码中的每一个精确的节点。例如声明语句,赋值语句等。

有没有什么更好的方式呢?有的,我们可以用babel-plugin-macros

三、babel的处理步骤

babel-plugin-macros 是什么?

Babel 的三个主要处理步骤分别是:解析(parse)转换(transform)生成(generate)

babel-plugin-macros 显而易见是一个 babel 插件,它提供了一种零配置编译时替换代码的方式。我们只需要在 babel 配置里添加 babel-plugin-macros 插件配置就可以使用了。显然这个 “零配置” 是把自身除外的。但别担心,create-react-app 已经内置了这个插件,可以开箱即用。

解析步骤接收代码并输出 AST

{ "plugins": ["macros"]}

转换步骤接收 AST 并对其进行遍历,在此过程中对节点进行添加、更新及移除等操作。 这是 Babel 或是其他编译器中最复杂的过程,同时也是插件将要介入工作的部分。在babel-loader中有两种方式可以配置babel插件,我们经常会配置:

然后就可以开始真正的零配置体验,引入我们需要的 macro 直接使用。

rules: [ { test: /.(js|jsx)$/, loader: 'babel-loader', exclude: /node_modules/, options: { presets: ["env", 'stage-0'], plugins: [ ["extract", { "library": "lodash" }], ["transform-runtime", {}] ] }, }]
// 编译前import preval from 'preval.macro';const one = preval`module.exports = 1 + 2 - 1 - 1`;// 编译后const one = 3;

注意:plugins 的插件使用顺序是顺序的,而 preset 则是逆序的。所以上面的执行方式是extracttransform-runtimestage-0env

与 babel-plugin-preval 相比,我们不在需要再进行额外的配置,而是通过 import macro 来使用对应的功能。babel 在编译期会读取以 .macro 结尾的包,并执行对应的逻辑来替换代码,这种方式比插件来的更加直观,我们再也不会出现 “这个 preval 是哪里引进来?” 的疑问了。

代码生成步骤把最终(经过一系列转换之后)的 AST 转换成字符串形式的代码,同时还会创建源码映射(source maps)。代码生成比较简单,就是深度优先遍历整个 AST,然后构建可以表示转换后代码的字符串。

那么怎么实现一个 babel macros 呢?

有了以上的基础知识之后我们看看babel插件是怎么编写的。

实现一个 Babel macros

四、编写一个简单的babel插件

假设我们有这么一个场景:我们的项目中包括前后端的代码,后端的 Node.js 通过dotenv读取项目根目录下的.env获取某些配置,现在我们有一些前端 JavaScript 代码也需要使用到.env里到某些配置,但不能把所有的配置都暴露到 JavaScript 中。

1、插件格式

一般情况下,我们可以将 .env 中的某些配置传入 webpack 的 DefinePlugin 插件中,前端代码通过读取全局变量的方式进行访问。现在我们通过 Babel macros 的方式来实现如下效果:

先从一个接收了当前babel对象作为参数的function开始。

# .envNAME=ahonnNUMBER=123

// 编译前import dotenv from 'dotenv.macro';const NAME = dotenv('NAME');const NUMBER = dotenv('NUMBER');// 编译后const NAME = "ahonn";const NUMBER = "123";
export default function(babel) { // plugin contents}

创建 Macro

我们经常会这样写:

babel-plugin-macros 会把引入的 .macro 或者 .macro.js 当成宏进行处理,所有首先我们需要创建一个名为 dotenv.macro.js 的文件,并且这个文件导出的应该是一个通过createMacro包装后的函数。

export default function({ types: t }) { //}

如果没有通过createMacro进行包装的话,执行babel就会提示:The macro imported from "../../dotenv.macro" must be wrapped in "createMacro" which you can get from "babel-plugin-macros".

接着返回一个对象,其visitor属性是这个插件的主要访问者。

const { createMacro } = require('babel-plugin-macros');module.exports = createMacro(({ references, state, babel }) = { // TODO});
export default function({ types: t }) { return { visitor: { // visitor contents } };};

传入createMacro的函数接受三个参数:

Visitor 中的每个函数接收2个参数:path和state;

references: 编译的代码中对该宏的引用state: 编译状态信息babel: babel-core 对象,与require(‘@babel/core’)相同

export default function({ types: t }) { return { visitor: { Identifier(path, state) {}, ASTNodeTypeHere(path, state) {} } };};

在我们的例子中 references 的值是{ default: [ NodePath {...} ] },这里的default中的 NodePath 即是上面编译前代码中dotenv调用在 AST 中的节点。(如果对 AST 或者 babel 插件开发不太熟悉的话,推荐阅读babel-handbook/plugin-handbook.md)

Path是表示两个节点之间连接的对象。表示AST中节点之间的相互关联关系。例如,如果有下面这样一个节点及其子节点︰

本文由10bet发布于Web前端,转载请注明出处:如何实现一个 Babel Macros?

关键词:

最火资讯