Babel-preset-env upgrades to north

preface

In September Babel announced that ES20xx presets such as ES2015/ES2016/ES2017, etc. were all scrapped in favor of Babel-Preset -env, promising that it would be a “future-proof” solution. So when we re-install these ES20XX packages, we will all throw a message like this:

NPM WARN deprecated [email protected]: Thanks for using Babel: We recommend using babel-env now: please read babeljs.io/env to update!Copy the code

It suggests that if we want to use the latest preset features in the future, there’s no need to install the preset-esxx, just one package will do it. This upgrade was inevitable on our front end, so I took a taste of it and started my upgrade migration.

Before we upgrade, let’s take a look at some of the package’s new features.

babel-preset-envImplementation principle of

This preset can automatically choose Babel and Polyfills to compile ES2015+ code using the target browser or runtime environment you configure. Json in the source code to see how many preset plugins are supported (these are referenced in the source code available-plugins.js) :

"Babel - the plugin - check - es2015 - constants" : "7.0.0 - beta. 2", "Babel - plugin - syntax - async - generators" : 7.0.0 - beta. "0" and "Babel - plugin - syntax - object - the rest - spread" : 7.0.0 - beta. "0", "Babel - plugin - syntax - optional - catch - binding" : 7.0.0 - beta. "0" and "Babel - plugin - syntax - trailing - function - commas" : 7.0.0 - beta. "0" and "Babel - plugin - transform - async - to - the generator" : "7.0.0 - beta. 2", "Babel - plugin - transform - async - generator - functions provides" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 - arrow - functions provides" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 - block - scoped - functions provides" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 - block - scoping" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 - classes" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 computed - properties" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 - destructuring" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 - duplicate keys" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 - for -" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 - function - the name" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 - literals" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 modules - amd" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 modules -- commonjs" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 modules - systemjs" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 modules - umd" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 - object - super" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 - the parameters" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 -- shorthand properties" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 - spread" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 - sticky - regex" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 - the template - literals" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 typeof - symbol" : "7.0.0 - beta. 2", "Babel - plugin - transform - es2015 unicode - regex" : "7.0.0 - beta. 2", "Babel - plugin - transform - exponentiation - operator" : "7.0.0 - beta. 2", "Babel - plugin - transform - new - target" : "7.0.0 - beta. 2", "Babel - plugin - transform - regenerator" : "7.0.0 - beta. 2", "Babel - plugin - transform - object - the rest - spread" : "7.0.0 - beta. 2", "Babel - plugin - transform - optional - catch - binding" : "7.0.0 - beta. 2", "Babel - plugin - transform - unicode - property - regex" : "^ at 2.0.5",Copy the code

In addition to these plug-ins, there are some built-in definitions, including new methods for Array, and more. There is a reference in the source code at SRC /built-in- defines.js. But it may not be complete. For example, there is no package for array.includes ().

So how does babel-preset-env automatically load the corresponding plug-in and built-in methods using the target we set?

By reading the code, you can see that support for all plug-ins and built-in methods is maintained in the /data/ directory, such as /data/plugin.json:

"transform-es2015-arrow-functions": { "chrome": "47", "edge": "13", "firefox": "45", "safari": "10", "node": "6" and "ios", "10", "opera" : "34" and "electron" : "0.36"}Copy the code

The arrow function has been supported since Chrome47 and can be seen in other browsers. Then, depending on the support for these plug-ins, the function in/SRC /index.js determines whether the currently set target needs to load the plugin:

export const isPluginRequired = (
  supportedEnvironments: Targets,
  plugin: Targets,
): boolean => {
  ......
}
Copy the code

This gives us a list of plug-ins or built-in methods for a given environment.

Knowing the implementation principle of Babel-Preset -env will give us some assurance in the migration and upgrade process, instead of being in a state of trial and error.

As for other implementations in the code, how to parse target and how to load built-in methods, you can read the source code sometime, in case you need to implement a similar feature next time.

babel-preset-envUpgrade migration of

To give us a comparison, we will take out a configuration before the migration and a configuration after the migration, so that we can make a comparison, after all, the version before the migration must be stable version. At the same time, we will do the corresponding migration of front-end JS and NodeJS respectively. Babelrc and nodejs file configuration before migration:

Front end:

{ "presets": [ ["es2015", {"modules": false}], "react", "stage-1" ], "plugins": [ "transform-async-to-generator", "transform-decorators-legacy", "transform-object-rest-spread", "transform-runtime", ]}Copy the code

Nodejs side:

{
  "presets": [
    "es2015",
    "react",
    "stage-0"
  ],
  "plugins": [
    "transform-class-properties",
    "transform-async-to-generator",
    "transform-decorators-legacy",
    "transform-object-rest-spread"
  ]
}
Copy the code

Let’s talk about the front end first.

Front-end migration and upgrade

First of all, we know what corresponding plug-ins are available for babel-Preset – ES2015, plus additional plug-ins configured. For a general list of plug-ins, refer to Table 1 below.

Ok, we have the general list of plugins for the old configuration, now let’s use babel-preset-env to get another list of plugins.

According to the interpretation of official document, just need to. Babelrc files in the preset options about es2015 / es2016 / es2017 / latest replace env. So we’re going to use this.babelrc file, because it’s mobile, so we’re going to configure the target to be identified by the system.

{" presets ": [[" env" {" targets ": {" browsers" : [" Android > = 4.0 ", "ios > = 6"]}, "debug" : true, "include" : [], "useBuiltIns": false }], "react", "stage-0" ], "plugins": [ "transform-runtime", "transform-decorators-legacy", "transform-object-rest-spread" ] }Copy the code

Babel =true (debug=true)

Using targets:
{
  "android": "4",
  "ios": "6"
}

Modules transform: commonjs

Using plugins:
  check-es2015-constants {"android":"4","ios":"6"}
  transform-es2015-arrow-functions {"android":"4","ios":"6"}
  transform-es2015-block-scoped-functions {"android":"4","ios":"6"}
  transform-es2015-block-scoping {"android":"4","ios":"6"}
  transform-es2015-classes {"android":"4","ios":"6"}
  transform-es2015-computed-properties {"android":"4","ios":"6"}
  transform-es2015-destructuring {"android":"4","ios":"6"}
  transform-es2015-duplicate-keys {"android":"4","ios":"6"}
  transform-es2015-for-of {"android":"4","ios":"6"}
  transform-es2015-function-name {"android":"4","ios":"6"}
  transform-es2015-literals {"android":"4","ios":"6"}
  transform-es2015-object-super {"android":"4","ios":"6"}
  transform-es2015-parameters {"android":"4","ios":"6"}
  transform-es2015-shorthand-properties {"android":"4","ios":"6"}
  transform-es2015-spread {"android":"4","ios":"6"}
  transform-es2015-sticky-regex {"android":"4","ios":"6"}
  transform-es2015-template-literals {"android":"4","ios":"6"}
  transform-es2015-typeof-symbol {"android":"4","ios":"6"}
  transform-es2015-unicode-regex {"android":"4","ios":"6"}
  transform-regenerator {"android":"4","ios":"6"}
  transform-exponentiation-operator {"android":"4","ios":"6"}
  transform-async-to-generator {"android":"4","ios":"6"}
  syntax-trailing-function-commas {"android":"4","ios":"6"}
Copy the code

With additional plugins configured, compare the list of plugins to the list of plugins configured previously:

Table 1: Comparison of new and old configuration plug-in lists

The plug-in The old configuration The new configuration
check-es2015-constants Y Y
transform-es2015-arrow-functions Y Y
transform-es2015-block-scoped-functions Y Y
transform-es2015-block-scoping Y Y
transform-es2015-classes Y Y
transform-es2015-computed-properties Y Y
transform-es2015-destructuring Y Y
transform-es2015-duplicate-keys Y Y
transform-es2015-for-of Y Y
transform-es2015-function-name Y Y
transform-es2015-literals Y Y
transform-es2015-modules-commonjs Y Y(print this line: Modules Transform: commonjs)
transform-es2015-object-super Y Y
transform-es2015-parameters Y Y
transform-es2015-shorthand-properties Y Y
transform-es2015-spread Y Y
transform-es2015-sticky-regex Y Y
transform-es2015-template-literals Y Y
transform-es2015-typeof-symbol Y Y
transform-es2015-unicode-regex Y Y
transform-regenerator Y Y
transform-exponentiation-operator N Y
syntax-trailing-function-commas N Y
transform-async-to-generator Y Y
transform-decorators-legacy Y Y
transform-object-rest-spread Y Y
transform-runtime Y Y

Obviously our configuration is much better than the old one.

Children familiar with preset may wonder: Why aren’t these plug-ins configured in the include field inside preset? Plugin-features.js must exist in this list, otherwise an error will be reported.

At this point, our front-end migration is complete. Next we want to make some upgrades. What are the upgrades?

In normal code writing, many of the built-in methods are already in use, but sometimes this bad situation occurs:

On some of the older phones a page reported an error or even couldn’t open the page simply because we used built-in methods like includes/find in the component’s willMount phase. But because our previous configuration was not configured to translate these built-in methods, none of these methods will be compiled to.

It was possible to compile these methods using Babel-Polyfill in the old configuration, but it wasn’t flexible enough to import the entire package, causing unnecessary overhead. In the new configuration, however, we only need to turn on a switch that implements: useBuiltIns=true. According to the official document:

This field gives babel-Preth-env a way to polyfill(but also via babel-polyfill). Instead, it simply references Babel-Polyfill directly by loading the corresponding set of plug-in translations based on the current configured environment. For example, we update the following configuration:

{" presets ": [[" env" {" targets ": {" browsers" : [" Android > = 4.0 ", "ios > = 6"]}, "debug" : true, "include" : [], "useBuiltIns": true }], "react", "stage-1" ], "plugins": [ "transform-runtime", "transform-decorators-legacy", "transform-object-rest-spread" ] }Copy the code

Then the debug print is as follows:

Using polyfills: es6.typed.array-buffer {"android":"4","ios":"6"} es6.typed.int8-array {"android":"4","ios":"6"} es6.typed.uint8-array {"android":"4","ios":"6"} es6.typed.uint8-clamped-array {"android":"4","ios":"6"} es6.typed.int16-array {"android":"4","ios":"6"} es6.typed.uint16-array {"android":"4","ios":"6"} es6.typed.int32-array {"android":"4","ios":"6"} es6.typed.uint32-array {"android":"4","ios":"6"} es6.typed.float32-array {"android":"4","ios":"6"} es6.typed.float64-array {"android":"4","ios":"6"} es6.map {"android":"4","ios":"6"} es6.set {"android":"4","ios":"6"} es6.weak-map {"android":"4","ios":"6"} es6.weak-set {"android":"4","ios":"6"} es6.reflect.apply {"android":"4","ios":"6"} es6.reflect.construct {"android":"4","ios":"6"} es6.reflect.define-property {"android":"4","ios":"6"} es6.reflect.delete-property {"android":"4","ios":"6"} es6.reflect.get {"android":"4","ios":"6"} es6.reflect.get-own-property-descriptor {"android":"4","ios":"6"} es6.reflect.get-prototype-of  {"android":"4","ios":"6"} es6.reflect.has {"android":"4","ios":"6"} es6.reflect.is-extensible {"android":"4","ios":"6"}  es6.reflect.own-keys {"android":"4","ios":"6"} es6.reflect.prevent-extensions {"android":"4","ios":"6"} es6.reflect.set  {"android":"4","ios":"6"} es6.reflect.set-prototype-of {"android":"4","ios":"6"} es6.promise {"android":"4","ios":"6"} es6.symbol {"android":"4","ios":"6"} es6.object.freeze {"android":"4","ios":"6"} es6.object.seal {"android":"4","ios":"6"} es6.object.prevent-extensions {"android":"4","ios":"6"} es6.object.is-frozen {"android":"4","ios":"6"} es6.object.is-sealed {"android":"4","ios":"6"} es6.object.is-extensible {"android":"4","ios":"6"} es6.object.get-own-property-descriptor {"android":"4","ios":"6"} es6.object.get-prototype-of {"android":"4","ios":"6"} es6.object.keys {"android":"4","ios":"6"} es6.object.get-own-property-names {"android":"4","ios":"6"} es6.object.assign {"android":"4","ios":"6"} es6.object.is {"android":"4","ios":"6"} es6.object.set-prototype-of {"android":"4","ios":"6"} es6.function.name {"android":"4","ios":"6"} es6.string.raw {"android":"4","ios":"6"} es6.string.from-code-point {"android":"4","ios":"6"} es6.string.code-point-at {"android":"4","ios":"6"} es6.string.repeat {"android":"4","ios":"6"} es6.string.starts-with {"android":"4","ios":"6"} es6.string.ends-with {"android":"4","ios":"6"} es6.string.includes {"android":"4","ios":"6"} es6.regexp.flags {"android":"4","ios":"6"} es6.regexp.match {"android":"4","ios":"6"} es6.regexp.replace {"android":"4","ios":"6"} es6.regexp.split {"android":"4","ios":"6"} es6.regexp.search {"android":"4","ios":"6"} es6.array.from {"android":"4","ios":"6"} es6.array.of {"android":"4","ios":"6"} es6.array.copy-within {"android":"4","ios":"6"} es6.array.find {"android":"4","ios":"6"} es6.array.find-index {"android":"4","ios":"6"} es6.array.fill {"android":"4","ios":"6"} es6.array.iterator {"android":"4","ios":"6"} es6.number.is-finite {"android":"4","ios":"6"} es6.number.is-integer {"android":"4","ios":"6"} es6.number.is-safe-integer {"android":"4","ios":"6"} es6.number.is-nan {"android":"4","ios":"6"} es6.number.epsilon {"android":"4","ios":"6"} es6.number.min-safe-integer {"android":"4","ios":"6"} es6.number.max-safe-integer {"android":"4","ios":"6"} es6.math.acosh {"android":"4","ios":"6"}  es6.math.asinh {"android":"4","ios":"6"} es6.math.atanh {"android":"4","ios":"6"} es6.math.cbrt {"android":"4","ios":"6"} es6.math.clz32 {"android":"4","ios":"6"} es6.math.cosh {"android":"4","ios":"6"} es6.math.expm1 {"android":"4","ios":"6"} es6.math.fround {"android":"4","ios":"6"} es6.math.hypot {"android":"4","ios":"6"} es6.math.imul {"android":"4","ios":"6"} es6.math.log1p {"android":"4","ios":"6"} es6.math.log10 {"android":"4","ios":"6"} es6.math.log2 {"android":"4","ios":"6"} es6.math.sign {"android":"4","ios":"6"}  es6.math.sinh {"android":"4","ios":"6"} es6.math.tanh {"android":"4","ios":"6"} es6.math.trunc {"android":"4","ios":"6"} es7.array.includes {"android":"4","ios":"6"} es7.object.values {"android":"4","ios":"6"} es7.object.entries {"android":"4","ios":"6"} es7.object.get-own-property-descriptors {"android":"4","ios":"6"} es7.string.pad-start {"android":"4","ios":"6"} es7.string.pad-end {"android":"4","ios":"6"} web.timers {"android":"4","ios":"6"} web.immediate {"android":"4","ios":"6"} web.dom.iterable {"android":"4","ios":"6"}Copy the code

If you want to use a method that does not already exist in the plugins field, you can only configure the plugins field. For example, if YOU want to use an includes method, you can only configure the plugins field like this:

"plugins": [
  "array-includes"
]
Copy the code

The preset configuration and demo can be found here: babel-enV-demo

Nodejs side migration upgrade

Following the previous front-end migration and upgrade, it is not difficult to obtain the following new configuration :(assuming our Nodejs version is 6.4)

{" presets ": [[" env" {" targets ": {" node" : "6.4"}, "debug" : true, "useBuiltIns" : true, "include" : [ ] }], "react", "stage-0" ], "plugins": [ "transform-class-properties", "transform-decorators-legacy", "transform-object-rest-spread", "array-includes" ] }Copy the code

If you think this is the end of the migration upgrade, then you are too young too simple. One of the big differences in this configuration is that our target has changed, so we can imagine that the plugins and built-in methods of the corresponding application have also changed. Print the following:

Using targets: {"node": "6.4"} Modules transform: commonjs Using plugins: Transform-es2015-destructuring {"node":"6.4"} transform-es2015-for-of {"node":"6.4"} transform-es2015-function-name {"node":"6.4"} transform-exponentiation-operator {"node":"6.4"} transform-async-to-generator {"node":"6.4"} Syntax - trailing - function - commas {" node ":" 6.4 "} Using polyfills: Array-buffer {"node":"6.4"} es6.typed. Int8-array {"node":"6.4"} es6.typed. Uint8-clamped-array {"node":"6.4"} es6.typed. Int16-array {"node":"6.4"} es6.typed. Int32-array {"node":"6.4"} ES6.typed. Uint32 -array {"node":"6.4"} ES6.typed. Float32-array {"node":"6.4"} Es6. Typed. Float64 - array {" node ":" 6.4 "} es6. Map {" node ":" 6.4 "} es6. Set {" node ":" 6.4 "} es6. Weak - map {" node ":" 6.4 "} Es6. Weak - set {" node ":" 6.4 "} es6. Promise {" node ":" 6.4 "} es6. The symbol {" node ":" 6.4 "} es6. The function. The name {" node ":" 6.4 "} Es6. Array. The from {" node ":" 6.4 "} es7. Object. The values {" node ":" 6.4 "} es7. Object. Entries {" node ":" 6.4 "} Get-own-property-descriptors {"node":"6.4"} es7.string.pad-start {"node":"6.4"} es7.string.pad-end {" node ":" 6.4 "}Copy the code

You can see that the plugin usage is much less than the front-end page, after all, node6! It would have been even less with Node8. These plugins must have been enough for our daily use, but one of my uses in the project led to an error, which left me speechless. Here’s a demo:

// module.js module.exports = { error(message){ this.name = 'Dianwoda'; This. The message = message | | 'system is unusual, please try again later; this.type = 'customeErrorType' this.stack = (new Error()).stack; }} // main.js const mod = require('./module.js') (() => {throw new mod. Error (' here is a custom error ')})Copy the code

Do the smart kids see a problem with this code? Will it report an error? What was the error reported?

5

4

3

2

1

Error is not a constructor! Why is that?

The answer is that in this configuration, Babel doesn’t help you translate ES6’s new syntax: shorthand for class methods. The original code is still there, so what’s wrong with that? This refers to the differences between the three ways functions are defined:

  1. Normal definition
  2. Shorthand definition
  3. Arrow function

See the demo below:

const example = { normal: function() { console.log(this); }, arrow: () => { console.log(this); }, shorthand() { console.log(this); }}; new example.fn(); // fn {} new example.arrow(); // Uncaught TypeError: example.arrow is not a constructor new example.shorthand(); // Uncaught TypeError: example.shorthand is not a constructorCopy the code

There is a sentence in Method Definitions:

Method definitions are not constructable

Make it clear that all functions defined in the third way are not constructible. We can print out the structure of the three in Chrome to see it at a glance:

So it is natural to report this error under the current Babel configuration. So let’s modify the configuration and add the corresponding plug-in:

. "include": [ "transform-es2015-shorthand-properties" ] ...Copy the code

This allows the ES6 Dictate properties to be translated into normal function declarations, and this problem is resolved.

This configuration basically satisfies our use in the project, and the demo of this configuration can be referred to: babel-enV-demo

reference

  1. Method definitions
  2. FunctionCreate
  3. TypeError: “x” is not a constructor
  4. babel-preset-env
  5. babel-preset-es2015
  6. Node ES2015 compatibility