In this paper, starting from vivo Internet technology WeChat public links: mp.weixin.qq.com/s/plJewhUd0… Author: Morrain
One, foreword
In the previous section CommonJS: In ES6, we talked about ES6 Module, which is a specification for modules in ES6. ES6 is short for ECMAScript 6.0, the next generation standard for the JavaScript language. Its first version, ES2015, was officially released in June 2015, and the ES6 mentioned in this article includes ES2015, ES2016, ES2017, and more. As mentioned in the first section of “The Web: Going All the way, Forgetting all the Way”, ES2015 took more than a decade from the development to the release, and introduced many new features and new mechanisms. The progress of browser support for ES6 was far from enough to catch up with the enthusiasm of front-end development brothers to use ES6, so contradictions became increasingly obvious…
What is a Babel
Here’s the definition on the website:
Babel is a JavaScript compiler
Yes, Babel is a compiler for JavaScript. As to what a compiler is, see the project the-super-tiny-compiler for a good answer.
This article is based on the Babel 7.9.0 version for the demonstration and explanation. In addition, learners are advised to read the English official website. The Chinese official website is one version slower than the original website, and much of it is still in English.
Babel is a solution for translating ES6 code into code supported by browsers or other environments. Note that I didn’t mean convert to ES5, because different types and versions of browsers support the new features of ES6 to a different degree. Babel doesn’t convert what the browser already supports, so it depends on the browser version, which I’ll cover later. You can refer to the BrowerSlist project first.
The history of Babel
Before learning any subject, I am used to understanding its history, so that I can deeply understand its significance.
Babel was written by Sebastian McKenzie, an engineer at FaceBook. He released a JavaScript compiler, 6to5, in 2014. As the name suggests, its main function is to convert ES6 into ES5.
ES6 here refers to ES2015, as it was not officially released at the time and ES2015’s name had not yet been officially decided.
Therefore, many people evaluate that 6TO5 is just a transitional plan before ES6 is supported. Its authors strongly disagree with this view, and believe that 6TO5 will not only improve gradually according to the standard, but still have a great potential to influence and promote the formulation of the standard. Because of this, the 6to5 team felt that the name ‘6to5’ did not accurately convey the goals of the project. Plus, when ES6 was officially released, it was named ES2015, which was even more of a departure for 6to5. On February 15, 2015, 6to5 officially changed its name to Babel.
(Image from the Internet)
Babel is the Babylonian name for the tower of Babel, which is a fitting name for 6to5. Envy these people, not only good code writing, but also so literate, unlike us, have to hold a variable name for a long time, eat the loss of no culture. That’s why I called this post “Babel: The Tower of Babel that Sent ES6 to Heaven.”
How to use Babel
Now that we know what Babel is, it’s obvious to start thinking about how to use Babel to convert ES6 code. In addition to the cli tools that Babel provides, it also supports other packaging tools such as WebPack, Rollup, etc. For details, see the configuration descriptions provided on the official website.
In order to get a feel for the original use of Babel, this article uses the CLI of Babel without any other tools.
1. Build the Babel demo project
Build an NPM package with the following command and create a new SRC directory and an index.js file.
npm init -yCopy the code
Package. json has the following content:
{
"name": "demo"."version": "1.0.0"."description": ""."main": "index.js"."scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": []."author": ""."license": "ISC"
}Copy the code
2. Install dependency packages
npm install --save-dev @babel/core @babel/cli @babel/preset-envCopy the code
We’ll see what these packages do later, but first look at usage
Add the Babel command to compile files from SRC to dist:
{
"name": "demo"."version": "1.0.0"."description": ""."main": "src/index.js"."scripts": {
"babel": "babel src --out-dir dist"."test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": []."author": ""."license": "ISC"."devDependencies": {
"@babel/cli": "^ 7.8.4"."@babel/core": "^ 7.9.0"."@babel/preset-env": "^ 7.9.0"}}Copy the code
3. Add the Babel configuration file
Add the babel.config.js file to the project root directory and add the configuration that is compiled by Babel. No configuration is compiled.
const presets = [
[
'@babel/env',
{
debug: true
}
]
]
const plugins = []
module.exports = { presets, plugins }Copy the code
In the example above, the debug configuration is used to print the log when Babel is working, so it is convenient to see what syntax Babel translates.
- Presets are presets that are configured for compilation, and plugins are plug-ins that are configured for compilation. More on this later
- It is recommended to write configuration files in Javascript files rather than JSON files, so that you can dynamically configure the presets and plugins you need to use depending on your environment
const presets = [
[
'@babel/env',
{
debug: true
}
]
]
const plugins = []
if (process.env["ENV"= = ="prod") {
plugins.push(...)
}
module.exports = { presets, plugins }Copy the code
4. The result of compilation
After the configuration, we run the NPM run Babel command, and you can see that the file index.js is generated under the dist folder, as follows:
// src/index.js
const add = (a, b) => a + b
// dist/index.js
"use strict";
var add = function add(a, b) {
return a + b;
};Copy the code
As you can see, the const of ES6 is converted to var and the arrow function is converted to a normal function. The following logs are also displayed:
> babel src --out-dir dist
@babel/preset-env: `DEBUG` option
Using targets:
{}
Using modules transform: auto
Using plugins:
proposal-nullish-coalescing-operator {}
proposal-optional-chaining {}
proposal-json-strings {}
proposal-optional-catch-binding {}
transform-parameters {}
proposal-async-generator-functions {}
proposal-object-rest-spread {}
transform-dotall-regex {}
proposal-unicode-property-regex {}
transform-named-capturing-groups-regex {}
transform-async-to-generator {}
transform-exponentiation-operator {}
transform-template-literals {}
transform-literals {}
transform-function-name {}
transform-arrow-functions {}
transform-block-scoped-functions {}
transform-classes {}
transform-object-super {}
transform-shorthand-properties {}
transform-duplicate-keys {}
transform-computed-properties {}
transform-for-of {}
transform-sticky-regex {}
transform-unicode-regex {}
transform-spread {}
transform-destructuring {}
transform-block-scoping {}
transform-typeof-symbol {}
transform-new-target {}
transform-regenerator {}
transform-member-expression-literals {}
transform-property-literals {}
transform-reserved-words {}
transform-modules-commonjs {}
proposal-dynamic-import {}
Using polyfills: No polyfills were added, since the `useBuiltIns` option was not set.
Successfully compiled 1 file with Babel.Copy the code
How Babel works
Now that you’ve seen how to use it, let’s explore what goes on behind the scenes while familiarizing ourselves with the composition and advanced usage of Babel.
1. Babel workflow
As mentioned earlier, Babel is a pure JavaScript compiler. Any compiler workflow can be roughly divided into three steps:
-
Parser Parses the source file
-
Transfrom conversion
-
The Generator generates new files
Babel is no exception, as shown below:
(Image from the Internet)
Because Babel uses the Acorn engine for parsing, the library converts the source code into an abstract syntax tree (AST), converts the AST, and outputs the converted AST, resulting in a babel-compiled file.
So how did Babel know how to turn? The answer is through plug-ins. Babel provides a plug-in for every new syntax, and the configuration of the plug-in translates the corresponding syntax to the one configured in Babel. Plug-ins are named in the format @babel/plugin-xxx.
2. Babel
(1) @ Babel/preset – env
As mentioned above, @babel/preset-* is actually a collection of preset plug-ins, the most commonly used one is @babel/preset-env, which contains most of the syntax of ES6, the specific plug-in can be seen in the Babel log. If the source code uses a syntax not in @babel/preset-env, an error will be reported, just add it to the plugins manually.
For example, ES6 explicitly states that there are only static methods inside a Class and no static attributes. However, there is a proposal to provide static properties for classes, which are written by adding the static keyword before the instance properties.
// src/index.js
const add = (a, b) => a + b
class Person {
static a = 'a';
static b;
name = 'morrain';
age = 18
}Copy the code
The following error is reported at compile time:
Add @babel/plugin-proposal-class-properties as prompted.
npm install --save-dev @babel/plugin-proposal-class-propertiesCopy the code
// babel.config.js
const presets = [
[
'@babel/env',
{
debug: true
}
]
]
const plugins = ['@babel/plugin-proposal-class-properties']
module.exports = { presets, plugins }Copy the code
Targets is a very important parameter in @babel/preset-env. As we mentioned earlier, Babel translation is on-demand, and there is no conversion for the syntax supported by the environment. By configuring
targetsProperty to let Babel know about the target environment and therefore only translate syntax that is not supported by the environment.
If not configured, all ES6 syntax will be translated by default.
// SRC /index.js const add = (a, b) => a + b // dist/index.js Targets are not configured"use strict";
var add = function add(a, b) {
return a + b;
};Copy the code
Configure as follows
targets
// babel.config.js
const presets = [
[
'@babel/env',
{
debug: true,
targets: {
chrome: '58'
}
}
]
]
const plugins = ['@babel/plugin-proposal-class-properties']
module.exports = { presets, plugins }Copy the code
The compiled results are as follows:
// SRC /index.js const add = (a, b) => a + b // dist/index.js Targets Chrome 58"use strict";
const add = (a, b) => a + b;Copy the code
You can see that const and arrow functions are not translated because these features are already supported in this version of Chrome. The target environment can be flexibly configured according to requirements.
To facilitate further explanation, remove the targets configuration and let Babel translate all syntax by default.
(2) @ Babel/polyfill
A polyfill is an important concept in Babel. Let’s start with the following lines of code:
// src/index.js
const add = (a, b) => a + b
const arr = [1, 2]
const hasThreee = arr.includes(3)
new Promise()Copy the code
After running NPM Run Babel as before, we were surprised to find that array.prototype.includes and promises were not translated!
// dist/index.js
"use strict";
var add = function add(a, b) {
return a + b;
};
var arr = [1, 2];
var hasThreee = arr.includes(3);
new Promise();Copy the code
Babel divides ES6 standards into syntax and built-in. Syntax is the syntax type, such as const and =>, which is translated by default by Babel. The syntax that can be overridden by rewriting is considered built-in, such as includes and promises. Babel only translates syntax by default, but @babel/polyfill is required for built-in. The @babel/ Polyfill implementation is also very simple in that it overrides the built-in additions to ES6. The schematic is as follows:
Object.defineProperty(Array.prototype, 'includes'.function(){
...
})Copy the code
Since Babel announced in version 7.4.0 that @babel/polyfill was deprecated and replaced with core-js, this article uses core-JS directly to illustrate the use of polyfill.
-
Install the core – js
npm install --save core-jsCopy the code
- Note that core-JS is installed using the –save method because it needs to be injected into the source code to provide the execution environment for the built-in injection before executing the code
-
Configuration useBuiltIns
The useBuiltIns parameter is used to control the injection of the built-in in @babel/preset-env. It can be set to ‘entry’, ‘usage’ and false. The default value is false and no gaskets are injected.
When set to ‘entry’, you simply need to import core-JS at the entry point of the entire project.
// src/index.js
import 'core-js'
const add = (a, b) => a + b
const arr = [1, 2]
const hasThreee = arr.includes(3)
new Promise()
// dist/index.js
"use strict";
require("core-js/modules/es7.array.includes");
require("core-js/modules/es6.promise"); / / / /... There are many more // require("regenerator-runtime/runtime");
var add = function add(a, b) {
return a + b;
};
var arr = [1, 2];
var hasThreee = arr.includes(3);
new Promise();Copy the code
- When compiled, Babel will inject all the built-ins that are not supported by the target environment, whether they are used or not. The problem with this is that it is unnecessary to add code and waste package size for projects that use very little.
When set to ‘Usage’, instead of importing core-JS at the entry point of the project, Babel will select the implementation to inject based on the built-in usage during source compilation.
// src/index.js
const add = (a, b) => a + b
const arr = [1, 2]
const hasThreee = arr.includes(3)
new Promise()
// dist/index.js
"use strict";
require("core-js/modules/es6.promise");
require("core-js/modules/es6.object.to-string");
require("core-js/modules/es7.array.includes");
var add = function add(a, b) {
return a + b;
};
var arr = [1, 2];
var hasThreee = arr.includes(3);
new Promise();Copy the code
- The version of Corejs is configured
When useBuiltIns is set to ‘Usage’ or ‘entry’, the corejs parameter of @babel/preset-env needs to be set to specify the version of Corejs used when the built-in implementation is injected. Otherwise, the Babel log output will have a warning.
The final Babel configuration is as follows:
// babel.config.js
const presets = [
[
'@babel/env',
{
debug: true,
useBuiltIns: 'usage',
corejs: 3,
targets: {}
}
]
]
const plugins = ['@babel/plugin-proposal-class-properties']
module.exports = { presets, plugins }Copy the code
(3) @ Babel/plugin – transform – runtime
Before introducing the use of @babel/ plugin-transform-Runtime, here’s an example:
// src/index.js
const add = (a, b) => a + b
const arr = [1, 2]
const hasThreee = arr.includes(3)
new Promise(resolve=>resolve(10))
class Person {
static a = 1;
static b;
name = 'morrain';
age = 18
}
// dist/index.js
"use strict";
require("core-js/modules/es.array.includes");
require("core-js/modules/es.object.define-property");
require("core-js/modules/es.object.to-string");
require("core-js/modules/es.promise");
function _classCallCheck(instance, Constructor) { if(! (instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); }}function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
var add = function add(a, b) {
return a + b;
};
var arr = [1, 2];
var hasThreee = arr.includes(3);
new Promise(function (resolve) {
return resolve(10);
});
var Person = function Person() {
_classCallCheck(this, Person);
_defineProperty(this, "name".'morrain');
_defineProperty(this, "age", 18);
};
_defineProperty(Person, "a", 1);
_defineProperty(Person, "b", void 0);Copy the code
During compilation, the built-in syntax is compatible with require(“core-js/modules/ XXXX “) polyfill, For syntax types during translation, helper functions such as _classCallCheck and _defineProperty are injected into the current module for compatibility. For one module, that might be fine, but for many modules in a project, injecting these helper functions into each module is bound to create a lot of code.
@babel/ plugin-transform-Runtime is designed to reuse these helper functions and reduce the size of the code. In addition, of course, it provides a sandbox environment for compiled code to avoid global contamination.
Using the @ Babel/plugin – transform – runtime
-
(1) the installation
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtimeCopy the code
Where @babel/ plugin-transform-Runtime is used at compile time and installed as a development dependency, @babel/ Runtime is actually a collection of helper functions that need to be introduced into the compiled code, so it is installed as a production dependency
-
② Modify the Babel plugins configuration and add @babel/plugin-transform-runtime
// babel.config.js
const presets = [
[
'@babel/env',
{
debug: true,
useBuiltIns: 'usage',
corejs: 3,
targets: {}
}
]
]
const plugins = [
'@babel/plugin-proposal-class-properties'['@babel/plugin-transform-runtime'
]
]
module.exports = { presets, plugins }Copy the code
- The previous example, compiled once again, you can see, before the helper function, all become similar to the require (” @ Babel/runtime/helpers/classCallCheck “) implementation.
// dist/index.js
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
require("core-js/modules/es.array.includes");
require("core-js/modules/es.object.to-string");
require("core-js/modules/es.promise");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var add = function add(a, b) {
return a + b;
};
var arr = [1, 2];
var hasThreee = arr.includes(3);
new Promise(function (resolve) {
return resolve(10);
});
var Person = function Person() {
(0, _classCallCheck2["default"])(this, Person);
(0, _defineProperty2["default"])(this, "name".'morrain');
(0, _defineProperty2["default"])(this, "age", 18);
};
(0, _defineProperty2["default"])(Person, "a", 1);
(0, _defineProperty2["default"])(Person, "b", void 0);Copy the code
- Configuration @ Babel/plugin – transform – runtime
So far, the syntax for built-in types has been implemented using require(“core-js/modules/ XXXX “) polyfill, for example to support array.prototype.includes, Need the require
(” core – js/modules/es. Array. Includes “) in the array. The prototype to add includes ways to implement, but this could lead to a problem, it is directly modify the prototype, will cause the global pollution. This is not a problem if you are developing a standalone application, but if you are developing a library that is referenced by other projects that happen to implement array.prototype.includes, you are in trouble! The @babel/ plugin-transform-Runtime parameter corejs can be configured to resolve this problem. This parameter is false by default and can be set to 2 or 3 for @babel/runtime-corejs2 or @babel/runtime-corejs3, respectively.
Set the value of corejs for @babel/plugin-transform-runtime to 3 and replace @babel/ Runtime with @babel/runtime-corejs3.
Remove the useBuiltIns and corejs configurations of @babel/preset-env, and remove core-js. Because @babel/ Run-time Corejs3 is used for compatibility with the built-in syntax, useBuiltIns is not used.
npm uninstall @babel/runtime
npm install --save @babel/runtime-corejs3
npm uninstall core-jsCopy the code
// babel.config.js
const presets = [
[
'@babel/env',
{
debug: true,
targets: {}
}
]
]
const plugins = [
'@babel/plugin-proposal-class-properties'['@babel/plugin-transform-runtime',
{
corejs: 3
}
]
]
module.exports = { presets, plugins }
// dist/index.js
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/defineProperty"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));
var add = function add(a, b) {
return a + b;
};
var arr = [1, 2];
var hasThreee = (0, _includes["default"])(arr).call(arr, 3);
new _promise["default"] (function (resolve) {
return resolve(10);
});
var Person = function Person() {
(0, _classCallCheck2["default"])(this, Person);
(0, _defineProperty2["default"])(this, "name".'morrain');
(0, _defineProperty2["default"])(this, "age", 18);
};
(0, _defineProperty2["default"])(Person, "a", 1);
(0, _defineProperty2["default"])(Person, "b", void 0);Copy the code
You can see that the implementations of Promise and arr.includes have become local variables without changing the global implementation.
3. Differences in implementation of Babel Polyfill
So far, there have been three ways to polyfill built-in-type syntax:
-
Use @babel/preset-env, useBuiltIns set to ‘entry’
-
Use @babel/preset-env, useBuiltIns set to ‘usage’
-
Using the @ Babel/plugin – transform – runtime
Targets can be configured based on the target environment. Setting useBuiltIns to ‘Entry’ injects all the built-in syntax that is not supported by the target environment. Setting useBuiltIns to ‘Usage’ injects all the built-in syntax that is not supported by the target environment. The syntax of the injected built-in type is global pollution.
The third method does not support setting targets, so whether the target environment is supported or not is not considered. It implements all the built-in syntax in the form of local variables and does not pollute the global environment.
Babel is considering to solve the problem that the third method does not support setting targets. Currently, the intended solution is to unify the implementation of Polyfill through the Polyfill Provider:
-
The useBuiltIns and corejs parameters in @babel/preset-env are deprecated, and the @babel/preset-env is no longer used to implement polyfill.
-
Deprecate corejs in @babel/plugin-transform-runtime and no longer use @babel/plugin-transform-runtime to implement polyfill.
-
Add the polyfills parameter, similar to today’s presets and plugins, in place of the current polyfill scheme.
-
Push targets in @babel/preset-env up one level to the same level as presets, plugins, and polyfills and share them.
When implemented, Babel will look like this:
// babel.config.js
const targets = [
'> 1%'
]
const presets = [
[
'@babel/env',
{
debug: true
}
]
]
const plugins = [
'@babel/plugin-proposal-class-properties'
]
const polyfills = [
[
'corejs3',
{
method: 'usage-pure'
}
]
]
module.exports = { targets, presets, plugins, polyfills }Copy the code
There are three methods: ‘entry-global’, ‘usage-global’, and ‘usage-pure’.
-
‘entry-global’ is equivalent to useBuiltIns in @babel/preset-env: ‘entry’
-
‘usage-global’ is equivalent to useBuiltIns in @babel/preset-env: ‘usage’
-
‘usage-pure’ is equivalent to Corejs in @babel/ plugin-transform-Runtime
In this article, we use the native @babel/ CLI to compile the files. In practice, we use more third-party tools such as Webpack and Rollup.
So in the next video, we’ll talk about the packaging tool WebPack.
5. References
-
6to5 JavaScript Transpiler Changes Name to Babel
-
Babel Learning Series 2-Babel Design, Composition
-
Learn how Babel works
-
RFC: Rethink polyfilling story
For more content, please pay attention to vivo Internet Technology wechat official account
Note: To reprint the article, please contact wechat: Labs2020.