The translation cites the article Babel Macros
preface
Webpack and Babel are unavoidable tools for modern front-end projects.
Babel is relatively unknown compared to the buzz around WebPack, and popular positions like WebPack configuration engineers, but that’s not to say it’s not important.
The core of @babel/ Polyfill relies on the downloads of core.js, far outpacing popular libraries such as Webpack and React.
Babel configuration
Babel can be seen as a compiler that converts syntax that is not supported by the browser so that it works in the browser.
For different syntax, Babel provides a plugin for syntax conversion, and Preset can be used to unify plugin and simplify the configuration of Babel.
The Babel configuration file is made up of these specific plugin and preset, and Babel automatically converts the syntax once it is configured.
Existing problems
Using this approach for configuration and code conversions, all conversions are global and implicit, and custom syntax and functions for specific functions are not supported.
Take a chestnut
Before the optional-chaining proposal is adopted, there are many ways to handle props like this, right? .user? .friends?.[0]?. Friend attribute value
The most common way to write it, not easy to read, but the most effective
const firstFriend = props.user && props.user.friends && props.user.friends[0] ? props.user.friends[0].friend : null; // or use the ternary operator const firstFriend = props? props.user ? props.user.friends ? props.user.friends ? props.user.friends[0] ? props.user.friends[0].friend : null : null : null : null : null;Copy the code
Easy to write, easy to read, but adds runtime overhead
function idx(input, accessor) {
try {
return accessor(input);
} catch (e) {
return null;
}
}
const firstFriend = idx(props, _ => _.user.friends[0].friend);
Copy the code
Is there a way to have the advantage of both approaches that is easy to write and easy to read without adding runtime overhead to a try-catch?
Facebookincubator/IDX provides an idea that uses the Babel plugin to analyze which files reference IDX using require or import, and then replace where IDX is used. Here is an example
Install
NPM install idx babel-plugin-idx or yarn add idx babel-plugin-idx"babel-plugin-idx"]]}Copy the code
Usage
import idx from 'idx';
function getFriends() {
return idx(props, _ => _.user.friends[0].friends);
}
Copy the code
The final code is converted to
function getFriends() {
return props.user == null
? props.user
: props.user.friends == null
? props.user.friends
: props.user.friends[0] == null
? props.user.friends[0]
: props.user.friends[0].friends;
}
Copy the code
The above scheme basically achieves simple writing and reading, without increasing the runtime overhead. But there are some problems:
- First, write a Babel plugin
- A special marker is required to locate the syntax that needs to be transformed
- The Babel configuration file needs to be updated
- If there is a problem with the IDX function, it can be confusing to locate the problem. With the syntax used, you would look for the idX definition file, but IDx is just a special tag for Babel plugin. The real code is in babel-plugin-idx
Babel macros
What is Babel Macros
Babel Macros is a concept that appears in Babel-plugin-Macros and can be understood as a Babel macro. It is a standard interface for converting compile-time code into runtime code.
When Babel is compiled, babel-plugin-Macros traverses all modules looking for references ending in. Macro and passes these references to the. Marco file for code conversion, replacing the original references with the converted code.
If macros are exported by default, you can name references however you like; If the export is named, it can also be renamed using the AS syntax.
Use Babel Macros to handle IDX
Install
NPM install babel-plugin-macros --save-dev // Babel'babel-plugin-macros']};Copy the code
macro
// src/utils/idx.macro.js
const { createMacro } = require('babel-plugin-macros');
module.exports = createMacro(({ state, references }) => {
references.default.forEach(referencePath => {
idx_transform(referencePath.parentPath, state);
});
});
Copy the code
use
// src/index.js
import idx from './utils/idx.macro';
function getFriends() {
return idx(props, _ => _.user.friends[0].friends);
}
Copy the code
Marco, IDX. macro corresponding to IDX is already available in the community
Problem with Babel cache
In a real project, we will cache babel-loader and cache all files using babel-Loader to speed up webpack compilation.
Cache also works for files using Babel Marco. If Marco is not a pure function, the compiled results may differ from the expected results.
For example, if a macro works with a JSON file, the target JSON file has been modified, but no code changes have been made to the file using the macro, the code in the cache will still be used, which is the result of the last parsing of the unchanged JSON file.
The solution is to turn off the caching function of the babel-loader directly, but this may affect the compilation speed of the project; The second option is to add a comment where macro is used to tell babel-Loader that it needs to be recompiled.
How to write a Marco
Babel-plugin-marcos provides a specification for implementing macros, but it is based on Babel. So to develop a Marco, you have to have some understanding of Babel.
First, familiarize yourself with Babel and learn how to write a Babel plug-in manual.
Babel-plugin-marcos provides the official Marco development manual, hand Book for Macros Authors.
Here we implement a try-catch Marco
try-catch.macro
During project development, it is common to encounter errors that are not thrown, resulting in problems that cannot be located. You want a simple way to try catch wrapping a function.
On-demand transformation is done through babel-plugin-Marcos, rather than global substitution through Babel’s plug-ins.
Decorator syntax can’t handle simple functions, so flow syntax is used here. Use the flow syntax to mark the method to be converted, where the syntactic method may not conform to the Flow standard.
NPM install — save-dev@babel /preset — flow is used, so preset is installed. NPM install — save-dev@babel /preset-flow. The use of flow here may not be in compliance with the specification, just to simplify the operation.
// marco.js
const { createMacro } = require('babel-plugin-macros');
functionmacro({ babel, references, state }) { const { types, template } = babel; // Babel's template function, Const wrapFunctionWithTryCatch = template(' {try {BODY} catch(e) {console.error(e); }} `); references.TryCatch.forEach(reference => { const parentFunction = reference.getFunctionParent();if(! parentFunction) {return;
}
const body = parentFunction.node.body.body;
if(! body || ! body.length) {return; } // Functions already wrapped in a try catch are not processedif (body.length === 1 && types.isTryStatement(body[0])) {
return; } // Replace the old parentfunction.get () function with the template function.'body').replaceWith(wrapFunctionWithTryCatch({
BODY: body,
}));
});
}
module.exports = createMacro(macro)
Copy the code
Marco needs to be a JS module that wraps and exports the transformation function using the createMacro method exported by babel-plugin-Macros.
The Macro function takes an object with the three commonly used properties Babel, state, and References.
- Babel is
babel-plugin-macros
Module, which contains all the functions of Babel - State is the second parameter of the visitor in the regular Babel plug-in
- References are all references to Marco in the module
The final result
import { TryCatch } from 'try-catch.macro';
var a = (b): TryCatch => {
return b;
}
class Test {
print = (): TryCatch => {
const data = this.getData();
returndata; }} ↓ ↓ ↓ ↓ ↓ var a = (b) => {try {return b;
} catch(e) {
console.error(e);
}
}
class Test {
print = () => {
try {
const data = this.getData();
returndata; } catch(e) { console.log(e); }}}Copy the code
The Github repository is try-catch-macro
conclusion
Babel-plugin-marcos gives us another way to transform code on demand through explicit references.
Through the standardized interface definition, the development specification of Marco is guaranteed.
Compile-time code conversion can reduce runtime overhead in some scenarios.
The community also has a lot of high-quality macro open source, basically all CSS in JS solutions provide the corresponding Marco, reduce the dependency of installation, code is more intuitive.
link
Babel macros
Manual of the Babel plug-in
hand book for macros authors
awesome-babel-macros