Words: 4627; Reading Time: 12 Minutes; click to read the original article

Fenglin new leaves urge old leaves, nestling feng qing in the old wind. Li Shangyin

【 Front-end Engineering 】 Series of links:

  • 01 Set sail – development environment
  • 02 White Flawless – Package manager
  • Sweep the Eight Wasters -Webpack Basics
  • 04 Swept the Eight Wasteland -Webpack advanced

Sample code repository:Github.com/BWrong/dev-…

Declaration: If an error occurs following the code in this article, check whether the dependent version is consistent with the sample code repository.

An overview of the

As an ambitious front-end, if you haven’t heard of Babel, you’re out. But to be honest, if you don’t know much about Babel, it doesn’t really affect the day-to-day development, because a lot of the scaffolding is already in place and you don’t have to mess with it yourself. But if you want to play with front-end engineering, there’s no getting around that.

Babel is officially defined as a javascript compiler.

Use next generation JavaScript, today.

Use the next generation JavaScript syntax now

The term “next generation” refers to something new and not yet widely supported by browsers, rather than being limited to a certain version.

In simple terms, the browser (especially some low-end browser, say you IE) support for ECMAScript code is lag, although new syntax can bring us a better programming experience, but often can’t did not run directly in the user’s browser, so you need to run this code to the browser before convert them to support the grammar, And that’s what Babel does, but it’s much more than that, and we’ll get to that.

Here’s what Babel does:

  • Syntax conversion: Converts the new syntax into an older syntax that is more compatible.
  • Characteristic gasket: passPolyfillTo achieve the missing features in the target environment, to smooth out the differences between the characteristics of the environment, to put it bluntly, or do compatibility.
  • more…
// Babel Input: ES2015 arrow function
[1.2.3].map((n) = > n + 1);

// Babel Output: ES5 equivalent
[1.2.3].map(function(n) {
  return n + 1;
});
Copy the code

Operation mode

Babel runs in three stages: parsing, transformation, and generation

  1. Parsing: Parses the code and generates an AST abstract syntax tree
  2. Transform: Converts the AST from the previous step to an AST supported by the target environment
  3. Generate: Generate object code from the transformed AST

As you can see, the second step is the core operation Babel performs. Babel, however, does not have any conversion capabilities of its own, and some plug-ins must be used to convert the syntax. More on plugins later, but just remember that plugins give Babel great power, and without them it’s a ball.

For details of the transformation process, read from Babel to AST.

The kernel of Babel’s parsing syntax uses Babylon, now renamed @babel/ Parser.

Babel7

At the time of writing this article, Babel was released in version 7.10.0, and babel7 brings a number of new features. To avoid confusion, here are some of the changes to babel7.

The main changes are as follows:

  • Preset: Recommendedenvalternativees201x; deletestage-x, because the features in these presets were unstable and maintaining these presets would have wasted a lot of effort, so these presets were officially abandoned. If necessary, you can explicitly add the corresponding plug-in yourself.
  • NPM package name: will allbabel-*Change to@babel/*, more in line with the NPM naming specification.
    • babel-cli -> @babel/cli.
    • babel-preset-env -> @babel/preset-envFor short,@babel/env
  • Lower than nodejs6.0 is no longer supported.
  • @babel/cliNo longer contains@babel/node, if you want to use it needs to be installed separately.
  • providebabel-upgradeHelp developers upgrade from version 6 to 7.

Use and Configuration

use

Babel is used in two main scenarios: the command line and build tools.

  • The command line

Babel provides the @babel/cli tool for the command line to directly execute the Babel command from the command line.

First, install the necessary packages:

npm install --save-dev @babel/core @babel/cli
Copy the code

You have to install @babel/core, which is the core of Babel, which is the cornerstone of everything that follows, either way.

The transformation can be done by executing the Babel command in the installation project.

Babel SRC --out-dir lib #Copy the code
  • Build system

    The more common way we use Babel is in build tools. Let’s take webPack as an example.

    Babel provides a loader: babel-loader, with which we can easily implement code conversion.

module: {
  rules: [{test: /\.js$/,
      loader: 'babel-loader'.options: {
      	/ / configuration items}}}]Copy the code
  • Runtime (not recommended)

    Babel also provides runtime processing that can be converted in real time while the code is running. However, this approach is not recommended because it is less efficient to convert in real time while the code is running.

    In addition to the above methods, there are several other ways to use Babel configuration.

The configuration file

We didn’t provide the configuration in either of the previous ways, so the output is the same as the input, which makes no sense at all. So we typically provide some configuration that tells Babel how to do the transformation.

Babel7 supports several ways to configure:

  • Babel.config. json (project scope)

  • .babelrc.json (relative file)

  • Add the Babel field to package.json

{
  "name": "my-package"."version": "1.0.0"."babel": {
    "presets": [...]. ."plugins": [...]. ,}}Copy the code

The previous two configuration files use JSON format, but Babel also supports other formats, such as.js,.cjs (Commonjs), and.mjs (ESModule). These formats are more flexible than JSON format and can be processed programmatically during configuration, but will not be able to statically analyze the configuration and lose the cacheability.

// babel.config.js
module.exports = {
  presets: [].plugins: []};Copy the code

The configuration content of all methods is similar, and we can choose flexibly according to our own usage scenarios.

Babel has several ways to write configuration files, but the content of the configuration is similar, mainly consisting of plugins and presets, but there are a few things to note here:

  • Configuration format: Plugins and presets are arrays. If each item does not need to be configured, put it in an array (value is the string name of the item). If you do, you need to change the format to an array where the first element is the item’s string name and the second element is the item’s configuration object.

    Fill in the plugins and presets here and Babel will automatically check to see if they have been installednode_modulesNext, of course, you can also use relative paths to use local files.

"plugins": [["component",
      {
        "libraryName": "element-ui"."styleLibraryName": "theme-chalk"}]."@babel/plugin-transform-runtime"
 ]
Copy the code
  • Order of execution: Plugins are executed front to back, presets are executed back to front, and plugins are executed before Presets

    The purpose of reverse execution of presets is to ensure backward compatibility. We can list the presets in the normal chronological order, such as [‘ ES2015 ‘, ‘stage-1’], so that stage-1 will be used first, otherwise an error will occur.

  • Short name: When configuring plugin and preset names, babel-plugin- and preset- can be omitted from the name and only the plugin name is used.

{
  "plugins": [
    "myPlugin".// full name: "babel-plugin-myplugin"
    "@org/myPlugin" // Full name: "@org/ babel-plugin-myplugin"]."presets": ["myPreset".// Full name: "babel-preset-myPreset"
    "@babel/myPreset" // Preset full name: @babel/preset-myPreset]}Copy the code

Some common configurations of common plugins and presets are described in more detail below.

The core

The core of Babel is @babel/core, which is plugins and presets, and that’s what we do with Babel. Let’s find out.

Plugin

As we said earlier, Babel’s transformation is entirely plug-in dependent, and the power Babel has depends entirely on which plug-ins are configured for it.

There are two types of plug-ins for Babel:

  • Syntax plugins: To give Babel the ability to parse specific syntax, you can understand a translator working for Babel, only doing translation and parsing, working in the AST transformation phase.

  • Translation plug-ins: Translation plug-ins convert a specific syntax into a specified standard syntax.

    For example, to convert the arrow function (x) => x to function (x) {return x}, just configure the arrow function interpreter.

{
  "plugins": [
    "@babel/plugin-transform-arrow-functions"]}Copy the code

** Note: with the translation plug-in, there is no need to configure the corresponding syntax plug-in, because the translation plug-in automatically enables the corresponding syntax plug-in. In fact, it is easy to understand, can not even do the parsing, how to talk about transformation.

There are two steps to using plug-ins:

  1. Add the plug-in to the configuration filepluginsProperties, which can be configured according to the plug-in documentation.
  2. Install the plug-in to the project using NPM or YARN

The function of each plug-in is single. Therefore, a large number of plug-ins need to be used for conversion. In this case, presets can be considered. For common plug-ins and usages, see Babel-plugins

Custom plug-in

In addition to using plugins provided by the community, we can also develop our own.

module.exports = function() {
  return {
    visitor: {
      Identifier(path) {
        const name = path.node.name;
        // reverse the name: JavaScript -> tpircSavaJ
        path.node.name = name
          .split("")
          .reverse()
          .join(""); ,}}}; }Copy the code

This is just a simple example, the actual plug-in development is far from so simple, you can perform the Babel plug-in manual to learn.

Presets

In the actual development, if we use ES6 for development, there are a lot of syntax to convert, do we need to configure one by one? Do you want to be a chaperone?

This is where the Presets come in. Presets can be interpreted as a collection of plugins, just like when you go to Decos and order a bucket of family instead of asking for chicken legs, wings, fries… (If you want to spend more time with the waitress, don’t be afraid to.)

Presets can be found in the following ways:

  • Official: Official provides preset for commonly used environments.

    • @babel/preset-env
    • @babel/preset-flow
    • @babel/preset-react
    • @babel/preset-typescript
    • Stage-x (Experimental Presets)
  • Community: There are many community-maintained preset versions available on NPM. For example, Vue and React maintain their own preset versions.

  • Customization: Preset can be created for special requirements.

module.exports = () = > ({
  presets: [ // Reference other preset
    require("@babel/preset-env")],plugins: [ // The plug-in to assemble
    [require("@babel/plugin-proposal-class-properties"), { loose: true}].require("@babel/plugin-proposal-object-rest-spread")]});Copy the code

Note: Any transformations in stage-X presets are changes to unreleased Javascript features (such as ES6 / ES2015) and are unstable. These proposals may change in the future. Use with caution, especially proposals prior to Stage 3. As of V7, all stage presets written for features of the standard proposal stage have been deprecated, and are officially removed @babel/preset-stage-x.

@babel/preset-env

{
    "presets": ["@babel/preset-env"]}Copy the code

This is our most common and officially recommended preset, which is an intelligent preset that allows us to use the latest syntax without worrying about the conversion plugin needed in a particular environment (just specify the target environment). In addition to making it easier to use, it also makes the converted code smaller.

Preset -env selects the appropriate plugin for the necessary conversion and polyfill based on the features missing in the configured target environment, and the features supported by the target environment will not be converted, instead of a mineless hover.

If not configured, it will use the default configuration, which is latest (the list of plug-ins env maintains), and will set the latest JS features (ES2015,ES2016… , excluding the stages), and convert it to ES5 code. So if you use the syntax of the proposal phase, you need to configure it yourself.

How do I specify the target environment
  • Configuration file: Specify the target environment using the Targets property of the configuration file
{
  "presets": [["env", {
      "targets": {
        "browsers": ["last 2 versions"."safari >= 7"]."node": "12.10"}}}]]Copy the code
  • Browserslistrc: For the browser or Electron, the official recommendation is to use the. Browserslistrc file to specify the target environment (you can share the configuration with other tools like autoprefixer or Stylelint).

    However, if targets or ignoreBrowserslistConfig is set in the configuration file, the configuration in. Browserslistrc does not take effect.

# .browserslistrc
last 2 Chrome versions
Copy the code

See more configurations of Browserslist for details.

Other configuration

In addition to targets, some other configuration items are also commonly used:

  • Modules: used to specify the module specification after the transformation, can be set to “amd” | “umd” | “systemjs” | “commonjs” | “CJS” | | “auto” false, the default value is “auto”.

  • UseBuiltIns: This option configures how @babel/preset-env handles polyfill, which we’ll cover in more detail later.

  • Corejs: Specifies the corejs version. This configuration is valid only when useBuiltIns: Usage or useBuiltIns: Entry is used. Ensure that the correct version is configured.

    The current default version is 2.0, but 3.0 is recommended because 2.0 does not add any new features, and if you use some newer syntax, conversion will not be possible.

In addition to the above configuration, there are some less common configurations, you can check the official documentation.

Polyfill

The previous configuration is not perfect, and the converted code running directly in the browser can cause problems. This is because Babel only converts syntax by default, and some new apis do not handle it. Global objects and methods such as Generator, Set, Maps, Proxy, Promise, etc., are not transcoded and will report an error if we use them in our code.

At this point, we need to Polyfill, or “shim,” the browser’s role is to smooth out the differences in the browser, by simulating the missing global objects and apis for those browsers.

@babel/ Polyfill (discarded)

An early solution was @babel/ Polyfill (the early name was babel-Polyfill, internally integrated with core-JS and Regenerator), which introduced @babel/ Polyfill in the entry or webpack configuration entry, This tool emulates the full ES2015 + environment (not including the Phase 4 proposal), creates some global objects, or completes methods on existing global object prototypes, allowing us to use new apis like Promise, WeakMap, Array.from, etc.

For example, array.prototype. includes is an ES6 API, and a self-implemented includes method is added to array. prototype to emulate the original includes.

import '@babel/polyfill'; 
let include = [1.2.3].includes(1);
new Promise((resolve, reject) = > {
    resolve();
});
Copy the code

Our code is now ready to run on older browsers. However, this scheme has some disadvantages:

  • File size: because@babel/polyfillIt will complete all the apis, whether you use them or not, for example, if you only use one new API in a project, and then it just gives you a bug, and it includes all the apis. So you end up with a very big, very wasteful bag.
  • Pollution global variable:@babel/polyfillObjects are created globally or the prototype chain of the global object is modified, so global variables are polluted.

For the first disadvantage, we can use the useBuiltIns configuration provided earlier by @babel/preset-env. When set to Usage (corejs version required), Babel will check our code and only introduce polyfills that are needed in our code. The resulting package will be much smaller.

// babel.config.json
{
  "presets": [["@babel/env",
      {
        "useBuiltIns": "usage"."corejs": 3}}]]Copy the code

For example:

// import '@babel/polyfill'; When useBuiltIns: 'usage' is used, the required spacers are automatically imported, so manual import is unnecessary
let include = [1.2.3].includes(1);
new Promise((resolve, reject) = > {
    resolve();
});
Copy the code

Compiled code:

"use strict";
require("core-js/modules/es.array.includes");
require("core-js/modules/es.object.to-string");
require("core-js/modules/es.promise");

// import '@babel/polyfill'; 
let include = [1.2.3].includes(1);
new Promise((resolve, reject) = > {
    resolve();
});
Copy the code

We can see if polyfill, which adds only the new syntax used in the file to the header, works well.

However, this is not the end of the story. If many files use these features, wouldn’t every file be introduced once? Is there any way to improve it?

@babel/plugin-transform-runtime

@babel/ plugin-transform-Runtime is a plug-in that can reuse Babel injected helper to avoid repeated injection to save code size. It is usually used with @babel/ Runtime.

NPM install --save-dev @babel/plugin-transform-runtime NPM install --save-dev @babel/runtimeCopy the code
// babel.config.json
{
  "presets": [["@babel/preset-env",
      {
        "useBuiltIns": "usage"."corejs": 3}]],"plugins": [["@babel/plugin-transform-runtime"]]}Copy the code

After the above configuration, a class conversion is as follows:

/ / the source code
class Foo {
  method(){}}Copy the code
/ / after the transformation
import _classCallCheck from "babel-runtime/helpers/classCallCheck";
import _createClass from "babel-runtime/helpers/createClass";

let Foo = function () {
  function Foo() {
    _classCallCheck(this, Foo);
  }

  _createClass(Foo, [{
    key: "method".value: function method() {}}]);returnFoo; } ();Copy the code

As we can see, instead of using global import, local variables are introduced to replace the new syntax, providing a sandbox mechanism that incidentally solves the problem of global scope contamination above. And all of the simulated features here are imported from @babel/ Runtime, which we installed in production and didn’t package into every file, so these polyfills were reused and didn’t pack multiple times.

** Note: ** Corejs needs to be 3.0 to be fully converted. If you use 2.0, only the syntax will be converted, not polyfill. If you want to do this, you will need to introduce @babel/runtime-corejs3 separately.

After a lot of twists and turns, we finally got it done. However, this is not a silver bullet, and there may be other schemes in the future. For the history of Polyfill, you can see the past, present and future of Polyfill scheme of Yunqian Big Man.

ecological

In addition to the above, the Babel ecosystem is thriving, with a number of other tools (names usually start with Babel -* or @babel/*). Here are some of them:

  • babel-cliThe command line tool provided by Babel is providedbabelCommand to perform compilation tasks.
  • babel-registerWill:requireBefore loading the file, transcoding is done using Babel, which is used for the run-time compilation mentioned earlier.
  • babel-loader: Babel provides a loader for Webpack, and is the most common use. Transcoding can be done in a Webpack-built project.
  • babel-polyfill/@babel/polyfill: Polyfill tool from Babel.

The existence of Babel, let us to break the limit of running environment grammar specification, like a time machine, brought us to the future, that we can use when grammar to write the application at this time, for us to learn and use new grammar cleared the obstacle, peremptory Babel has become an essential tool in our engineering.

Reference documents: Babel Chinese, must-see Babel7 knowledge, a long time to learn about Babel