Macros are Compile-time libraries (Shawn Wang)
Babel Macros are Compile-Time Execution
macros is both a plugin and a proposal of a standard interface for npm-modules that provide compile-time code transformations (the same as a Babel plugin) but without requiring the user to add the babel plugin to their Babel configuration system. To use it
- You have to install the babel-macros-plugin
- You have to add
macros
to the plugins section of your .babelrc file:{ "plugins": ["macros"] }
Here is an example: The yaml macro is a Babel macro for loading YAML files. We first install the macro:
npm install --save-dev yaml.macro
then we write some YAML file file.yml:
- YAML file
- with: some contents
then we write some JavaScript file use-yml.cjs:
const yaml = require('yaml.macro');
const foo = yaml('./file.yml');
then we simple run babel src/use-yml.cjs
and we get the following result:
➜ babel-macros git:(main) ✗ npx babel src/use-yml.cjs
const foo = ["YAML file", {
"with": "some contents"
}];
See /docs/yaml.md for more information on the yaml.macro
.
A macro is a JavaScript module whose name matches /[./]macro(\.c?js)?$/
that exports a function. Here's a simple example:
const { createMacro, MacroError } = require('babel-plugin-macros')
module.exports = createMacro(myMacro)
function myMacro({references, state, babel}) {
...
}
createMacro
is simply a function that ensures your macro is only
called in the context of a babel transpilation and will throw an
error with a helpful message if someone does not have babel-plugin-macros
configured correctly.
Use MacroError
to throw an error inside your macro.
The function you export from your macro module
function myMacro({references, state, babel}) {
...
}
is called with an object that has the following properties:
references
is an object that contains arrays of all the references to things imported from the macro. They are keyed based on the name of the import. The items in each array are the paths to the references.state
The state of the file being traversed. It's the second argument you receive in a visitor function in a normal babel plugin. Thisstate
object is used to store and share information across different visitor methods. It can hold any kind of data that the macro might need to maintain state across different nodes of the AST. The state object typically contains the following properties:opts
: Contains the options passed to the plugin.file
: Provides information about the file being processed, such as the filename and the AST.path
: The current path being visited (available within visitor methods).- Custom properties defined by the plugin to store intermediate results or configuration data.
babel
is thebabel-plugin-macros
module. It is also the same thing you get if yourequire('babel-core')
.
Here is the full code of the yaml macro:
module.exports = createMacro(yamlMacro)
function yamlMacro({ references, state }) {
for (const { parentPath } of references.default) {
if (parentPath.type !== 'CallExpression')
throw new MacroError('yaml.macro only supports usage as a function call')
let argPath, argOptions
try {
const args = parentPath.get('arguments')
argPath = args[0].evaluate().value
if (args.length > 1) argOptions = args[1].evaluate().value
} catch (error) {
error.message = `yaml.macro argument evaluation failed: ${error.message}`
throw error
}
/* istanbul ignore if */
if (!argPath) throw new MacroError('yaml.macro argument evaluation failed')
const dirname = path.dirname(state.file.opts.filename)
const fullPath = require.resolve(argPath, { paths: [dirname] })
const fileContent = fs.readFileSync(fullPath, { encoding: 'utf-8' })
const options = Object.assign({}, argOptions, {
intAsBigInt: false,
json: true,
mapAsMap: false
})
const res = YAML.parse(fileContent, options)
const exp = parseExpression(JSON.stringify(res))
parentPath.replaceWith(exp)
}
}
It can be published to the npm registry (for generic macros) or used locally (for domain-specific macros).
There are two parts to the babel-plugin-macros
API:
- The filename convention
- The way that
babel-plugin-macros
determines whether to run a macro is based on the source string of therequire
statement. It must match this regex:/[./]macro(\.c?js)?$/
. For instance:require('yaml.macro')
- The way that
- The function you export
See
- babel-plugin-macros Usage for macros authors for the complete information.
- eemeli/yaml.macro/macro.js
See section docs/idx-macro.md.
- Babel macros by Tan Liu Hau
- I Can Babel Macros (and So Can You!) by Shawn "swyx" Wang in JSConf Hawaii 2019 (Growing a Language)
- Learning Babel Macros: ULL-ESIT-PL/learning-babel-macros by Casiano
- babel-plugin-macros: Usage for macros authors. A tutorial on how to write Babel macros by Kent C. Dodds
- My PL notes on Babel are here: https://ull-pl.vercel.app/topics/tree-transformations/babel
- ULL-ESIT-PL/babel-learning tutorial on the Babel Transpiler by Casiano
- Babel Handbook at jamiebuilds/babel-handbook This document covers how to create Babel plugins.
- A boilerplate monorepo for people writing babel plugins in normal plugin form as well as babel-plugin-macros form by Shawn "swyx" Wang
- Awesome Babel A list of awesome Babel plugins, presets, etc.