Writing in the front

Babel will be present in daily business architecture, front-end framework design, and even in front-end interviews.

Most front-end developers balk at talking about Babel, and many developers don’t know why but just know what it is.

There are many blogs on the web about how Babel should be configured, and it’s hard to find the best practice for how Babel should be configured in different business scenarios, even though the website only lists some simple instructions.

That’s why I’m writing this article to talk about how I’ve used Babel to find best practice configurations for different front-end projects and get you comfortable with Babel configurations.

Prior to the start

There’s not a lot of Babel basics and plug-in development and rationale.

In this article, I want to talk about how to choose the most appropriate Babel configuration solution to assist your business architecture. If you are interested in learning more about Babel, you can check out these two articles.

  • “Front-end infrastructure” takes you through Babel’s world

This is an introduction to Babel, from the basics to the basics, for those who are interested in a comprehensive overview of Babel configuration.

This article gives you a guide to configuring Babel in your daily front-end infrastructure, and then moves on to simple compilation principles to take you through some simple Babel plug-ins.

  • From Tree Shaking into the world of Babel plugin developers

This article takes you through the implementation of a slightly more complex Babel plug-in, which you can check out if you are interested in learning more about Babel plug-in development.

background

First of all, in most front-end projects we use Babel more as a so-called translator.

Make use of some of the plug-ins provided during the project packaging process, such as babel-loader in WebPack, @rollup/plugin-babel in rollup, and so on.

We use a series of plug-ins to transform our higher version ECMAScrpit into a more compatible lower version syntax through Babel during the packaging process for production use.

Today, this is one of the main things Babel hosts in most front-end projects. Let’s follow this direction and talk about how to find the best configuration for a business scenario.

I will use rollup to demonstrate the configuration in this article, because rollup is cleaner and more intuitive for packing JS files compared to other packaging tools.

If you are interested in learning more about Webpack and Babel you can check out “Front-end Infrastructure” which takes you through Babel’s world. This article uses Webpack more to explain Babel, but any packaging tool is essentially just an aid to understanding Babel.

Babel-preset-env

The so-called preset refers to a preset, and simply the combination of many Babel plug-ins is called Babel-preset.

Babel-preset -env is a preset that contains a set of babel-plugins.

The main thing about this preset is that it allows us to use the latest JavaScript syntax in our projects regardless of target browser compatibility.

Preset -env has a set of built-in plugins, such as @babel/plugin-transform-arrow-functions, @babel/plugin-transform-block-scoping, etc.

Only JavaScript syntax proposals no lower than Stage 3 are included in PRESET -env.

If there is a new JavaScript syntax, preset-env is not built in, and you need to manually include the Plugin in the Babel configuration to support this.

Not configured Babel – preset – env

First, I quickly generated a Rollup configuration project locally. At this point I have not configured any Babel related plug-ins, when I run the package command to package the following files:

const arrowFunction = () = > {
  console.log('Hello My name is 19Qingfeng');
};

arrowFunction();
Copy the code

Package output:

(function () {
  'use strict';

  const arrowFunction = () = > {
    console.log('Hello My name is 19Qingfeng'); }; arrowFunction(); }) ();Copy the code

We can clearly see that the arrow function in the code has not been translated. If a user opens our page with an older browser that does not support the arrow function, the arrow function in the project is bound to fail.

Babel-preset -env is simply used to refer to high level JS syntactic translations as compatible JavaScript code.

Next I’ll add the Babel configuration to rollup, and let’s look at it again:

// rollup.config.js
import { getBabelOutputPlugin } from '@rollup/plugin-babel';
import path from 'path';

export default {
  input: 'src/main.js'.output: {
    file: 'build/bundle.js'.format: 'esm',},plugins: [
    getBabelOutputPlugin({
      configFile: path.resolve(__dirname, './babel.config.js'),})]};// babel.config.js
module.exports = {
  presets: ['@babel/preset-env']};Copy the code

Take a look at the packing result again:

var arrowFunction = function arrowFunction() {
  console.log('Hello My name is 19Qingfeng');
};

arrowFunction();
Copy the code

It can be seen that the packaged arrowFunction is preset-env, and the packaged arrowFunction has been converted to a normal function.

This is what preset-env is for, translating our higher-version JavaScript syntax into one that lower-version browsers can support.

Of course preset-env is also processed based on the targets configuration, simply speaking it determines whether the translation syntax is needed based on the compatible browser you are configuring.

I’m just too tired to mention basic configuration concepts like PRESET -env translation syntax, but for those interested, go to the official website or check out this article “Preset infrastructure” which takes you into the world of Babel.

Polyfill best practices

background

I didn’t want to cover the basics of polyfill in advance in this article, because I’ve already covered some of the basics and usage in detail.

In order to take care of some students who are not good at basic knowledge, I will briefly introduce what polyfill is and why it is needed.

First, let’s clarify these three concepts:

  • The latestESSyntax, such as arrow functions,let/const.
  • The latestES Api, such asPromise
  • The latestESInstance/static method, for exampleString.prototype.include

Babel-prest-env only converts the latest ES syntax, not the corresponding API and instance methods, such as the array. from static method in ES6. Babel doesn’t translate this method, so if we want to recognize and run the array. from method in older browsers as we expect, we need to add polyfill to implement this method on Array.

Polyfill is simply a shim, such as some new ES API and some old browsers do not have corresponding implementation rules.

At this point, the corresponding function cannot be realized simply by using syntactic transformation, and the corresponding function needs to be realized by Polyfill itself.

For example, I used the array.prototype. include method in my code:

/ / the source code
const arr = [1];

const result = arr.includes(2);

console.log(result, 'result');

// The compiled code
var arr = [1];
var result = arr.includes(2);
console.log(result, 'result');
Copy the code

It is clear that the so-called ES syntax such as const lest has been translated successfully, but for array.prototype.includes you can see that it has not been implemented.

If you want to implement these new ES apis or static/instance methods in older browsers, you need to use Polyfill.

preset-env

There are two common ways to implement polyfill in the industry. The first is to use the useBuiltIns parameter of Preset -env, which exists in three configurations.

false

UseBuiltIns defaults to false, meaning that no polyfill translations are performed.

As we demonstrated in the previous Demo, only JavasScript syntax is translated without processing the corresponding API.

In general, if we don’t need babel-preset-env to polyfill our code, we can set false to turn off polyfil for preset-env.

entry

UseBuiltIns The second value is entry, which means that polyfill is introduced at the entry for processing.

Let’s start with an example

Here’s a simple example:

// rollup.config.js
import commonjs from 'rollup-plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import babel from '@rollup/plugin-babel';

export default {
  input: 'src/main.js'.output: {
    file: 'build/bundle.js'.format: 'esm'.strict: false,},plugins: [
    commonjs(),
    resolve(),
    babel({
      babelrc: false.babelHelpers: 'bundled'.presets: [['@babel/preset-env',
          {
            targets: {
              browsers: '> 0, ie >= 0 ',},useBuiltIns: 'entry'.corejs: 3,},],],}),],};// Project entry file
import 'core-js/stable';
import 'regenerator-runtime/runtime';

const arr = [1];

/ / using the Array. Protototype. Includes methods
const result = arr.includes(2);

console.log(result, 'result');
Copy the code

There are a few configuration issues that need to be highlighted:

  • Rollup /plugin-node-resolve Rollup packaging does not package third party dependencies into our source code by default. We need to use this plug-in to merge the output of the source code we wrote with the dependent third-party libraries.

  • rollup-plugin-commonjs rollup. Module references in compiled source code only support ESM module mode by default. However, some third-party NPM modules are written based on CommonJS modules, which results in a large number of NPM modules that cannot be compiled directly. The purpose of this plug-in is to enable rollup to support translation of CJS modules as well.

There are two main points to emphasize about the Babel configuration:

  • First, we useuseBuiltIns: 'entry'Configuration.

It means that we tell Babel that we need to use polyfill, and we use polyfill by introducing polyfill in the entry file.

  • Second, for Corejs: 3, we used the latest version 3.

Corejs version 2 is not currently being maintained, and you can think of corejs as the core implementation of the ES API and built-in modules in Polyfill as described above.

In other words, Corejs internally implements a number of built-in API modules that are not supported by older browsers.

  • Project entry file
import 'core-js/stable';
import 'regenerator-runtime/runtime';
Copy the code

When using useBuiltIns: ‘Entry’, you need to introduce these two packages in the entry file of your project in addition.

Using entry configuration prior to [email protected] required @babel/ Polyfill in the entry. After version 7.4, @babel/ Polyfill was deprecated and replaced with the two packages mentioned above.

Now let’s have a look at the packing result:

Here I just took a screenshot of part of the package result, and you can see that the output package result is actually over 10,000 lines of code.

This is because when using the useage: Entry configuration, Babel is determined based on the targets browser compatibility list configured. In this way, the contents that are not supported by the target browser are fully introduced into the project entrance and mounted on the corresponding global objects respectively to achieve the effect of polyfill.

Just to summarize a little bit

Above we talked about the first way to use polyfills: use useBuiltIns: ‘Entry’ for preset-env.

This approach requires a two-step configuration:

  • First configure useBuiltIns: ‘Entry’ in preset-env.

  • Next, you need to introduce the corresponding package in the entry file of the project.

After that, Babel will fully introduce and implement polyfills that are not supported in the target browser based on the list of browsers we configure to support.

Most developers who know about Babel might think this approach doesn’t work, but it doesn’t.

I will contrast its usage of preset-env for you later to show how it is used.

Of course, you could just import the corresponding polyfill package in the entry file without using useage: Entry, but Babel would lose track of the target browser.

usage

Once you understand what entry means, let’s look at another method of useBuiltIns: usage.

Again, let’s start with an example

There is a fatal problem with the useBuiltIns: Entry configuration:

Using entry, if we only use the array.prototype.includes method in our code, it will implement all incompatible polyfills from the target browser and introduce them into the project, resulting in a package that is too large.

For this scenario, babel-preset -Env useBuiltIns provides an alternative configuration :usage.

Usage means that it will analyze our source code and only introduce the corresponding Polyfil in the module if the target browser does not support it and it is used in our code.

Let’s modify the Demo above to see again:

// rollup.config.js
// ...

export default {
  // ...
  plugins: [
    commonjs(),
    resolve(),
    babel({
      // exclude: ['node_modules/core-js/**', '/core-js/'],
      babelrc: false.babelHelpers: 'bundled'.presets: [['@babel/preset-env',
          {
            targets: {
              browsers: '> 0, ie >= 0 ',},// Change entry to Usage
            useBuiltIns: 'usage'.corejs: 3,},],],}),],};// ./src/main.js
// There is no need to import these two libraries in the entry file
// import 'core-js/stable';
// import 'regenerator-runtime/runtime';

const arr = [1];

const result = arr.includes(2);

console.log(result, 'result');
Copy the code

Re-run the package command to verify:

At this point, you can see that there are only 4K + lines of packaged code left. This is because we only use array.prototype.includes.

When usage is used, only the polyfill content corresponding to array.prototype.includes is introduced into the code.

Usage summary

We can see that Babel will intelligently analyze what is used in the source code with Preset-Env usage parameter.

It simply introduces us to content that is not supported in the target browser and that we are using in our code, and strips out polyfill content that is not used.

For things that are not supported by the target browser, such as promises, but are not used here in our code, it does not package the corresponding Polyfill content into the final result.

Compared to the Entry option, Usage looks smarter and lighter.

Preset-env is a best practice of both modes

Compare the two approaches

Earlier we talked about how to implement babel-polyfill using the useBuiltIns parameter of Preset-Env.

Most articles will tell you that Entry is useless, so just use Usage. But that’s not the case.

First of all, usage is indeed more lightweight and intelligent, but if such a business scenario:

In general, when we use Babel, Babel is compiled to exclude the node_modules directory (a third-party template).

If we use the Usage parameter, if we rely on third-party packages that use some of the newer ES built-in modules, such as promises.

However, we don’t use Promise in our code at this point, so if we use the Usage parameter then we have a problem.

Polyfill doesn’t use promises in the code and the natural Promise polyfill doesn’t compile into the final output directory, and the third module relies on promises but no Polyfill browser knows that at this point.

You might point out that it’s ok if I compile my third-party modules using Babel, or if I introduce Promise’s polyfill separately at the entrance.

First of all, introducing promises in the entry file alone assumes that I know that I use promises in third-party library code and I don’t have promises in my code and I need Polyfil.

Such a situation is only an ideal situation in a large project with many people working together.

Second, USING Babel to compile third party modules is something I strongly recommend against, despite the fact that it is slow to compile and may cause repeated compilation to be too bulky.

In this case, wouldn’t it be nice to configure it with Entry?

Usage is more lightweight and intelligent than Entry, but in business scenarios, if there is no strong requirement on package size, I more recommend that you use entry to introduce polyfills required in the project at the entrance, because this will avoid the strange problem caused by many third-party modules that do not have corresponding polyfills.

Disadvantages of both approaches

At the same time, everything we’ve talked about above is practical for everyday business scenarios.

First, in the compiled code after Polyfil is turned on with the useBuiltIns configuration, there is this code:

This function essentially does what you can easily think of as:

Object.defineProperty(Array.prototype,'includes', {
    value: / /... A function implemented by Polyfill
})
Copy the code

Both Entry and Usage are essentially implemented by injecting polyfills that are not supported by browsers to add functionality to the corresponding global object, which undoubtedly pollutes the global environment.

If you’re developing a library, use useBuiltIns to implement polyfill on the compilation of the library, and when you’re done submitting Github looks like it’s done.

At this point an Indian kid uses your library, but he redefines the array.prototype.includes method implementation in his own code, adding some extra logic.

OK, install the library you developed. Run the code, no accident code error. Because the array.prototype.includes Polyfill implementation in your library is polluting the whole world and affecting the code defined by the Indian guy himself.

At this point, Indian little brother skillfully open Github in your library issue with its broken English condolences to your family….

At this point, we desperately needed a polyfill that didn’t pollute the whole world when developing the class library, and @babel/ Runtime saved my life.

@babel/runtime

In short, @babel/ Runtime provides a way to polyfill without contaminating the global scope, but it is not smart enough to manually introduce the corresponding packages for the relevant polyfills in our code.

At the same time, @babel/ Runtime will implement helper functions such as _extend() and classCallCheck() in each module. It would be a nightmare to implement these methods in each project when there are many modules in the project.

As for the @babel/ Runtime utility function duplication in each module, this article goes into detail about the front-end infrastructure “to take you through the world of Babel.

@babel/ plugin-transform-Runtime is a plugin based on @babel/ Runtime to analyze our code more intelligently. Also, @babel/ plugin-transform-Runtime supports a helper parameter that defaults to true, which will extract some of the utility functions that are repeated during @babel/ Runtime compilation into the way that external modules are introduced.

For more detailed links and demonstrations of @babel/ Runtime and @babel/ plugin-transform-Runtime, check out this article.

Here’s how to use @babel/plugin-transform-runtime:

import commonjs from 'rollup-plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import babel from '@rollup/plugin-babel';

export default {
  input: 'src/main.js'.output: {
    file: 'build/bundle.js'.format: 'esm'.strict: false,},plugins: [
    commonjs(),
    resolve(),
    babel({
      babelrc: false.babelHelpers: 'runtime'.presets: [['@babel/preset-env',
          {
            // The default value is false, but I emphasize not to mix it with others
            useBuiltIns: false,}]].plugins: [['@babel/plugin-transform-runtime',
          {
            absoluteRuntime: false.// The version of Corejs used by Polyfill
            // Note that @babel/runtime-corejs3 and preset-env are different NPM packages
            corejs: 3.// Toggle utility functions like _extend() that cause duplication for @babel/runtime
            // The default is true to indicate that these utility functions are pulled out as toolkits instead of having to be defined separately in each module
            helpers: true.// Toggle whether generator functions pollute globally
            // The package size is slightly larger when true but the generator function does not pollute the global scope
            regenerator: true.version: '7.0.0 - beta. 0,},],],}),],};Copy the code

Here I turn off useBuiltIns for preset-env and just use preset-env translation ES syntax.

For some ES apis and corresponding built-in templates, use @babel/ plugin-transform-Runtime in conjunction with @babel/ Runtime to provide polyfills.

Here I’ve captured some of the packaged code:

Also compared to @babel/ plugin-transform-Runtime with @babel/ Runtime, they will analyze our code intelligently.

Just pull away and preserve the new syntax used in our code to provide polyfill implementation, but @babel/ Runtime has a more notable point relative to Usage that does not pollute the global scope.

$({target:’Array’,proto:true}) $({target:’Array’,proto:true}) Define the corresponding Polyfill implementation directly on Array.prototype.

The @babel/ Runtime package results clearly show that we are called with the introduction of _includesInstanceProperty.

Interested partners can go deep into the implementation of packaging to have a look.

@babel/ Runtime Why not suitable for business projects

With all that said, is the Transform Runtime really as flawless as we thought it was?

@babel/ Runtime in conjunction with @babel/ plugin-transform-Runtime does solve the usage contamination global scope problem, and it seems perfect for developing class libraries.

Some of you might be thinking, since it provides the same intelligence as Usage for on-demand import without polluting the global scope.

So why can’t I just use @babel/ Runtime directly in my business projects? Isn’t that better?

The answer is definitely no, everything has its two sides.

It is also important to note that the Transform Runtime is context-independent and does not dynamically adjust the content of polyfills based on the target browser on our page, whereas useBuiltIns will determine whether polyfills need to be introduced based on the configured target browser.

Explore best practices

First, any configuration project has its own purpose. In my opinion, the best configuration practice does not refer to a fixed configuration, but to finding the most suitable configuration solution based on different service scenarios.

@babel/ Runtime && @babel/preset-env are completely polyfill solutions designed for different scenarios,

business

In daily business development, the problem of global environmental pollution is often not that important. The final host of business code is mostly the browser side, so the introduction of corresponding polyfill for different target browser support will undoubtedly have a great influence on our code volume, and it would be better to choose PRESET -env to start useBuiltIns.

So in brief, I personally highly recommend using @babel/ Preset -env’s useBuiltIns configuration as much as possible in daily business to provide polyfills with supported browser compatibility.

At the same time, I have compared the two methods in detail with you on whether to choose Entry or Usage in useBuiltIns in business projects. How to choose the two configuration schemes? We hope you can choose the best scheme according to different service scenarios. Instead of categorizing entry as useless and mindless usage.

The class library

When we are developing our library, it is often independent of the browser environment, so the main consideration when polyfilling should be not to pollute the global environment. In this case, @babel/ Runtime is more appropriate.

Just turning on @babel/preset-env in conjunction with @babel/ Runtime to provide a global-friendly polyfill might be more suitable for your project scenario.

Tips

In terms of providing polyfills, I strongly recommend that you do not open two polyfills at the same time, which are completely different solutions Babel provides for different scenarios.

It not only causes the problem of double packaging, but also may directly cause exceptions in some environments. For details, you can refer to this Issue.

Of course, you can also polyfill @babel/preset-env in a business project and use the helper parameter @babel/ plugin-transform-Runtime to solve the problem of redundant tooling functions defined repeatedly in multiple modules.

However, be sure to set the Runtime corejs:false option to turn off polyfill provided by The Runtime and keep only one polyfill provider.

Finally, no matter what type of polyfill you have, I highly recommend that you use the corejs@3 version to provide polyfill.

Written in the end

First of all, thank you for reading this little friend, I hope the content of the article can bring you help.

If you’re confused about the content of this article, please leave your questions in the comments section.

If you are interested in learning more about Babel, please check out my column on Engineering Babel, from the beginning to the beginning, and I will continue to share more insights about Babel.