The original link

preface

It’s certainly fair to say that Babel was a game-changing tool on the front end, and that its presence has allowed many programmers to happily embrace the new ES6 syntax. But you’re just gonna let Babel run on Extranet? I’m not sure. I’ve been cheated before, and I came up with the idea of working on Babel transcoding. Instead of analyzing the Babel source code, this article looks at the end product of the Babel transformation.

Es6 is also known as ES2015 in Babel. Due to the abundant es2015 syntax, this article only selects some common preset points and mainly analyzes the babel-preset-2015 plug-in (preset is often used in webpack during React development).

babel-preset-2015

If you open the Babel-Preset2015 plugin, there are 20 plugins. Those familiar with es2015 syntax will know, more or less literally, which syntax transformations a plug-in is used for

  • Babel-plugin-transform-es2015-template-literals => ES2015 template
  • babel-plugin-transform-es2015-literals
  • Babel-plugin-transform-es2015-function-name => function name attribute
  • Babel-plugin-transform-es2015 -arrow-functions => arrow functions
  • Babel-plugin-transform-es2015-block-scoped -functions => function block-level scope
  • Babel – plugin – transform – es2015 – classes = > class class
  • Babel-plugin-transform-es2015-object-super => super provides a way to call prototype
  • Babel-plugin-transform-es2015-properties => Quick definition of object properties, e.g. Obj = {x, y}
  • Babel-plugin-transform-es2015-computed -properties => parenthesis properties of objects, such as obj = {[‘x]: 1}
  • Babel-plugin-transform-es2015 -for-of => object for of traversal
  • babel-plugin-transform-es2015-sticky-regex
  • babel-plugin-transform-es2015-unicode-regex
  • Babel-plugin-check-es2015 -constants => const constants
  • Babel-plugin-transform-es2015-spread => object extension operator properties, such as… foobar
  • Babel-plugin-transform-es2015-parameters => Function parameter default value and extended operator
  • Babel-plugin-transform-es2015-destructuring => Assignment destructuring
  • Babel-plugin-transform-es2015-block-scoping => let and const block-level scope
  • Babel-plugin-transform-es2015 -typeof-symbol => symbol properties
  • Babel-plugin-transform-es2015-modules-commonjs => CommonJS module load
  • Babel-plugin-transform-regenerator => Generator features

var, const and let

Const and let are now both converted to var. So how is const guaranteed to be constant? If you change the value of a const constant a second time in the source code, Babel compilation will report an error. Transformation before

var a = 1;
let b = 2;
const c = 3;
Copy the code

After the transformation:

var a = 1;
var b = 2;
var c = 3;
Copy the code

What is the block-level effect of let? Take a look at the following example, which essentially changes the variable name at the block level to make it different from the outer layer. Before conversion:

let a1 = 1;
let a2 = 6;

{
    let a1 = 2;
    let a2 = 5;

    {
        let a1 = 4;
        let a2 = 5;
    }
}
a1 = 3;
Copy the code

After the transformation:

var a1 = 1;
var a2 = 6;

{
    var _a = 2;
    var _a2 = 5;

    {
        var _a3 = 4;
        var _a4 = 5;
    }
}
a1 = 3;
Copy the code

Assignment to deconstruct

When we write React, we use negative deconstruction to fetch the value of an object. It’s pretty cool to use, like this:

var props = {
    name: "heyli",
    getName: function() {

    },
    setName: function() {

    }
};

let { name, getName, setName } = this.props;
Copy the code

Let’s look at the result of the transformation:

var props = {
    name: "heyli",
    getName: function getName() {},
    setName: function setName() {}
};

var name = props.name;
var getName = props.getName;
var setName = props.setName;
Copy the code

What about arrays? If it’s an anonymous array, Babel will help you define a variable to hold the array, and then assign values to the variables that need to be assigned. Before conversion:

var [ a1, a2 ] = [1, 2, 3];
Copy the code

After the transformation:

var _ref = [1, 2, 3];
var a1 = _ref[0];
var a2 = _ref[1];
Copy the code

Seeing this, I feel that the conversion results are quite consistent with our expectations. Lol, the usage nightmare hasn’t started yet.

What if we used anonymous objects for direct assignment deconstruction? As follows. Babel, in order to make the received variable unique, directly pieced together the attributes of the anonymous object to form the variable that received the anonymous object, which scared me to check the project for this notation. Before conversion:

var { abc, bcd, cde, def } = { "abc": "abc", "bcd": "bcd", "cde": "cde", "def": "def", "efg": "efg", "fgh": "fgh" };
Copy the code

After the transformation:

var _abc$bcd$cde$def$efg$ = { "abc": "abc", "bcd": "bcd", "cde": "cde", "def": "def", "efg": "efg", "fgh": "fgh" };
var abc = _abc$bcd$cde$def$efg$.abc;
var bcd = _abc$bcd$cde$def$efg$.bcd;
var cde = _abc$bcd$cde$def$efg$.cde;
var def = _abc$bcd$cde$def$efg$.def;
Copy the code

There is also a deeper type of object deconstruction assignment:

var obj = {
    p1: [
        "Hello",
        { p2: "World" }
    ]
};

var { p1: [s1, { p2 }] } = obj;
Copy the code

After the transformation:

Var _slicedToArray = (function() {function sliceIterator(arr, I) {var _arr = []; var _n = true; var _d = false; var _e = undefined; Try {// use symbol. iterator to create a traversable object and go through it. for (var _i = arr[Symbol.iterator](), _s; ! (_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (! _n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function(arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }}; }) (); var obj = { p1: ["Hello", { p2: "World" }] }; var _obj$p = _slicedToArray(obj.p1, 2); var s1 = _obj$p[0]; var p2 = _obj$p[1].p2;Copy the code

Babel produces a common code at the top of the code _slicedToArray. Some properties of the object are converted into arrays to facilitate the deconstruction of the assignment. However, Symbol. Iterator is not very compatible (see figure below), so use it with caution.

In addition, the following string assignment destruct also uses the _slicedToArray method:

const [a, b, c, d, e] = 'hello';
Copy the code

Function parameter default values and extended operators

In the days of ES5, we used to write the default values of arguments like this:

function func(x, y) {
    var x = x || 1;
    var y = y || 2;
}
Copy the code

Let’s look at Babel’s conversion:

function func({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}

function func1(x = 1, y = 2) {
    return [x, y];
}
Copy the code
function func() {
  var _ref = arguments.length <= 0 || arguments[0] === undefined ? { x: 0, y: 0 } : arguments[0];

  var x = _ref.x;
  var y = _ref.y;

  return [x, y];
}

function func1() {
  var x = arguments.length <= 0 || arguments[0] === undefined ? 1 : arguments[0];
  var y = arguments.length <= 1 || arguments[1] === undefined ? 2 : arguments[1];

  return [x, y];
}
Copy the code

Arguments.babel [arguments] The first case involves deconstructing the assignment, so the values of x and y could still be undefined. In the second case, the default values for the two arguments are 1 and 2, respectively.

Let’s do another one. . Y means it receives the rest of the arguments. Arguments The rest of the arguments besides the arguments of the first label. Before conversion:

function func(x, ... y) { console.log(x); console.log(y); return x * y.length; }Copy the code

After the transformation:

function func(x) {
    console.log(x);

    for (var _len = arguments.length, y = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
        y[_key - 1] = arguments[_key];
    }

    console.log(y);
    return x * y.length;
}
Copy the code

Arrow function

The main thing about clipping is that you don’t have to write the function code, and you can just use this without having to worry about context switching. In the old days we used to have to put an extra _this/self in the outer layer straight to this. Babel’s conversion is essentially the same as ours. Before conversion:

var obj = { prop: 1, func: function() { var _this = this; var innerFunc = () => { this.prop = 1; }; var innerFunc1 = function() { this.prop = 1; }; }};Copy the code

After the transformation:

var obj = { prop: 1, func: function func() { var _this2 = this; var _this = this; var innerFunc = function innerFunc() { _this2.prop = 1; }; var innerFunc1 = function innerFunc1() { this.prop = 1; }; }};Copy the code

The object’s capabilities are enhanced

A quick definition of an object property

Before conversion:

var a = 1,
    b = "2",
    c = function() {
        console.log('c');
    };

var obj = {a, b, c};
Copy the code

After the transformation:

var a = 1,
    b = "2",
    c = function c() {
    console.log('c');
};

var obj = { a: a, b: b, c: c };
Copy the code

Object with parenthesis properties

Es2015 has added the ability to explain attributes in brackets in objects, which is especially useful for variables, constants, and other object attributes. Before conversion:

const prop2 = "PROP2";
var obj = {
    ['prop']: 1,
    ['func']: function() {
        console.log('func');
    },
        [prop2]: 3
};
Copy the code

After the transformation:

var _obj; 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 prop2 = "PROP2"; var obj = (_obj = {}, _defineProperty(_obj, 'prop', 1), _defineProperty(_obj, 'func', function func() { console.log('func'); }), _defineProperty(_obj, prop2, 3), _obj);Copy the code

Seemingly simple properties, Babel takes them to task. A _defineProperty function was added to define attributes for the newly created _obj = {}. In addition, a series of left-to-right operations are wrapped in parentheses to make the definition more concise.

Use super to call Prototype

We used to use obj. Prototype or try to use this to find methods above Prototype. Babel wrote his own algorithm for finding methods/attributes on the Prototype chain. Transformation before

var obj = { toString() { // Super calls return "d " + super.toString(); }};Copy the code

After the transformation:

var _obj; Var _get = function get(object, property, receiver) {// If prototype is null If (object === null) object = Function. Prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); If (parent === null) {return undefined; if (parent === null) {return undefined; } else { return get(parent, property, receiver); Else if ("value" in desc) {return desc. Value; } // If it is a method, it is called with a call, and receiver is the object called else {var getter = desc.get; // Getmethod returned by getOwnPropertyDescriptor if (getter === undefined) {return undefined; } return getter.call(receiver); }}; var obj = _obj = { toString: function toString() { // Super calls return "d " + _get(Object.getPrototypeOf(_obj), "toString", this).call(this); }};Copy the code

Object. The Object and assign. Is

The new Object. Assign feature in ES6 greatly facilitates Object cloning and replication. But Babel’s ES2015 Preset is not supported, so preset is not converted, which can cause some mobile phones to report errors when preset is used. Therefore, developers will use the object-assign library for compatibility.

Object.is is used to compare Object values and types, and ES2015 Preset also does not support compilation.

Es6 template

Multiline string

Before conversion:

console.log(`string text line 1
string text line 2`);
Copy the code

After the transformation:

console.log("string text line 1\nstring text line 2");
Copy the code

Operation of a variable in a character

Before conversion:

var a = 5;
var b = 10;
console.log(`Fifteen is ${a + b} and not ${2 * a + b}.`);
Copy the code

After the transformation:

var a = 5;
var b = 10;
console.log("Fifteen is " + (a + b) + " and not " + (2 * a + b) + ".");
Copy the code

The label template

This new feature in ES6 gives template processing more power. Instead of replacing templates with various replace methods, a unified handler is used to handle templates. Babel’s conversion mainly adds two attributes, so it doesn’t seem like a huge compilation. Before conversion:

var a = 5; var b = 10; function tag(strings, ... values) { console.log(strings[0]); // "Hello " console.log(strings[1]); // " world " console.log(values[0]); // 15 console.log(values[1]); // 50 return "Bazinga!" ; } tag`Hello ${ a + b } world ${ a * b }`;Copy the code

After the transformation:

var _templateObject = _taggedTemplateLiteral(["Hello ", " world ", ""], ["Hello ", " world ", ""]); Function _taggedTemplateLiteral(strings, raw) {return object.freeze (object.defineProperties (strings, {raw: { value: Object.freeze(raw) } })); } // Define two immutable attributes for the passed object, strings and RAW. var a = 5; var b = 10; function tag(strings) { console.log(strings[0]); // "Hello " console.log(strings[1]); // " world " console.log(arguments.length <= 1 ? undefined : arguments[1]); // 15 console.log(arguments.length <= 2 ? undefined : arguments[2]); // 50 return "Bazinga!" ; } tag(_templateObject, a + b, a * b);Copy the code

Modularity and classes

Class class

Javascript implementation OO has always been a hot topic. From the primitive days when you had to manually maintain the superclass constructor in the constructor, to the days when you wrapped the function to extend, to the days when you could write classes like any other object-oriented language. The es2015 class scheme is still a transitional scheme, and the features it supports still do not cover all the features of the class. Currently, we mainly support:

  • constructor
  • The static method
  • The get method
  • Set method
  • Class inheritance
  • Super calls the superclass method.

Before conversion:

class Animal { constructor(name, type) { this.name = name; this.type = type; } walk() { console.log('walk'); } run() { console.log('run') } static getType() { return this.type; } get getName() { return this.name; } set setName(name) { this.name = name; } } class Dog extends Animal { constructor(name, type) { super(name, type); } get getName() { return super.getName(); }}Copy the code

After the conversion (the auxiliary methods are omitted because the code is too long) :

/ * *...... **/ var Animal = (function () {function Animal(name, type) {// _classCallCheck(this, Animal); this.name = name; this.type = type; } // _createClass(Animal, [{key: 'walk', value: function walk() { console.log('walk'); } }, { key: 'run', value: function run() { console.log('run'); } }, { key: 'getName', get: function get() { return this.name; } }, { key: 'setName', set: function set(name) { this.name = name; } }], [{ key: 'getType', value: function getType() { return this.type; } }]); return Animal; }) (); Var Dog = (function (animal) {// If (animal) {// if (animal); function Dog(name, type) { _classCallCheck(this, Dog); Constructor // Babel forces the subclass to use super in constructor, Otherwise the compiler complains return _possibleConstructorReturn (this, Object getPrototypeOf (Dog). The call (this, the name, type)); } _createClass(Dog, [{ key: 'getName', get: Function get() {function get() {function get() { Return _get(object.getProtoTypeof (dog.prototype), 'getName', this).call(this);}}]); return _get(object.getProtoTypeof (dog.prototype), 'getName', this).call(this); return Dog; })(Animal);Copy the code
Function _classCallCheck(instance, constructor) {if (! (instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); }}Copy the code
Var _createClass = (function() {function defineProperties(target, props) {for (var I = 0; i < props.length; i++) { var descriptor = props[i]; / / es6 specification requirements class method for non - enumerable descriptor. The enumerable = descriptor. The enumerable | | false; descriptor.configurable = true; // For setter and getter methods, writable is false if ("value" in descriptor) descriptor. Writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function(Constructor, protoProps, StaticProps) {// if (protoProps) defineProperties(Constructor. Prototype, protoProps); // Static methods are defined directly on the constructor function if (staticProps) defineProperties(constructor, staticProps); return Constructor; }; }) ();Copy the code
Function _inherits(subClass, superClass) {// If (typeof superClass! == "function" && superClass ! == null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } // Make the chain subclass.prototype.__proto__ point to the superClass, Prototype = Object. Create (superClass && superClass. Prototype, {constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); // Ensure that subclass.__proto__ refers to the superClass if (superClass) object.setProtoTypeof? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }Copy the code
/ / subclass constructor function _possibleConstructorReturn {(self, call) if (! self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } / / / object is returned if the call is function return call && (typeof call = = = "object" | | typeof call = = = "function")? call : self; }Copy the code

When refactoring projects with React, all react components used ES6 instead of ES5. It is more convenient to continue writing with the benefits of classes, but mixins are not available, and you need decorators in newer ES7 syntax to implement mixin-like functionality (such as pureRender). However, after analyzing the Babel source code, it turns out that Babel defines a number of methods to implement the class feature, even if it doesn’t look very elegant.

modular

When developing React, we often use Webpack and Babel’s ES2015 and React preset to build. An article I read earlier gave some insight into module loading here at Babel (” Analyzing how Babel transforms ES6 Modules “).

Example:

// test.js import { Animal as Ani, catwalk } from "./t1"; import * as All from "./t2"; class Cat extends Ani { constructor() { super(); } } class Dog extends Ani { constructor() { super(); }}Copy the code
// t1.js
export class Animal {

    constructor() {

    }

}

export function catwal() {
    console.log('cat walk');
};
Copy the code
// t2.js
export class Person {
    constructor() {

    }

}

export class Plane {
    constructor() {

    }

}
Copy the code

Compiled with WebPack and Babel:

// T1.js module object.defineProperty (exports, "__esModule", {value: true}); exports.catwal = catwal; Var Animal = exports.Animal = function Animal() {_classCallCheck(this, Animal); }; function catwal() { console.log('cat walk'); }; // T2.js module object.defineProperty (exports, "__esModule", {value: true}); // Exports, "__esModule", {value: true}); Var Person = exports.person = function Person() {_classCallCheck(this, Person); }; var Plane = exports.Plane = function Plane() { _classCallCheck(this, Plane); }; // The test.js module var _t = __webpack_require__(1); var _t2 = __webpack_require__(3); Var All = _interopRequireWildcard(_T2); var All = _interopRequireWildcard(_t2); Function _interopRequireWildcard(obj) {if (obj && obj.__esModule) {return obj; Var newObj = {}; var newObj = {}; if (obj ! = null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; Var c = (function (_inherits) {_inherits(Cat, _Ani); function Cat() { _classCallCheck(this, Cat); return _possibleConstructorReturn(this, Object.getPrototypeOf(Cat).call(this)); } return Cat; })(_t.Animal); var Dog = (function (_Ani2) { _inherits(Dog, _Ani2); function Dog() { _classCallCheck(this, Dog); return _possibleConstructorReturn(this, Object.getPrototypeOf(Dog).call(this)); } return Dog; })(_t.Animal);Copy the code

Es6 module loading is multi-object multi-load, while CommonJS is single-object single-load. Babel needs to do some tricks to write es6 modules as commonJS. The __esModule attribute is used to determine whether a module is compiled by Babel. References to each module are then processed using _interopRequireWildcard.

Another discovery was that Babel compiled code packaged with Webpack included the same class inheritance helper methods in every module, which were ignored during development. Use the ES5 syntax to create a smaller JS bundle than using es6 classes when developing React.

babel es2015 loose mode

[[email protected]] [[email protected]]

Uncaught TypeError: Cannot assign to read only property '__esModule' of #.

After verification, it is found that the mode of babel-es2015 loader in construction is faulty, which will lead to errors reported by users of Android4.0. Just use loose Mode to solve the problem. Here are the related StackOverflow issues and the NPM packages that address them.

  • stackoverflow
  • babel-preset-est2015-loose npm package

Babel 6: Loose Mode Babel 6: Loose Mode

The essence is that normal mode conversions are much closer to es6 writing, and many properties are done via Object.defineProperty. Loose Mode is closer to es5, with better performance and compatibility, but converting this part of the code to Native ES6 is a bit more difficult (feel this is not a disadvantage, have the source code is ok).

The above esModule solution, in essence, is to

Object.defineProperty(exports, "__esModule", {
    value: true
});
Copy the code

Exports.__esmodule = true;

For another example, the Cat class definition looks like this:

class Cat extends Ani { constructor() { super(); } miao() { console.log('miao'); }}Copy the code

Normal mode will compile to:

var Cat = (function (_Ani) {
    _inherits(Cat, _Ani);

    function Cat() {
        _classCallCheck(this, Cat);

        return _possibleConstructorReturn(this, Object.getPrototypeOf(Cat).call(this));
    }

    _createClass(Cat, [{
        key: "miao",
        value: function miao() {
            console.log('miao');
        }
    }]);

    return Cat;
})(_t.Animal);
Copy the code

Loose mode compiles to:

var Cat = (function (_Ani) {
    _inherits(Cat, _Ani);

    function Cat() {
        _classCallCheck(this, Cat);

        return _possibleConstructorReturn(this, _Ani.call(this));
    }

    Cat.prototype.miao = function miao() {
        console.log('miao');
    };

    return Cat;
})(_t.Animal);
Copy the code

The loose mode in Babel ES2015 is mainly for the following plugins:

  • transform-es2015-template-literals
  • transform-es2015-classes
  • transform-es2015-computed-properties
  • transform-es2015-for-of
  • transform-es2015-spread
  • transform-es2015-destructuring
  • transform-es2015-modules-commonjs

Each conversion mode is not described here, you can try at home.

If there are mistakes, please correct!

Reference article:

babel try out

  • babeljs.io/repl/

template literals

  • Developer.mozilla.org/en-US/docs/...

block-scoped functions

  • Blogs.msdn.microsoft.com/cdndevs/201...
  • www.programmerinterview.com/index.php/j...
  • www.2ality.com/2015/02/es6...

classes

  • Purplebamboo. Making. IO / 2014/07/13 /...
  • Blog. Rainy. Im / 2015/07/20 /...

objects

  • Fourkitchens.com/blog/articl...

commonjs and es6 module

  • Ryerh.com/javascript/...
  • www.2ality.com/2015/12/bab...
  • www.2ality.com/2015/02/es6...

Copy the code