使用 babel-plugin-macros 的零配置代码转换
Babel 最初是作为一个转译器出现的,让你可以写最新版本的 ECMAScript 规范,但运行在还不支持这些特性的环境中。不过它现在已经远不止于此。“编译器是新的框架” 的作者 Tom Dale 的观点我非常赞同。我们看到越来越多的库和框架在编译时进行优化。我说的不是语言的语法扩展,而是一些简单的代码转换,让一些原本很难实现的模式变得可行。
🌐 Babel started out as a transpiler to let you write the latest version of the ECMAScript specification but ship to environments that don't implement those features yet. But it has become much more than that. "Compilers are the New Frameworks" says Tom Dale and I could not agree more. We're seeing more and more compile-time optimizations for libraries and frameworks. I'm not talking about syntax extensions to the language, but simple code transformations that enable patterns that would be difficult to accomplish otherwise.
我最喜欢编译器插件的一个原因是,你可以同时用它们来优化用户体验和开发者体验。(想了解更多,参见“如何编写自定义 Babel & ESLint 插件来提升生产力和改善用户体验”)。
🌐 One of my favorite things about compiler plugins is that you can use them to optimize the user experience and developer experience at the same time. (Read more about "How writing custom Babel & ESLint plugins can increase productivity & improve user experience").
不过我对 Babel 插件有一些问题:
🌐 I have a few problems with Babel plugins though:
- 它们可能会导致混淆,因为在查看项目中的代码时,你可能不知道有一个插件正在改变那些代码。
- 它们必须在全局配置,或者通过带外方式配置(在
.babelrc或 webpack 配置中)。 - 由于所有的 Babel 插件都是同时运行的(在 Babel 的 AST 上只遍历一次),它们可能会以非常让人困惑的方式发生冲突。
如果我们能够导入 Babel 插件并直接应用到我们的代码中,这些问题就可以解决了。这意味着转换会更明确,我们不需要把它们添加到配置中,而且插件的顺序可以按导入的顺序执行。那不是很酷吗!?!
🌐 These problems could be solved if we could import Babel plugins and apply them directly to our code. This would mean the transformation is more explicit, we wouldn't need to add them to configuration, and ordering can happen in the order the plugins are imported. Wouldn't that be cool!?!?
介绍 babel-plugin-macros 🎣
🌐 Introducing babel-plugin-macros 🎣
猜猜看!居然有这样的工具存在!babel-plugin-macros 是一个新的 Babel 插件,它可以让你正好做我们正在讨论的事情。这是一种“新”的代码转换方法。它让你可以实现零配置、可导入的代码转换。这个想法 来自 Sunil Pai,我是在 这个 create-react-app 问题 中注意到它的。
🌐 Guess what! A tool like this exists! babel-plugin-macros is a new Babel plugin that allows you to do exactly what we're talking about. It's a "new" approach to code transformation. It enables you to have zero-config, importable code transformations. The idea came from Sunil Pai and caught my attention in this create-react-app issue.
那它看起来怎么样?哎呀!现在已经有几个你今天就可以试用的 babel-plugin-macros 包了!
🌐 So what does it look like? Whelp! There are already a few babel-plugin-macros packages out there you can try today!
这里有一个使用 preval.macro 在用 Next.js 构建的 通用应用 中内联 SVG 的真实例子:
🌐 Here's a real-world example of using preval.macro to inline an SVG in a universal application built with Next.js:
// 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
这有什么酷的?嗯,替代方案看起来会和上面的例子一模一样,只是:
🌐 What's cool about this? Well, the alternative would look exactly like the example above except:
- 它不那么明确,因为源代码中不会有
import preval from 'preval.macro'。 - 必须在你的 Babel 配置中添加
babel-plugin-preval。 - 需要更新你的 ESLint 配置,以允许
preval变量作为全局变量使用。 - 如果你配置错了
babel-plugin-preval,就会得到一个像Uncaught ReferenceError: preval is not defined这样的神秘 运行时 错误。
通过将 preval.macro 与 babel-plugin-macros 一起使用,我们没有任何这些问题,因为:
🌐 By using preval.macro with babel-plugin-macros, we don't have any of those problems because:
- 这个导入存在,并且被明确使用了。
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 🔜。
🌐 There are already several published babel-plugin-macros you can use, including preval.macro, codegen.macro, idx.macro, emotion/macro, tagged-translations/macro, babel-plugin-console/scope.macro, and glamor 🔜.
另一个例子
🌐 Another example
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 函数会做一些相当酷的事情:
🌐 Now, when that code is run, the scope function does some pretty nifty things:
浏览器:

节点:
很酷吧?使用它就像使用其他依赖一样,只不过它拥有上述提到的所有好处。
🌐 Cool right? And using it is just like using any other dependency, except it has all the benefits mentioned above.
结论
🌐 Conclusion
我觉得我们才刚刚开始探索 babel-plugin-macros 的潜力。我希望我们能把它整合进 create-react-app,这样使用 create-react-app 的人就能零配置地获得更多功能。我真的很期待看到更多的 Babel 插件除了已有的功能外,还能提供一个 macro。我迫不及待想看到大家根据自己项目的需求创建专属的宏。
🌐 I think we've only begun to scratch the surface of what babel-plugin-macros can do. I'm hoping that we can land it in create-react-app so folks using create-react-app can have even more power with zero configuration. I'm really excited to see more Babel plugins expose a macro in addition to the existing plugin functionality they already have. I can't wait to see folks create macros that are specific to their project needs.
创建宏甚至比创建普通的 Babel 插件还容易,但它确实需要一些关于 AST 和 Babel 的知识。如果你对此还不熟悉,这里有一些、资料、资源供你参考 😀
祝你们好运!👋
🌐 Good luck to you all! 👋
附言:我应该提一下,语言宏根本不是一个新概念。能够教语言一些新技巧已经存在很久了。事实上,已经有这样的 JavaScript 工具,甚至已经有一个作为 Babel 插件实现的。不过,babel-plugin-macros 的方法略有不同。虽然宏通常与为语言定义新语法相关联,但这完全不是 babel-plugin-macros 的目标。在 babel-plugin-macros 的情况下,更侧重于代码转换。
🌐 P.S. I should mention that language macros are not a new concept at all. Being able to teach a language new tricks has been around for a very long time. In fact, there's already such a tool for JavaScript and even one implemented as a Babel plugin already. babel-plugin-macros takes a slightly different approach however. While macros have often been associated with defining new syntax for a language, that's not the goal of babel-plugin-macros at all. In the case of babel-plugin-macros it's more about code transformations.