什么是宏?

在计算机编程中,宏(Macro)是一种被预处理器处理的代码块或指令,用来在编译时进行代码替换或扩展,以便简化代码编写或实现一些特定功能。宏允许程序员定义自己的简短、易于理解的代码片段,然后在代码中重用这些宏。

Babel 宏也提供一个类似的概念,可以在编译时对代码进行转换,通过 AST 进行代码操作,生成新的代码。

Babel宏和Babel插件的区别

Babel 宏和 Babel 插件都是用来对代码进行转换的工具,唯一的区别就是 Babel 宏可以在编码阶段局部动态指定转换行为,而 Babel 插件是在编译阶段全局代码转换的。

比如要实现一个 console.scope 方法来输出特定格式的日志。

  • 使用 Babel 插件写法
console.scope('hello world')
  • 使用 Babel 宏写法
import scope from 'console.scope.macro'
scope('hello world')

使用 Babel 宏的好处就是

1. scope 方法只在需要的地方引入,不会全局污染,比如 eslint 会提示 scope 方法不存在。

2. 一些工具,比如 create-react-app 已经默认支持宏,不需要额外配置。

3. babel 宏相对于 plugin 更方便编写,可以指定代码片段进行转换。

Babel宏实现

Babel 宏的实现原理是通过 babel-plugin-macroscreateMacro 来实现的,比如要实现一个对象转成 Proxy 的宏。

注意定义宏的文件一定要以 .macro 结尾,比如 console.scope.macro.js

// console.scope.macro.js
const { createMacro } = require('babel-plugin-macros');
function constantMacro({ references, state, babel }) {
  // references 是一个对象,包含了所有引用了宏的地方
  // state 是一个对象,包含了一些有用的方法和属性
  // babel 是 babel-core 的引用,可以用来操作 AST
  const handler = `
    get(target, key) { 
        if (key in target) {
            console.log('get key:', key, 'value:', target[key]);
            return target[key];
        } 
        throw new Error("Property " + key + " does not exist."); 
    }
  `;
  const code = `
    new Proxy($0, {
      ${handler}
    })
 `;
  references.default.forEach(({ parentPath }) => {
    // parentPath 是一个 Babel NodePath,可以用来操作 AST
    // 例如,可以用 parentPath.replaceWith(newNode) 来替换节点
    // 从 AST 中获取参数
    const args = parentPath.get('arguments');
    // 获取args的代码
    const objectStr = args[0].getSource();
    const newNode = babel.template.ast(code, {
      placeholderPattern: /^\$\d+/,
    });
    newNode.expression.arguments[0].name = objectStr;
    // 替换节点
    parentPath.replaceWith(newNode);
  });
}

module.exports = createMacro(constantMacro);

测试用例

tests/index.test.js 文件编写测试用例代码

const pluginTester = require('babel-plugin-tester');
const plugin = require('babel-plugin-macros');

pluginTester({
  plugin,
  snapshot: true,
  tests: [
    {
      title: '转成Proxy对象',
      code: `
        import constant from '../constant.macro';
        const obj = constant({ a: 1, b: 2 });
      `,
    },
  ],
});

可以看到 snap 输出结果

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`macros 1. should work: 1. should work 1`] = `
import constant from '../constant.macro';
const obj = constant({ a: 1, b: 2 });
 
↓ ↓ ↓ ↓ ↓ ↓
const obj = new Proxy(
  { a: 1, b: 2 },
  {
    get(target, key) {
      if (key in target) {
        return target[key];
      }
      throw new Error('Property ' + key + ' does not exist.');
    },
  }
);
`;

上面只是一个简单的代码转换,实际上 Babel 宏可以实现更复杂的代码转换,比如 styled-components 的 css 宏,将 css 转成 className。可以根据实际场景随意编写宏。

本文为原创,未经授权,禁止任何媒体或个人自媒体转载
商业侵权必究,如需授权请联系340443366@qq.com
标签: babel