Finally started learning Babel
Babel official Chinese document
Babel official English document
What is Babel?
Babel is a toolchain for converting ECMAScript 2015+ version code into backwardly compatible JavaScript syntax so it can run in current and older versions of browsers or other environments. Here’s a list of things Babel can do for you:
- The syntax conversion
- Polyfill missing features in the target environment (via @babel/ Polyfill module)
- Source code conversion (Codemods)
Still not sure what a compiler is?
It is highly recommended to find out through the the-super-tiny-Compiler project.
The working process of a compiler
Parse (lexical + grammar) => transform => generate
In the case of the -super-tin-Compiler project, the (Add 2 (Subtract 4 2)) code needs to be processed to create Add (2, subtract(4, 2)); The code.
- Lexical Analysis
Parsing is generally divided into two stages: Lexical Analysis and Syntactic Analysis.
Lexical analysis takes raw code and splits it into things called Tokens. This is done in a Tokenizer or Lexer
The Token is an array made up of fragments of code statements. They can be numbers, labels, punctuation marks, operators, or anything else. Subtract 4 2) After a lexical analyzer, the Token is found as follows:
[{"type": "paren"."value": "(" },
{ "type": "name"."value": "add"},
{ "type": "number"."value": "2"},
{ "type": "paren"."value": "("},
{ "type": "name"."value": "subtract"},
{ "type": "number"."value": "4"},
{ "type": "number"."value": "2"},
{ "type": "paren"."value": ")"},
{ "type": "paren"."value": ")"}]Copy the code
- Syntactic Analysis
Parsing takes previously generated tokens and converts them into an abstract representation that describes each fragment of a code statement and the relationship between them. This is called intermediate representation or Abstract Syntax Tree (AST for short)
Websites to assist in development:
astexplorer
ast visualizer
An abstract syntax tree is a deeply nested object that represents the code itself in a more manageable way and gives us more information. The Token after lexical analysis is processed by the parser and the AST is as follows:
{
"type": "Program"."body": [{"type": "CallExpression"."name": "add"."params": [{"type": "NumberLiteral"."value": "2"
},
{
"type": "CallExpression"."name": "subtract"."params": [{"type": "NumberLiteral"."value": "4"
},
{
"type": "NumberLiteral"."value": "2"}]."_context": [{"type": "NumberLiteral"."value": "4"
},
{
"type": "NumberLiteral"."value": "2"}}]]."_context": [{"type": "NumberLiteral"."value": "2"
},
{
"type": "CallExpression"."callee": {
"type": "Identifier"."name": "subtract"
},
"arguments": [{"type": "NumberLiteral"."value": "4"
},
{
"type": "NumberLiteral"."value": "2"}]}],"_context": [{"type": "ExpressionStatement"."expression": {
"type": "CallExpression"."callee": {
"type": "Identifier"."name": "add"
},
"arguments": [{"type": "NumberLiteral"."value": "2"
},
{
"type": "CallExpression"."callee": {
"type": "Identifier"."name": "subtract"
},
"arguments": [{"type": "NumberLiteral"."value": "4"
},
{
"type": "NumberLiteral"."value": "2"}]}]}}Copy the code
- Transformation
These nodes can be added, moved, or replaced when transforming an AST, or a new AST can be generated from an existing AST
Since we need to convert the input code into a new language in our example, we will focus on generating a brand new AST for the new language, see the-super-tiny-Compiler.
{
"type": "Program"."body": [{"type": "ExpressionStatement"."expression": {
"type": "CallExpression"."callee": {
"type": "Identifier"."name": "add"
},
"arguments": [{"type": "NumberLiteral"."value": "2"
},
{
"type": "CallExpression"."callee": {
"type": "Identifier"."name": "subtract"
},
"arguments": [{"type": "NumberLiteral"."value": "4"
},
{
"type": "NumberLiteral"."value": "2"}]}]}}Copy the code
- Code generation
The last stage of the compiler is code generation, which sometimes overlaps with transformation, but the main part of code generation is to output code according to the AST.
add(2, subtract(4.2));
Copy the code
Babel in the package
We’ve shown you how a compiler works. Babel works in much the same way
@babel/babel-parser is responsible for parsing into AST from Babel.
var result = babel.transformSync("code();", options);
result.code;
result.map;
result.ast;
Copy the code
@babel/babel-traverse are the packets that are responsible for the transitions in Babel
import * as parser from "@babel/parser";
import traverse from "@babel/traverse";
const code = `function square(n) { return n * n; } `;
const ast = parser.parse(code);
traverse(ast, {
enter(path) {
if (path.isIdentifier({ name: "n" })) {
path.node.name = "x"; }}});Copy the code
@babel/ Generator is the package in Babel that is responsible for generating new code from the AST
import { parse } from "@babel/parser";
import generate from "@babel/generator";
const code = "class Example {}";
const ast = parse(code);
const output = generate(
ast,
{
/* options */
},
code
);
Copy the code
Configure the Babel
The official documentation
In fact, just using the package above, so far by running Babel itself, you haven’t “translated” the code, you’ve just copied it from one place to another.
That’s because we haven’t told Babel what to do yet.
Because Babel is a general-purpose compiler that can be used in a variety of ways, it does nothing by default. You have to tell Babel exactly what to do. If you want Babel to do any real work, you need to add plug-ins to it.
There are several ways to configure Babel
- Create.babelrc in the root directory of the project
{
"presets": [...]. ."plugins": [...]. }Copy the code
- Create babel.config.json in the root directory of the project
{
"presets": [...]. ."plugins": [...]. }Copy the code
- You can also write babel.config.json and.babelrc.json files in JavaScript:
constpresets = [ ... ] ;constplugins = [ ... ] ;module.exports = { presets, plugins };
Copy the code
You can also call any of Node.js’s apis, such as dynamic configuration based on the process environment:
constpresets = [ ... ] ;constplugins = [ ... ] ;if (process.env["ENV"= = ="prod") { plugins.push(...) ; }module.exports = { presets, plugins };
Copy the code
Babel plug-in
There are many official plug-ins, which can be divided into the following categories
- The transformation of ES new features
- Modular transformation
- Function in the experiment
- Plug-ins that reduce the size of your code
- React plugin
- other
To get a feel for Babel, take the @babel/plugin-transform-arrow-functions plug-in as an example. The plugin-transform-arrow-functions plugin converts our ES syntax arrow functions to normal function form.
Using the CLI
- Initialize the project and create a new SRC directory
npm init -y
Copy the code
- Install @babel/core, @babel/cli
Since we now need to experience Babel programmatically and want to be able to compile our code by typing Babel on the command line, we need to install the @babel/core and @babel/ CLI packages
cnpm install -D @babel/core @babel/cli
Copy the code
The @babel/core package actually provides parsing > transform > generation. As can be seen from the package.json package, it contains @babel/ Parser, @babel/traverse and @babel/ Generator
"dependencies": {
"@babel/code-frame": "The workspace: ^ 7.12.13"."@babel/generator": "The workspace: ^ 7.13.0"./ / generated
"@babel/helper-compilation-targets": "The workspace: ^ 7.13.0"."@babel/helper-module-transforms": "The workspace: ^ 7.13.0"."@babel/helpers": "The workspace: ^ 7.13.0"."@babel/parser": "The workspace: ^ 7.13.0"./ / parsing
"@babel/template": "The workspace: ^ 7.12.13"."@babel/traverse": "The workspace: ^ 7.13.0"./ / conversion
"@babel/types": "The workspace: ^ 7.13.0"."convert-source-map": "^ 1.7.0"."debug": "^ 4.1.0." "."escape-string-regexp": "condition:BABEL_8_BREAKING ? ^ 4.0.0:"."gensync": 2 "" ^ 1.0.0 - beta.."json5": "^ 2.1.2"."lodash": "^ 4.17.19"."semver": "condition:BABEL_8_BREAKING ? ^ sections 7.3.4:7.0.0."."source-map": "^ 0.5.0"
},
Copy the code
- Install @babel/plugin-transform-arrow-functions plugin
cnpm install --save-dev @babel/plugin-transform-arrow-functions
Copy the code
- Add Babel compiled command to pakage.json file (you can also add command line input)
"scripts": {
"build:babel": "babel src/arrow-functions.js -d dist --plugins @babel/plugin-transform-arrow-functions"."test": "echo \"Error: no test specified\" && exit 1"
},
Copy the code
- Create a new arrow-functions.js file in the SRC directory and write the code
const func = () = > {
console.log('Hello Babel');
}
Copy the code
- Run the NPM run build: Babel command on the terminal to view the compiled result in the dsit directory
const func = function () {
console.log('Hello Babel');
};
Copy the code
As you can see, our arrow function syntax is translated by Babel
Using babel.config.json
In addition to entering the plug-in configuration from the command line, you can also configure Babel by writing configuration files as follows
- Create the babel.config.json file in the project root directory
This time, we added a plugin to babel.config.json :plugin-transform-function-name
{
"plugins": [
"@babel/plugin-transform-arrow-functions"."@babel/plugin-transform-function-name"]}Copy the code
- Installing a plug-in
cnpm install --save-dev @babel/plugin-transform-function-name
Copy the code
- Add the build:babel-config command to package.json
"scripts": {
"build:babel": "babel src/arrow-functions.js -d dist --plugins @babel/plugin-transform-arrow-functions"."build:babel-config": "babel src/arrow-functions.js -d dist"."test": "echo \"Error: no test specified\" && exit 1"
},
Copy the code
- Run NPM run build:babel-config and view the results
const func = function func() {
console.log('Hello Babel');
};
Copy the code
We can see that not only has the arrow function been converted, but the transform-function-name plug-in has also been converted to the function name.
Babel preset (Presets)
As you can see from the plugin example, Babel’s design idea is that we need to explicitly tell him that he needs to do A, B, and C before he does it. If preset is a hassle for you, preset can be used as a combination of Babel plugins and even as a shared options configuration.
The official Preset
- @babel/preset-env
- @babel/preset-flow
- @babel/preset-react
- @babel/preset-typescript
Besides, you can write a preset yourself
There is a difference in the running order between plugin and preset. Preset is run in reverse order, plugin is run in sequence, and the priority of plugin is higher than preset.
babel-polyfill
The Babel plug-in compiles almost all new JavaScript syntax, but not for APIs. For example, the following code to compile contains the arrow function:
function addAll() {
return Array.from(arguments).reduce((a, b) = > a + b);
}
Copy the code
When compiled, it looks like this:
function addAll() {
return Array.from(arguments).reduce(function(a, b) {
return a + b;
});
}
Copy the code
Although the syntax of the arrow function is translated by Babel, the array. from API is not translated and the reality is that not all browsers support the array. from API
Uncaught TypeError: Array.from is not a function
Copy the code
To solve this problem, we use a technique called polyfill(code supplement, also translated as shim). Polyfill in Babel is essentially a combination of core-JS and Facebook’s Regenerator. Its purpose is to simulate a complete ES6 runtime environment, so it polyfills promises, maps, and other types in the form of global variables. Prototype objects are also contaminated with array.prototype.includes ().
It should be noted that since Babel 7.4.0 @babel-polyfill has been deprecated and removed in later versions, It is recommended that core-js/stable and regenerator-Runtime/Runtime be introduced directly and used in tandem with @babel/preset-env
Compiled, we found that the automatic introduces the core – js/modules/es. Array. From the js core – js will help us to low version of the browser compatibility
. require("core-js/modules/es.array.from.js"); . function addAll() { return Array.from(arguments).reduce(function (a, b) { return a + b; }); }Copy the code
Be aware of the difference between plugins and polyfills. Plugins can convert advanced JavaScript syntax into backward compatible syntax by reinforcing the conversion steps in the compiler. Polyfill is a syntactic supplement to APIs like arra. from.
Babel – the runtime and plugin – transform – runtime
Babel-runtime is a function called regenerator-Runtime that contains helpers for modular Babel operations. .
Babel-runtime is usually used with plugin-transform-Runtime.
Suppose you have code like this:
class Circle {}
Copy the code
Be converted into
function _classCallCheck(instance, Constructor) {
/ /...
}
var Circle = function Circle() {
_classCallCheck(this, Circle);
};
Copy the code
When we have multiple files that use this syntax, it means that we repeatedly declare the _classCallCheck function in each file.
Using @babel/ plugin-transform-Runtime will import _classCallCheck from @babel/ Runtime to avoid re-importing (similar to extracting public methods).
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");
var Circle = function Circle() {
_classCallCheck(this, Circle);
};
Copy the code
polyfill vs babel-runtime?
If you are careful, you may notice that both Ployfill and babel-Runtime use corejs and regenerator-Runtime. What’s the difference?
If you import core-js or @babel/ Polyfill directly and the built-in components it provides, such as Promise, Set, and Map, these will pollute the global scope. While this is fine for applications or command-line tools, it can be a problem if your code is a library that you plan to release for others to use, or if you don’t have complete control over the environment in which your code runs.
Therefore, babel-Polyfill is officially recommended to be used in independent business development, even if global and prototype contamination is not a big impact; Babel-runtime is suitable for third-party library development without global contamination.
reference
Babel’s official website
A suggested compiler
Babel User manual
core-js@3, babel and a look into the future
Babel went from starter to runner