零配置代码转换:babel-plugin-macros 实践
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
Babel 最初是作为转译器诞生的,它允许开发者编写最新 ECMAScript 规范的代码,同时兼容尚未实现这些特性的环境。但它的能力远不止于此。Tom Dale 在 "编译器即新框架" 中提出的观点,我深表赞同。我们看到越来越多针对库和框架的编译时优化方案涌现。这里讨论的并非语法扩展,而是通过简单代码转换实现的、原本难以达成的开发模式。
编译器插件最令我欣赏的特性在于,它能同时优化用户体验和开发者体验。(了解更多请阅读"自定义 Babel & ESLint 插件如何提升效率与用户体验")。
但 Babel 插件也存在几个问题:
-
它们可能导致理解混淆:查看项目代码时,开发者可能意识不到有插件正在转换这些代码
-
必须全局配置或在外部配置(通过
.babelrc或 webpack 配置) -
所有 Babel 插件同时运行(基于 AST 的单次遍历),可能引发难以排查的冲突
若能直接导入 Babel 插件并应用到代码中,这些问题便可迎刃而解——转换过程将更显式化,无需额外配置,且加载顺序由导入顺序决定。这难道不酷吗?
隆重推出 babel-plugin-macros 🎣
惊喜来了!这样的工具已经存在!babel-plugin-macros 是一款创新的 Babel 插件,完美实现上述构想。它开创了代码转换的"新"范式,提供零配置、可导入式的代码转换方案。该方案源自 Sunil Pai 的创意,我在这个 create-react-app 议题中首次注意到它。
实际效果如何?看好了!现已有多款 babel-plugin-macros 生态包可供体验!
以下是在基于 Next.js 构建的通用应用中,使用 preval.macro 内联 SVG 的真实案例:
// search.js
// this file runs in the browser
import preval from 'preval.macro'
import glamorous from 'glamorous'
const base64SearchSVG = preval.require('./search-svg')
// this will be transpiled to something like:
// const base64SearchSVG = 'PD94bWwgdmVyc2lv...etc...')
const SearchBox = glamorous.input('algolia_searchbox', props => ({
backgroundImage: `url("data:image/svg+xml;base64,${base64SearchSVG}")`,
// ...
}))
// search-svg.js
// this file runs at build-time only
// because it's required using preval.require function, which is a macro!
const fs = require('fs')
const path = require('path')
const svgPath = path.join(__dirname, 'svgs/search.svg')
const svgString = fs.readFileSync(svgPath, 'utf8')
const base64String = new Buffer(svgString).toString('base64')
module.exports = base64String
它的精妙之处何在?对比传统方案就能一目了然:
-
传统方案不够显式化:源代码中不会出现
import preval from 'preval.macro'语句 -
必须将
babel-plugin-preval添加到你的 Babel 配置中。 -
需要更新 ESLint 配置,以允许将
preval变量作为全局变量。 -
如果错误配置了
babel-plugin-preval,你会得到一个晦涩的运行时错误,例如:Uncaught ReferenceError: preval is not defined。
通过将 preval.macro 与 babel-plugin-macros 结合使用,我们不会遇到上述任何问题,因为:
-
导入是显式存在并使用的。
-
你只需将
babel-plugin-macros添加到配置中一次,之后就可以使用所有你想要的宏(甚至是本地宏!) -
因为是显式导入,所以无需更新 ESLint 配置。
-
如果错误配置了
babel-plugin-macros,你会得到一个更加友好的编译时错误消息,它会指出实际问题并引导你查看文档。
那么它到底是什么?简而言之,babel-plugin-macros 是一种更简单的方式来编写和使用 Babel 转换。
目前已有多个已发布的 babel-plugin-macros 可供使用,包括 preval.macro、codegen.macro、idx.macro、emotion/macro、tagged-translations/macro、babel-plugin-console/scope.macro,以及即将到来的 glamor 🔜。
另一个例子
babel-plugin-macros 是一种无需配置非语法 Babel 插件的方法。许多现有的 Babel 插件都可以作为宏来实现。下面是 babel-plugin-console 的另一个例子,它提供了自身的宏版本:
import scope from 'babel-plugin-console/scope.macro'
function add100(a) {
const oneHundred = 100
scope('Add 100 to another number')
return add(a, oneHundred)
}
function add(a, b) {
return a + b;
}
现在,当代码运行时,scope 函数会做一些非常巧妙的事情:
浏览器:

Node:
很酷吧?而且使用它就像使用任何其他依赖一样,只是它具备上述所有优点。
结论
我认为我们才刚刚开始挖掘 babel-plugin-macros 的潜力。我希望它能集成到 create-react-app 中,这样使用 create-react-app 的开发者就能在零配置的情况下获得更强大的功能。我非常期待看到更多的 Babel 插件除了现有的插件功能外,还能提供 macro 版本。我迫不及待地想看到开发者们创建满足其项目特定需求的宏。
创建宏比编写常规 Babel 插件更简单,但这需要掌握一些关于抽象语法树(AST)和 Babel 的知识。如果你是这方面的新手,可以参考这些、优质资源 😀
祝各位开发顺利! 👋
附注:需要说明的是,语言宏并非全新概念。通过宏为语言添加新特性的做法早已存在多年。事实上,JavaScript 领域已有类似工具,甚至还有基于 Babel 插件实现的方案。但 babel-plugin-macros 采取了略有不同的实现路径——传统宏通常与定义新语法关联,而 babel-plugin-macros 的核心目标并非如此。实际上,babel-plugin-macros 更侧重于代码转换。