Github.com/willson-wan… Deep understanding of Babel ecology

polyfill

Polyfill means something in the bottom of your pocket. The reason for polyfill is because ECMASCRIPT releases new apis all the time, and when we use these new apis, they don’t work on older browsers because they don’t work on older browsers, so in order to make the code run on older browsers, So you add the API manually, and that’s polyfill;

Core-js2. X is commonly used to introduce polyfill

require('core-js'); / / global polyfill
var core = require('core-js/library'); // Polyfill without global pollution
require('core-js/shim'); / / only Shim

require('core-js/fn/array/find-index'); // Single API polyfill
var findIndex = require('core-js/library/fn/array/find-index'); // Single API polyfill without global contamination
Copy the code

The difference between global polyfills and individual API polyfills:

Global is a polyfill that introduces all apis; A single polyfill just introduces a single API;

Therefore, if you know which API is used in the project, you can just introduce polyfill of the corresponding API, which can reduce the volume of the package.

Global pollution

The difference between global polyfills and polyfills without global contamination: Have pollution refers to the directly on the window to add a static method or attribute, such as Array adding a static method or constructor prototype method, such as findIndex method, will be directly added to the Array. The prototype on the prototype of this direct pollution of the Array. The prototype;

Pollution-free refers to: put the core global variable or directly export the API;

browserslist

Browserslist is used to share the target browser and Node.js version configuration between different front-end tools (e.g. Autoprefixer, Babel);

Tell the front-end tool, the current project running the target browser and node version, so that the tool can add the corresponding CSS prefix according to the target browser and Node version and whether the syntax needs to transform;

How browserslist is configured:

  1. Add the Browserslist field inside package.json
 "browserslist": [
    "last 1 version"."1%" >."maintained node versions"."not dead"
  ]
Copy the code
  1. A separate.browserslistrc configuration file
    last 1 version
    > 1%
    maintained node versions
    not dead
Copy the code
  1. The corresponding properties in each tool, such as the Targets property in Babel
{
    "presets": [["@babel/preset-env",
            {
                "targets": {
                    "node": "4"."browsers": "edge >= 13"}}]]}Copy the code

Github.com/willson-wan…

preset runtime

@babel/preset-env and @babel/ transform-Runtime now support core-js@3, and @babel/template has a new syntax!

UseBuiltIns conflicts with @babel/ plugin-transform-Runtime?

The two are in conflict in introducing polyfills and are not recommended together. To introduce polyfills, the former leads to polyfills globally and the latter only works within the imported file.

The transform-Runtime is context-independent and does not dynamically adjust the content of polyfill for your target environment; And useBuildInts.

Usage scenarios: the former is used for projects; The latter is used to develop third-party libraries.

Improvements: useBuiltIns can also locally introduce polyfills like transform-Runtime, but can also use targets of preset-env’s to configure the target environment.

Here is the solution to introduce polyfill:

Scheme 1 preset-env + useBuildIns configuration: Implement polyfill

{
  "presets": [["@babel/preset-env",
      {
        "useBuiltIns": "usage"."corejs": 3}}]]Copy the code

The above configuration can transform both high-level syntax and introduce polyfill to transform apis on demand.

The downside: repetitive helper redundant code is generated, and polyfills introduced can break the global environment.

Solution 2 Transform-Runtime + Runtime-corejs3: Implements polyfill

{
  "presets": [["@babel/preset-env",
      {
        "useBuiltIns": false}]],"plugins": [["@babel/plugin-transform-runtime",
      {
        "corejs": 3}}]]Copy the code

Disadvantages: The targets or Browserslist configuration for polyfill fails. Because the Transform-Runtime environment is independent, the content of Polyfill will not be dynamically adjusted based on the target browser of your page; And useBuildInts.

The test code

1. Processing related to helper functions

First of all, what is helpers?

It’s a helper function that’s called Babel Transform, that’s called syntax degradation, that’s called babel-helpers. If babe builds and detects a file that needs these helpers, it puts them at the top of the module when it’s being compiled.

(Note: Helpers are not related to polyfill.)

Babel :(turns polyfill of preset-env off, transform-runtime is not enabled)

{
  "presets": [["@babel/preset-env",
      {
        "useBuiltIns": false}}]]Copy the code

Then prepare two ES files, run Babel, transcode the files, and the final result is:

The above transcoding results: _classCallCheck, _defineProperties, etc. belong to the internal helper functions of Babel. These functions are repeated in every file after Babel transcodes.

2. Use transform-Runtime to introduce modules in Runtime to optimize helper functions

Modify the Babel configuration as follows:

{
  "presets": [["@babel/preset-env",
      {
        "useBuiltIns": false}]],"plugins": [
    "@babel/plugin-transform-runtime"]}Copy the code

Rerunning Babel transcoding results in:

As you can clearly see from this result, transform-Runtime converts the helper functions to references to modules in @babel/ Runtime, and @babel/ Runtime will be compiled into the output file.

You can also see why @babel/ Runtime is installed under Dependencies.

Conclusion: The transform-Runtime plugin can convert these repeated helper functions into a common, separate dependency to save transcoding file size, which is the main reason for the @babel/ plugin-transform-Runtime plugin.

3,@babel/preset-env + Cojs3 + useBuiltIns implement polyfill

Modify the Babel configuration as follows:

{
  "presets": [["@babel/preset-env",
      {
        "useBuiltIns": "usage"."corejs": 3}}]]Copy the code

Rerunning Babel transcoding results in:

The Babel configuration above enables the introduction of polyfills on demand and precise conversion for the target environment. So, what are the problems? There are two problems

The first problem is that the helper functions mentioned above have repetitive definitions and code redundancy.

Includes includes includes includes includes includes includes includes includes includes includes includes includes includes

require("core-js/modules/es.array.includes.js");
Copy the code

Not in a more intuitive way:

var includes = require('xxx/includes')
Copy the code

This mechanism is added directly to global.array. prototype for instance methods such as includes. This direct modification of the global variable prototype can lead to unexpected problems.

This issue is especially important when developing third-party libraries that modify global variables and may conflict with another third-party library that also modified global variables, or with users of our third-party library. Generally accepted programming paradigms also discourage direct modification of global variables and global variable prototypes.

4,@babel/plugin-transform-runtime + @babel/runtime-corejs3 polyfill

Babel.config. json is set to:

{
  "presets": [["@babel/preset-env",
      {
        "useBuiltIns": false}]],"plugins": [["@babel/plugin-transform-runtime",
      {
        "corejs": 3}}]]Copy the code

The conversion result is:

As can be seen:

  • Helpers @babel/runtime-corejs3/helpers/ XXX
  • The polyfills required by the conversion API are in @babel/runtime-corejs3/core-js-stable/ XXX.
  • Avoid contamination of global variables and their prototypes;
  • Helpers have been changed from being defined in place to being included in a unified module, so that only one helper exists for each packaged result.

5. Enable polyfill for preset-env and Transform-Runtime simultaneously?

The configuration is as follows:

{
  "presets": [["@babel/preset-env",
      {
        "useBuiltIns": "usage"."corejs": 3}]],"plugins": [["@babel/plugin-transform-runtime",
      {
        "corejs": 3}}]]Copy the code

Conversion result:

Polyfill of preset-env and polyfill of transform-Runtime coexist. However, the introduction of helper functions is only valid for transform-Runtime. Such transcoding results are definitely problematic. These two are different polyfill methods and have different application scenarios. Therefore, both polyfills cannot be enabled at the same time.

6, develop applications, best configuration??

Env-usebuiltins-usage, corejs3, and @babel/ plugin-transform-Runtime, corejs-false are recommended for general business projects, because we do not need to consider the impact of global pollution, because these are controllable. The Babel configuration is as follows:

{
  "presets": [["@babel/preset-env",
      {
        "useBuiltIns": "usage"."corejs": 3}]],"plugins": [["@babel/plugin-transform-runtime",
      {
        "corejs": false."regenerator": false}}]]Copy the code

Helpers @babel/plugin-transform-runtime Polyfills is introduced with “useBuiltIns”: “usage”.

The installation

npm install –save core-js@3

yarn add core-js@3

yarn add @babel/runtime

yarn add @babel/runtime-corejs3

Or:

npm install –save-dev @babel/core @babel/cli

Other:

@babel/preset-flow, @babel/preset-react, and @babel/preset-typescript

Note that the Corejs and Runtime packages are installed in Dependencies.

Part of the package. The json

{
  "browserslist": [
        "last 2 version"."1%" >."IE 10"]."devDependencies": {
        "@babel/cli": "^ 7.14.3"."@babel/core": "^ 7.14.3"."@babel/plugin-syntax-dynamic-import": "^ 7.8.3"."@babel/preset-env": "^ 7.14.2"."@babel/preset-react": "^ 7.13.13"."babel-loader": 4 "" ^ 8.0.0 - beta.."clean-webpack-plugin": "^ 0.1.19"."copy-webpack-plugin": "^ 4.6.0"."css-loader": "^ 1.0.0"."cssnano": "^ 4.1.4." "."file-loader": "^ 1.1.11"."html-webpack-plugin": "^ 3.2.0"."optimize-css-assets-webpack-plugin": "^ 5.0.1." "."sass-loader": "^ 7.0.3." "."style-loader": "^ 0.21.0"."webpack": "^ 4.43.0"."webpack-bundle-analyzer": "^ 4.4.0"."webpack-cli": "^ 3.3.11." "."webpack-dev-server": "^ 3.11.2"."webpack-merge": "^ 5.8.0"
  },
   "dependencies": {
    "@ant-design/icons": "^ 4.6.0"."@babel/runtime": "^ 7.14.0"."@babel/runtime-corejs3": "^ 7.14.0"."antd": "^ 4.16.0"."axios": "^ 0.21.1"."classnames": "^ 2.3.1." "."core-js": "3"."dayjs": "^ 1.8.30"."history": "^ 5.0.0"."lodash": "^ 4.17.20"."mobx": "^ 6.1.4." "."mobx-react": "^ 7.1.0"."react": "^ 17.0.1"."react-dom": "^ 17.0.1"."react-router": "^ 5.2.0." "."react-router-dom": "^ 5.2.0." "}},Copy the code

The configuration file

After V7.7.0, the recommended configuration file name is babel.config.json or.babelrc.json:

 {
  "presets": [["@babel/preset-env",
      {
       "targets": {
            ios: 8,
            android: 4
        },
        "useBuiltIns": "usage"."debug": true."corejs": 3,}]],"plugins": [["@babel/plugin-transform-runtime",
      {
        "corejs": false."regenerator": false}}]]Copy the code

Babel api

Common parameters and methods include path and Scope

A plug-in (called a “concrete visitor” in the design pattern) simply defines the node type it is interested in and calls the plug-in’s visit method when the visitor visits the corresponding node. Babel passes a path parameter to our visitor method. What does this path parameter do?

path

  • The path parameter is used to associate nodes. It is an object that represents the connection between two nodes.
  • Consider the AST as a tree, and the relationship between nodes in the tree is represented by path. The result is a large mutable object that can be manipulated and accessed to represent the relationship between nodes.

Common Path operations are as follows:

1. New node:

insertBefore(nodes: [Object]) // Insert a new node before the current node
 insertAfter() // Insert a new node after the current node
Copy the code

2. Delete nodes:

BooleanLiteral(path) {
  path.remove(); // remove Deletes the current node
}
Copy the code

3. Replace the node

 // replaceWith(replacement: Object)
BooleanLiteral(path) {
  path.replaceWith(t.identifier("bar"));
}

// Replace replaceWithMultiple(Nodes: [Object])
BooleanLiteral(path) {
  const nodes = [
    t.identifier("foo"),
    t.identifier("bar")]; path.replaceWithMultiple(nodes); }Copy the code

scope

When we operate on the AST, we need to consider Scope; In order for us to better operate AST, Babel provides Scope, and each Scope contains the following information

{
  path: path,
  block: path.node, 
  parentBlock: path.parent,  / / the parent node
  parent: parentScope, // Parent scope
  bindings: [...].// All bindings for the current scope
}
Copy the code

Scope objects are similar to Path objects in that they contain associations between scopes (via parent to parent scopes), collect all bindings under scopes, and provide rich methods for scope-only operations.

Common Scope operation methods

  1. GenerateUidIdentifier (name: string = “temp”) generates a UNIQ ID and returns an identifier
  2. GenerateUid (name: string = “temp”) generates a UNIQ ID and returns a string
  3. rename(oldName: string, newName? : string, block? : Object renames the name of a variable in the current scope
  4. GetBinding (name: string) Gets the binding for a name
  5. GetBindingIdentifier (Name: string) Gets the identifier of the binding relationship corresponding to a name
  6. GetOwnBindingIdentifier (Name: string) Gets the identifier of the binding relationship corresponding to the scope of name
  7. HasOwnBinding (name: string) Determines whether name is defined in the current scope
  8. parentHasBinding(name: string, noGlobals? : Boolean) Judge name to negate the scope before the parent

How to write a Babel plug-in

Let’s write a plugin together

Compile on demand for the incoming Library

module.exports = function ({ types: t }) {
    return {
        visitor: {
            ImportDeclaration(path, {opts}) {
                if(! opts.library)return
                let librarys = []
                if (Array.isArray(opts.library)) {
                    librarys = opts.library
                } else {
                    librarys = opts.library.split(', ')}The get method gets the path to an attribute, or if it is an array, it is an array
                const specifiers = path.get('specifiers');
                if(! specifiers.length)return;
                const source = path.get('source');
                const library = source.node.value
                if (librarys.indexOf(library) > -1) {
                    const nodes = specifiers.map((it) = > {
                        const localNode = it.get("local").node
                        const importedNode = it.get("imported").node
                        return t.ImportDeclaration(
                            [t.importDefaultSpecifier(localNode)], 
                            t.StringLiteral(`${library}/${importedNode.name}`)
                        )
                    })
                    path.replaceWithMultiple(nodes)
                }
            }
        }
    }
}
Copy the code

Refer to the link

What is @babel/plugin-transform-runtime? : zhuanlan.zhihu.com/p/147083132

Blog.liuyunzhuge.com/2019/09/04/… Explain the clouds

Blog. Windstone. Cc/es6 / Babel / @… The blog of Moving Stone

Segmentfault.com/a/119000002…