preface

I used to put forward my own conjecture by reading official documents, and then put my conjecture into practice with official examples. Finally, I combined the application of modern framework and engineering tools to learn the usage and operation principle of Babel, so as to have a more comprehensive understanding of Babel. This article is to sort out and record the learning process.

In this paper:

  1. Officially provided instances as pointcuts through step-by-step installationbabelRelated packages and view the example compilation results, to recognize and learnbabel.
  2. Vue.jsStep through a simple configuration for pointcutsVueProject, explicitbabelwithVue.js,webpackRelationships at code compile time.

Packages and versions used:

  • babel:7
  • babel-loader: 8
  • vue: 2
  • Vue – cli: 4
  • vue-loader: 15
  • vue-template-compiler: 2
  • webpack: 5
  • Webpack – cli: 4.9

Babel official definition

Babel is a JavaScript compiler

Is a tool chain for converting code written in ECMAScript 2015+ syntax into backward-compatible JavaScript syntax so it can run in current and older browsers or other environments.

Instance to explore

The packages to be installed are mentioned in the overview of the user guide on the official website. Let’s put it into practice.

First, we create a project through NPM init that can download and install packages from the NPM platform.

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

The above is the installation command given on the official website. Let’s first understand the functions of these three libraries.

@babel/core

@babel/core The name refers to the core package of Babel, which presumably handles code parsing, translation, and generation. Let’s take a look at @babel/core’s package.json file to verify our conjecture.

Sure enough, it’s a core feature.

The package’s transform method, which we assume is used to translate source code to run in current and older browsers or other environments, is first mentioned on the package’s website.

So at least the arrow function should be translated as a function declaration.

Let’s verify:

npm install --save-dev @babel/core
Copy the code
"DevDependencies ": {"@babel/core": "^7.16.5"}Copy the code

After the installation command is executed, the corresponding package is added to package.json.

Let’s create a new file called testbabelcore.js:

var babel = require("@babel/core");
var sourceCode = `[1, 2, 3].map(n => n + 1)`;
var options = {};

babel.transform(sourceCode, options, function(err, result) {
  console.log('sourceCode=========', result.code);
  console.log('result.code=========', result.code);
  console.log(result);
});

Copy the code

perform

After execution, the code is not converted, guess wrong?

I found that @babel/core is responsible for parsing, translating, and generating, but it is not wrong to translate exactly why the version of JS should be customized. If you need to install another package or plug-in, add the corresponding parameter processing in options.

It’s a really nice design, and you can customize it according to your needs, so it’s very flexible.

@babel/preset-env

The official recommendation is to use this package to define the translated JS version, so let’s take a look at the effect.

The installation

npm install --save-dev @babel/preset-env
Copy the code

In the options parameter

var babel = require("@babel/core");
var sourceCode = `[1, 2, 3].map(n => n + 1)`;
var options = {
  presets: [
    '@babel/env'
  ]
};

babel.transform(sourceCode, options, function (err, result) {
  if (err) {
    console.error(err)
    return
  }
  console.log('sourceCode=========', sourceCode);
  console.log('result.code=========', result.code);
  console.log(result);
});
Copy the code

Performed againThe result shows that the code has been converted.

@babel/preset-env I think can be understood as the version of the generated code that is preset to compile according to the host environment version. For example, older versions of modern browsers already support async/await keywords, so compiled files do not need to generate Polyfill code and use async/await keywords to avoid redundant code and increase the size of the code package.

Specify host environment

This is where the question arises, how is the version of the host environment determined? The above configuration does not set the host environment version, how does it know about the host environment at compile time? Is there a default configuration? What is the default configuration?

We are in @babel/preset-env How Does it Work? To find the answer.

Babel uses browserslist to set the default host environment version.

The Queries in browserslist refer to the method for obtaining the host environment version, where clause 5 is the default version:

  1. If the above methods did not produce a valid result Browserslist will use defaults: > 0.5%, last 2 versions, Firefox ESR, not dead.

We can add a.browserslistrc file to the project root

> 0.5%, last 2 versions, Firefox ESR, not dead
Copy the code

Then add the debug parameter in the code to facilitate viewing information

var options = {
  presets: [
    [
      '@babel/env',
      { 'debug': true }
    ]
  ]
};
Copy the code

Run again

Using targets clearly shows the default version of each browser hosting environment configured in our.browserslistrc file.

The plugin is transform-arrow-functions {ie}, which means that if the host environment contains Internet Explorer, the arrow functions need to be translated. Plugin will be explained later.

If you want to compile code that specifies a host environment, you can configure the targets configuration in the targets parameter as well as in the.browserslistrc file.

var options = {
  presets: [
    [
      '@babel/env',
      { 'targets': {"chrome": "47"}, debug: true }
    ]
  ]
};
Copy the code

The result is as follows:

We find that chrome: 47 is the only host environment, and the translation plugin is much less, and transform-arrow-functions are not used, because Chrome: 47 supports arrow functions, so there is no translation.

You can look at this table to see which statements need to be translated in which host environment

If chrome version 46 is changed, the arrow function is translated to the function declaration.

More configurations, such as the Node version, can be found on the official website.

If you don’t want to preset — env, you can also customize the preset version, just install the appropriate package and configuration parameters, which is not described here.

@babel/cli

@babel/core and @babel/preset-env already meet my needs for translation code, but the source code that needed to be translated before can be written in one line, what if I want to translate thousands of lines of code?

Again, I want to write a source file and execute a command that directly generates a translated object file, not print a long string.

@babel/cli meets the above requirements well.

Let’s install it

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

Note: @babel/ CLI is dependent on @babel/core and @babel/preset-env, so these two packages must be installed as well.

newtestBabelCli.jsFile, just type in one line of code

Then execute the following command:

npx babel ./src/testBabelCli.js --out-file ./src/testBabelCli-compiled.js --presets=@babel/preset-env
Copy the code

The translated object file is generated in the same directory as the source code.

You may say that it is too cumbersome to add parameters to the command to perform translation. Can you create a configuration file instead?

B: Sure. @babel/cli You can create the babel.config.js configuration file. When you run the Babel command, the configuration information in the file can be read automatically.

Add the file babel.config.js in the project root directory

module.exports = {
  presets: [
    '@babel/preset-env'
  ]
}

Copy the code

And then execute the instructions

npx babel ./src/testBabelCli.js --out-file ./src/testBabelCli-compiled.js
Copy the code

The content of the translated file is the same as that generated by adding parameters to the command line. The. Babelrc configuration file needs to be mentioned. Start with babel7. Babel.config. js is the global configuration file,.babelrc is the module configuration file.

Plugins

Babel also includes the concept of Plugins, which allow you to configure JS translation rules more flexibly to extend the translation capabilities of your code.

What is the relationship between presets and Plugins? It’s both translated code. Why two?

In fact, presets are a collection of plugins.

Let’s look at @babel/presets-env package.json.

Obviously, @babel/presets-env is made up of a bunch of plugins.

I have to say that Babel’s architecture is clever: the granularity of functionality is as small as possible to ensure that Babel is flexible and extensible.

It is important to note that the order in which presets and plugins are introduced is not the same as that in plugins

Plugin implementation principle

As mentioned earlier, Babel performs three core code-switching steps: parser, traverse, and generator.

It is at the traverse step that the Plugin handles the code.

First, we need to obtain the AST according to the source code, you can generate the AST online in the AST Explorer website, and then edit the AST.

Translation of new built-in functions in JS

This plugin is not mentioned in the official example, but in practice it is essential. Let’s go step by step.

First, in the testBabelcli.js file, change the code to the following

We then set the host environment to IE8 in babel.config.js.

These two lines of code contain the const keyword and includes array methods. These are new syntax and apis for ES6, but not for IE8. In theory, both should be translated.

Execute the command

npx babel ./src/testBabelCli.js --out-file ./src/testBabelCli-compiled.js
Copy the code

View the compiled file testbabelcli-compile.js

Found that const is translated, but includes methods are not translated, so running on IE8 will definitely report an error.

Let’s take a look at the console output

Transform-block-scoping is a plugin for const processing, which is included by default in @babel/preset-env. But an API like includes doesn’t have a package that handles it by default.

@babel/polyfill

Newer js built-in functions such as includes can be translated by introducing @babel/polyfill.

@babel/polyfill contains regenerator Runtime and Core-js.

Core-js is a library that polyfills new ES6+ feature apis for older browsers.

The reGenerator Runtime is needed because generator code such as async/await and yield is translated by Babel to call the regeneratorRuntime method, which is defined in the ReGenerator Runtime. Babel is a dependency reference to the ReGenerator Runtime.

To verify this, change the testBabelcli.js content to

Execute the command

npx babel ./src/testBabelCli.js --out-file ./src/testBabelCli-compiled.js
Copy the code

View the compiled file testbabelcli-compile.js

There are calls.

New configuration item in configuration file babel.config.js

UseBuiltIns is how polyfill is imported, Usage is imported on demand, and Entry is imported all. Usage is more reasonable.

Execute the command

npx babel ./src/testBabelCli.js --out-file ./src/testBabelCli-compiled.js
Copy the code

View the compiled file testbabelcli-compile.js

According to global introduces the core – js/modules/es7. Array. Includes. Js

And then we install @babel/ Polyfill.

npm install --save @babel/polyfill
Copy the code

Note that this is installed in dependency, and here is the official tip:

Because this is a polyfill (which will run before your source code), we need it to be a dependency, not a devDependency

@babel/plugin-transform-runtime

Solve global variable contamination problems

If you haven’t noticed, the introduction of es7.array.includes is a global approach, as described on @babel/ Polyfill:

This means you can use new built-ins like Promise or WeakMap, static methods like Array.from or Object.assign, instance methods like Array.prototype.includes, and generator functions (provided you use the regenerator plugin). The polyfill adds to the global scope as well as native prototypes like String in order to do this.

Adding methods directly to object constructors or prototypes risks contaminating global variables. Because you can’t guarantee that the same global variable has been modified in the third party library that you reference, there’s a problem.

@babel/ plugin-transform-Runtime solves this problem.

As described on the official website

Another purpose of this transformer is to create a sandboxed environment for your code. If you directly import core-js or @babel/polyfill and the built-ins it provides such as PromiseSet and Map, those will pollute the global scope. While this might be ok for an app or a command line tool, it becomes a problem if your code is a library which you intend to publish for others to use or if you can’t exactly control the environment in which your code will run.

To verify this, install the relevant package:

npm install --save-dev @babel/plugin-transform-runtime
Copy the code

rightbabel.config.jsAdding Configuration Information

Execute the command

npx babel ./src/testBabelCli.js --out-file ./src/testBabelCli-compiled.js
Copy the code

View the compiled file testbabelcli-compile.js

It says to import packages@babel/runtime-corejs3Get the method_interopRequireDefault

The installation

npm install --save @babel/runtime-corejs3
Copy the code

This package will be installed in the Dependency because it is used by the code at runtime.

After installing it, we found the method and looked at what we had done

Check whether the parameter is ESM, if it is directly returned; If not, the argument is returned in the default property of an object for the sake of modularity.

Solve auxiliary code duplication problem

The @babel/plugin-transform-runtime also has the added benefit of solving the problem of duplicate definitions of auxiliary code after compilation.

Let’s look at this example:

Put the configuration file first@babel/plugin-transform-runtimeComment out the plug-in configuration.

And then, define oneFooclass

Execute the command

npx babel ./src/testBabelCli.js --out-file ./src/testBabelCli-compiled.js
Copy the code

View the compiled filetestBabelCli-compiled.js

You can see that the compiled code defines and calls a _classCallCheck method.

Add @babel/ plugin-transform-Runtime to babel.config.js.

Reexecute command

npx babel ./src/testBabelCli.js --out-file ./src/testBabelCli-compiled.js
Copy the code

View the compiled file testbabelcli-compile.js

You can see that the _classCallCheck2 method is defined in @babel/runtime-corejs3. The compiled code can be called directly by reference without any further definition.

summary

Babel /core is the core package of Babel, which is responsible for PARSER, traverse, and generator of JS code.

However, the specific translation of why the version of JS, but also need to introduce the package definition. The official recommendation is to use @babel/preset-env, but you can preset to your own needs.

In order to project engineering needs, the official also provides @babel/ CLI, you can directly compile the source code file, directly generate the target file.

In order to make Babel more extensible and flexible for code compilation, Babel provides a Plugin feature that allows users to implement their own compilation rules that meet their needs. All Babel translation rules are based on plugins, and presets are a set of plugins.

This package contains core-js and reGenerator Runtime. One is the implementation method of the new ES6+ features provided by earlier versions of the browser. One is to define generator functions to call after they are translated by Babel, and install them in Dependencies.

There are two problems with @babel/polyfill. One is that the polyfill method is defined on a global variable and can pollute the global environment. Again, the auxiliary code that has been compiled will have the problem of double definition. To solve these problems, you can use the @babel/ plugin-transform-Runtime plug-in.

Explore Babel’s translation of Vue code

For current project development, we use modern frameworks, and we can develop quickly based on the templates provided by the frameworks. However, the template code is not recognized by the browser, and ultimately must be converted to the browser’s knowledge of the code. Babel does the js code translation work, and the following uses the Vue framework as an example to explore how Babel translates Vue code.

Try Babel to translate Vue code directly

Install the Vue. Js

npm install vue --save
Copy the code

Create a new index.html file

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, <meta HTTP-equiv =" x-UA-compatible "content=" IE =edge"> <title>Document</title> </head> <body> <div id="app"></div> </body> </html>Copy the code

Create a new index.js file

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  el: '#app',
  render: (h) => h(App),
  components: { App }
})
Copy the code

Create a new app. vue file

<template> <div id="app"> <h1>My Vue App! </h1> </div> </template>Copy the code

Execute Babel compile command:

npx babel ./src/vue-demo/index.js --out-file ./src/vue-demo/dist/index.js 
Copy the code

Executing the command produces a compiled file, /dist/index.js

"use strict";

var _vue = _interopRequireDefault(require("vue"));

var _App = _interopRequireDefault(require("./App"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

_vue["default"].config.productionTip = false;
new _vue["default"]({
  el: '#app',
  render: function render(h) {
    return h(_App["default"]);
  },
  components: {
    App: _App["default"]
  }
});
Copy the code

Add the /dist/index.js file to index.html.

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, <meta HTTP-equiv =" x-UA-compatible "content=" IE =edge"> <title>Document</title> </head> <body> <div id="app"></div> <script src="./dist/index.js"></script> </body> </html>Copy the code

Then we opened the HTML file in the browser and found that the console reported an error

The browser doesn’t recognize the require method that introduces the file, and Babel ends up changing import to require in the CommonJS canonical form.

To solve the require method definition problem and to implement inter-file import, we can use Webpack.

Resolve require undefined issues with Webpack

Install webpack

npm install --save-dev webpack webpack-cli
Copy the code

Create a new webpack.config.js file in the root directory

const path = require('path')

module.exports = {
  entry: './src/vue-demo/index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'build.js'
  }
}

Copy the code

Add to script in package.json

"scripts": {
  "build": "webpack --mode development",
},
Copy the code

Execute the command

npm run build
Copy the code

Error reporting…

Error: Webpack does not recognize. Vue files and needs a loader to convert them into JS modules.

Let’s install a Vue-loader

npm install --save-dev vue-loader
Copy the code

Configure the introduction of vue-loader in webpack.config.js

const path = require('path') const VueLoaderPlugin = require('vue-loader/lib/plugin'); module.exports = { entry: './src/vue-demo/index.js', output: { path: path.resolve(__dirname, './dist'), filename: Plugins: [new VueLoaderPlugin()], module: {rules: [{test: /\.vue$/, loader: 'vue-loader' } ] }, }Copy the code

Error: NPM run build

The vUE template-compiler module is used to precompile vue 2.0 templates into rendering functions (template => ast => render) to avoid runtime compilation overhead and CSP constraints.

The installation

npm install vue-template-compiler --save-dev
Copy the code

Execute NPM run build again, success!

Add the build.js file from the dist folder to the index.html file, and open the index.html file in your browser

Perfect!

Babel loader is not used in the webpack configuration file. The vue loader is not used in the webpack configuration file. Babel loader is used in the webpack configuration file. Or is our example too simple to cover the use of relevant functionality?

Let’s keep doing the experiment

Add js code to app.vue

<template> <div id="app"> <h1>My Vue App! </h1> </div> </template> <script> export default { created () { this.init() }, methods: { init () { [1, 2, 3].map(n => n + 1); console.log('init method') } } } </script>Copy the code

Let’s run NPM run build to see the compiled file.

I found that this code is unchanged, and the created method where this statement is created is defined in ES6 syntax, which also works in the browser because my browser version is old enough to recognize this syntax.

Then we install babel-loader

npm install --save-dev babel-loader
Copy the code

Configure babel-loader into webpack.config.js.

const path = require('path')

const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
  entry: './src/vue-demo/index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'build.js'
  },
  plugins: [
    new VueLoaderPlugin()
  ],
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader'
      },
    ]
  },
}
Copy the code

Then run NPM run build to see the output

Look! The code mentioned earlier has been translated.

How does vuE-CLI4 translate code?

We can learn all about vuE-CLI using Babel in the section on Browser compatibility in the official vuE-CLI4 documentation. The purpose of using Babel, as indicated here, is to address browser compatibility.

Let’s start with the vuE-CLI generated actual project.

The installation steps are omitted here and we go straight to the package.json file of the actual project generated through vue-CLI.

We found the plugin @vue/cli-plugin-babel in the devDependencies file. Let’s take a look at the plugin’s description on the website:

Uses Babel 7 + babel-loader + @vue/babel-preset-app by default, but can be configured via babel.config.js to use any other Babel presets or plugins.

This plugin includes Babel 7, babel-Loader, and @vue/babel-preset-app (just based on the name), and is used for environment preset, which we’ll look at later.

Take a look at the package.json file of @vue/cli-plugin-babel to see what it depends on.

It contains the main Babel package and the Babel-loader used with WebPack.

Let’s see what @vue/babel-preset-app is used for.

This is the default Babel preset used in all Vue CLI projects. Note: this preset is meant to be used exclusively in projects created via Vue CLI and does not consider external use cases.

This is the default option for vue-CLI.

Look again at its package.json file.

Inside, we can find many of the key packages and plug-ins for Babel, which we’ve already covered. In addition, there are some default plug-ins and helper methods introduced by @vue/babel-preset-app, which are not covered here, but can be reviewed in the official documentation.

summary

Exploring how to compile Vue project code into browser-aware code, Babel can only convert an import statement into a require statement, which is also unrecognized by the browser. So you need to use Webpack to introduce the problem to the Require module again.

However, when webPack handles module import, it encounters a problem: it does not recognize the. Vue template file and needs to introduce vue-loader to translate the template file into JS module.

However, vue-Loader can only use templates as JS modules of ES6 version, so babel-Loader should be added to carry out backward compatible translation of code in JS files.

The @vue/ CLI-plugin-babel plug-in in VUe-CLI encapsulates the Babel function, including Babel 7, babel-Loader, and @vue/ babel-babel-app, realizing the integration of Vue, Babel, and Webpack.

Vue-cli also introduces the default proposal-dynamic-import plugin, @babel/plugin-proposal-class-properties plugin, @babel/plugin-proposal-decorators and other syntax translation plug-ins @babel/plugin-syntax-jsx, @vue/babel-preset- JSX and other vue JSX syntax translation plug-ins.

Vue-cli also uses the @babel/ plugin-transform-Runtime plug-in to optimize compilation results.

conclusion

Babel is the official definition of a JavaScript compiler. Plugin is the most fine-grained module, combined into js code translation rules scheme. The most immediate goal is to make your code compatible with the host environment.

However, it is only a compilation of a single file, and does not involve the import of files between files. If the import of multi-module files is involved, a tool like WebPack should be used.

In the modern framework like Vue, the first is the need for tools such as Webpack for code module file management, and the implementation of the framework toolkit (such as vue-Loader), often adopt ES6 and above to achieve. In this case, babel-Loader needs to be installed to perform a secondary translation of the generated JS files to fit the target host environment.

In the process of learning inquiry, it is hard to avoid omissions and mistakes. Welcome your criticism and advice, thank you!