Babel 中文文档
  • 印记中文
  • 文档
  • 配置
  • 试用
  • 视频
  • 博客
  • 赞助
  • 团队
  • GitHub

›All Blog Posts

All Blog Posts

  • 7.19.0 Released: Stage 3 decorators and more RegExp features!
  • 7.18.0 Released: Destructuring private elements and TypeScript 4.7
  • 7.17.0 Released: RegExp 'v' mode and ... 🥁 decorators!
  • 7.16.0 发布: ESLint 8 和 TypeScript 4.5
  • 7.15.0 发布:支持 Hack-style 管道, TypeScript 枚举常量和 Rhino 目标
  • Babel is used by millions, so why are we running out of money?
  • 7.14.0 Released: New class features enabled by default, TypeScript 4.3, and better CommonJS interop
  • 7.13.0 Released: Records and Tuples, granular compiler assumptions, and top-level targets
  • 7.12.0 Released: TypeScript 4.1, strings as import/export names, and class static blocks
  • 7.11.0 Released: ECMAScript 2021 support in preset-env, TypeScript 4.0 support, printing config and the future of `babel-eslint`
  • The State of babel-eslint
  • 7.10.0 Released: Class Fields in preset-env, '#private in' checks and better React tree-shaking
  • 7.9.0 Released: Smaller preset-env output, Typescript 3.8 support and a new JSX transform
  • 7.8.0 Released: ECMAScript 2020, .mjs configuration files and @babel/cli improvements
  • Babel's Funding Plans
  • 7.7.0 Released: Error recovery and TypeScript 3.7
  • 7.6.0 Released: Private static accessors and V8 intrinsic syntax
  • 7.5.0 Released: dynamic import and F# pipelines
  • The Babel Podcast
  • 7.4.0 Released: core-js 3, static private methods and partial application
  • 7.3.0 Released: Named capturing groups, private instance accessors and smart pipelines
  • 7.2.0 发布:私有实例方法(Private Instance Methods)
  • 在 Babel 中支持 TC39 标准的装饰器
  • 7.1.0 Released: Decorators, Private Static Fields
  • Babel 7 发布
  • Removing Babel's Stage Presets
  • What's Happening With the Pipeline (|>) Proposal?
  • Announcing Babel's New Partnership with trivago!
  • On Consuming (and Publishing) ES2015+ Packages
  • Nearing the 7.0 Release
  • Babel Turns Three
  • Planning for 7.0
  • Zero-config code transformation with babel-plugin-macros
  • Contributing to Babel: Three Lessons to Remember
  • Personal Experiences at Babel #1 — A PR with Unusually High Number of Reviews
  • Babel and Summer of Code 2017
  • Upgrade to Babel 7 (moved)
  • Upgrade to Babel 7 for Tool Authors (WIP)
  • 6.23.0 Released
  • The State of Babel
  • 6.19.0 Released
  • 6.18.0 Released
  • 6.16.0 Released
  • Babili (babel-minify)
  • 6.14.0 Released
  • Babel Doctor
  • Setting up Babel 6
  • 6.0.0 Released
  • React on ES6+
  • Function Bind Syntax
  • 5.0.0 Released
  • Babel 喜爱 React
  • 并非出生而逐渐走向灭亡
  • 2to3
  • 6to5 + esnext

Zero-config code transformation with babel-plugin-macros

September 11, 2017

Kent C. Dodds

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.

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").

I have a few problems with Babel plugins though:

  1. They can lead to confusion because when looking at code in a project, you might not know that there's a plugin transforming that code.
  2. They have to be globally configured or configured out-of-band (in a .babelrc or webpack config).
  3. They can conflict in very confusing ways due to the fact that all babel plugins run simultaneously (on a single walk of Babel's AST).

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!?!?

Introducing babel-plugin-macros 🎣

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.

So what does it look like? Whelp! There are already a few babel-plugin-macros packages out there you can try today!

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:

  1. It's less explicit because there would be no import preval from 'preval.macro' in the source code.
  2. Have to add babel-plugin-preval to your babel configuration.
  3. Need to update your ESLint config to allow for the preval variable as a global.
  4. If you misconfigured babel-plugin-preval you'd get a cryptic runtime error like: Uncaught ReferenceError: preval is not defined.

By using preval.macro with babel-plugin-macros, we don't have any of those problems because:

  1. The import is there and used explicitly.
  2. babel-plugin-macros needs to be added to your config, but only once, then you can use all the macros you'd like (even local macros!)
  3. No need to update ESLint config because it's explicit.
  4. If you misconfigure babel-plugin-macros then you'll get a much more friendly compile time error message that indicates what the actual problem is pointing you to documentation.

So what is it really? The TL;DR is that babel-plugin-macros is a simpler way to write and use Babel transforms.

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 is a way to have no config for non-syntax babel plugins. So many existing babel plugins could be implemented as a macro. Here's another example of babel-plugin-console which exposes a macro version of itself:

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;
}

Now, when that code is run, the scope function does some pretty nifty things:

Browser:

Browser console scoping add100

Node:

Node console scoping add100

Cool right? And using it is just like using any other dependency, except it has all the benefits mentioned above.

Conclusion

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.

Creating a macros is even easier than a regular Babel plugin, but it does require a bit of knowledge around ASTs and Babel. If this is new to you, there are a, few, resources for you 😀

Good luck to you all! 👋

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.

Recent Posts
  • Introducing babel-plugin-macros 🎣
    • Another example
  • Conclusion
Babel 中文文档
文档
学习 ES2015
社区
视频用户Stack OverflowSlack 频道Twitter
更多
博客GitHub 组织GitHub 仓库Website 仓库旧版网址 6.x旧版网址 5.x