Dissect Babel — Take one small step forward for the architect

When talking about Babel’s role, many people’s first reaction is: to implement the API Polyfill.

In fact, Babel is much more than that as a cornerstone of front-end engineering.

As a large family, there are many concepts in the Babel ecosystem, such as: Preset, Plugin, Runtime, etc.

These concepts make Babel intimidating for beginners, and the babel-Loader configuration for WebPack is the only way to understand it.

This article will begin with the core functions of Babel, demystifying the Babel family step by step, and taking a small step forward as an architect.

What is the Babel

Babel is a JavaScript compiler.

As a JS compiler, Babel receives the input JS code, goes through the internal processing process, and finally outputs the modified JS code.

Within Babel, the following steps are performed:

  1. Parsing Input Code into an AST (abstract syntax tree) is called parsing

  2. Editing the AST, which is called the transforming step

  3. Printing the edited AST as an Output Code is called printing

From the source code of the Babel repository, it can be found that Babel is a Monorepo composed of dozens of projects.

Babel-core provides the capability for the three steps mentioned above.

Inside Babel-Core, to be more specific:

  • Babel-parser implements the first step

  • Babel-generator implements the third step

To understand the second step, we need to take a brief look at AST.

The structure of the AST

Go to AST Explorer, select @babel/ Parser as the parser, and type on the left:

const name = ['ka'.'song'];
Copy the code

The AST can be parsed as a tree structure in JSON format:

Inside Babel-Core:

  • Babel-traverse traverses the AST tree in depth-first mode

  • For each path traversed, babel-types provides node type data for modifying AST nodes

Therefore, the overall Babel underlying compilation capability consists of the following components:

Now that we know the underlying capabilities of Babel, let’s look at what the upper layers can do based on those capabilities.

Babel’s superior abilities

Based on Babel’s ability to compile JS code, the most common upper level capabilities of Babel are:

  • polyfill

  • DSL transformations (such as parsing JSX)

  • Syntax transformations (such as parsing high-level syntax into currently available implementations)

Due to space constraints, this section only covers polyfill and syntactic conversions.

polyfill

As a front end, the most common Babel ecology libraries are probably @babel/ Polyfill and @babel/preset-env.

Degraded implementation of advanced syntax and polyfill of apis can be implemented using @babel/polyfill or @babel/ Preset -env.

Babel is just a compiler for JS.

The answer is: core-js

The core – js profile

Core-js is a set of modular JS standard library, including:

  • All the way to polyfill ES2021

  • Promise, Symbols, iterators and other features of the implementation

  • Feature implementation in ES proposal

  • Cross-platform WHATWG/W3C features such as urls

Core-js is also a Monorepo consisting of multiple libraries, including:

  • core-js-builder

  • core-js-bundle

  • core-js-compat

  • core-js-pure

  • core-js

We introduce a few of them:

core-js

Core-js provides the core implementation of Polyfill.

import 'core-js/features/array/from'; 
import 'core-js/features/array/flat'; 
import 'core-js/features/set';        
import 'core-js/features/promise';    

Array.from(new Set([1.2.3.2.1]));          // => [1, 2, 3]
[1[2.3], [4[5]]].flat(2);                 // => [1, 2, 3, 4, 5]
Promise.resolve(32).then(x= > console.log(x)); / / = > 32
Copy the code

Using core-js directly pollutes the global namespace and object prototype.

For example, in the previous example, the Array prototype was modified to support the flat method of Array instances.

core-js-pure

Core-js-pure provides a separate namespace:

import from from 'core-js-pure/features/array/from';
import flat from 'core-js-pure/features/array/flat';
import Set from 'core-js-pure/features/set';
import Promise from 'core-js-pure/features/promise';

from(new Set([1.2.3.2.1]));                // => [1, 2, 3]
flat([1[2.3], [4[5]]].2);                // => [1, 2, 3, 4, 5]
Promise.resolve(32).then(x= > console.log(x)); / / = > 32
Copy the code

This use does not pollute the global namespace and object prototype.

core-js-compat

According to Browserslist, core-js-compat maintains a set of features that need to be supported in different host environments and versions.

Browserslist provides support for ES features in different browsers and Node versions

Such as:

"browserslist": [
    "not IE 11"."maintained node versions"
  ]
Copy the code

Represents: non-IE11 versions and all versions maintained by the Node.js Foundation.

@babel/polyfill and core-JS

@babel/polyfill can be thought of as core-js plus regenerator-Runtime.

Regenerator-runtime is a runtime dependency of generator and async/await

Using @babel/polyfill alone will import core-JS in full, resulting in too much project packaging.

As of Babel V7.4.0, @babel/polyfill is deprecated and can be directly referenced to core-js and Regenerator-Runtime instead

To solve the problem of excessive packing volume caused by the full introduction of Core-js, we need to use @babel/ PRESET -env.

The meaning of the preset

Before introducing @babel/preset-env, let’s know what preset means.

Initially, Babel has no additional capabilities and its workflow can be described as follows:

const babel = code= > code;
Copy the code

The plugin provides the ability to intervene in babel-core externally, and the Webpack-like plugin provides the ability to intervene in the WebPack compilation process externally.

Plugins fall into several categories:

  • @babel/plugin-syntax-* syntax related plugin for new syntax support. For example, babel-plugin-syntax-decorators provides syntax support for decorators

  • @babel/plugin-proposal-* for ES proposal feature support, such as babel-plugin-proposal-optional-chaining is optional chain operator feature support

  • @babel/plugin-transform-* Is used to transform code, and the corresponding syntax plug-in is used inside the transform plug-in

A set consisting of multiple plugins is called preset.

@babel/preset-env

Using @babel/ Preset -env, features in Core-JS can be packaged as needed, which can significantly reduce the final package size.

Here on demand, divided into two granularity:

  • Granularity of the host environment. Package all the features needed for that environment according to the different hosting environment

  • Granularity by use. Package only the features that are used

Let’s look at them in turn.

Granularity of the host environment

When we configure the browserslist file in the project directory (or in the targets property of @babel/preset-env, or in the browserslist property of package.json) with the following parameters:

not IE 11
maintained node versions
Copy the code

Features required for all versions of Node maintained by the Node.js Foundation that are not IE11 will be bundled into the final package.

Obviously this is taking advantage of the core-js capability that we just introduced, core-js-compat under Monorepo.

Granularity by use

It would be better to pack only the features we used.

Set @babel/ Preset -env’s useBuiltIns attribute to Usage.

Such as:

A. s:

var a = new Promise(a);Copy the code

B.j s:

var b = new Map(a);Copy the code

When the host environment does not support Promise and Map, the output file is:

A. s:

import "core-js/modules/es.promise";
var a = new Promise(a);Copy the code

B.j s:

import "core-js/modules/es.map";
var b = new Map(a);Copy the code

When the host environment supports these two features, the output file is:

A. s:

var a = new Promise(a);Copy the code

B.j s:

var b = new Map(a);Copy the code

Further optimize the packaging volume

Open Babel Playground and type:

class App {}
Copy the code

You will find that the compiled result is:

function _classCallCheck(instance, Constructor) { if(! (instanceinstanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); }}var App = function App() {
  "use strict";

  _classCallCheck(this, App);
};
Copy the code

_classCallCheck is the auxiliary method.

If multiple files use the class feature, then each file package will contain _classCallCheck in the corresponding Module.

To reduce packaging volume, it is better to refer to all modules that need helper methods from the same place rather than maintain a copy yourself.

@babel/ Runtime contains all of the Babel helper methods as well as the regenerator-Runtime.

Simply introducing @babel/ Runtime doesn’t work because Babel doesn’t know when to refer to helper methods in @babel/ Runtime.

Therefore, @babel/ plugin-transform-Runtime is also needed.

This plugin will change all use of helper methods at compile time from maintaining a copy to importing them from @babel/ Runtime.

So we need to set @babel/plugin-transform-runtime to devDependence because it is used at compile time.

I’ll set @babel/runtime to dependence because it’s used at runtime.

conclusion

This article takes a bottom-up look at the members of the Babel family that the front-end day-to-day business development deals with. They include:

The underlying

@babel/core (consisting of @babel/parser, @babel/traverse, @babel/types, @babel/generator, etc.)

They provide Babel with the ability to compile JS.

Note: @babel/core is the repository name, and babel-core is the repository name

In the middle

@babel/plugin-*

Babel exposes apis that allow developers to interfere with its ability to compile JS

The upper

@babel/preset-*

A collection of plug-ins that will be used for daily development.

For students who aspire to become front-end architects, Babel is the cornerstone of front-end engineering, so it is necessary to understand and use it.

It’s not easy to see here. Give yourself a big hand.