This paper mainly records the js API compatibility check and the use of tools and polyfill to repair the compatibility problems in the project a practice.

I. Introduction: Why do we need to check compatibility problems?

I believe that many front-end development partners know the website Caniuse, on which we can query the compatibility of JS API for different versions of different browsers. As shown in the figure below, we have checked Object. Values compatibility through the website.

However, in the development phase, it is not reliable to rely on manual assurance of API compatibility. A more reliable way is to use tools to automate scanning. This way, not only can we see the API compatibility in use at any time to make immediate changes, but we can also ensure that any API with compatibility problems detected will not be committed to the project code.

Two, automatic compatibility problem scanning

Eslint-plugin-compat and eslint-plugin-builtin-compat are two plugins

2.1 use eslint – plugin – compat

Eslint-plugin-compat is a plugin for ESLint, developed by Uber engineer Amila Welihinda. It can help us find incompatible apis in code, plugin github address

Now we’ll look at how to plugin eslint-plugin-compat

2.1.1 eslint – the plugin installation – compat

$npm install eslint-plugin-compat --save-dev 或
$yarn add eslint-plugin-compat -dev
Copy the code

Since the plug-in needs to be used only in the development environment, -dev here installs the plug-in into devDependencies

We can also install the dependent Browserslist with Caniuse-Lite:

$npm install browserslist caniuse-lite --save-dev 或
$yarn add browserslist caniuse-lite -dev
Copy the code

2.1.2 Modifying ESlint Configurations

Next we need to modify the ESLint configuration, plus the use of the plugin:

// .eslintrc.json
{
  "extends": "eslint:recommended",
  "plugins": [
    "compat"
  ],
  "rules": {
    //...
    "compat/compat": "error"
  },
  "env": {
    "browser": true
    // ...
  },
  "settings": {
    "polyfills": [""]
  }
  // ...
}
Copy the code

Polyfills in Settings prevents errors from being reported.

2.1.3 Configuring the target Operating environment

Configure the target runtime environment by adding the Browserslist field to package.json. e.g.

// package.json
{
    // ...
    "browserslist": [ "chrome 80", "safari 9", "ie 9", "ios 8"]
}
Copy the code

The values above indicate that the target is running chrome version 80 or above, Safari version 9 or above, and so on. Of course, the target runtime environment can be specified in some other form here, and the filling format follows the set of description specifications defined by Browserslist.

Browserslist contains Android, Baidu, BlackBerry,Chrome, Edge, Explorer and other existing browsers in the market. You can also use conditional syntax and conditional combinations (or, and, not), to name a few 🌰

- >5% : indicates that the browser version is compatible with more than 5% of global users. >=, <, <= all available - >5% in US: indicates browser versions that are compatible with >5% of US user statistics. Here US is the ALPHA-2 code for the United States, but alpha-2 codes for other countries can also be used. For example China is CN - > 5% in alt-as: In my Stats: in my stats: in my stats: in my stats: in my stats: in my stats: in my stats: in my stats: Represents the top 99.5% compatible browser version. You can also add US, Alt-As, my Stats, etc. - Maintained Node versions: All officially maintained Versions of Node.js. - Current node: Browserslist Specifies the current node.js version in use. - extends Browserslist-config-myCompany: specifies the query result of the NPM package browserslist-config-myCompany. - IE 6-8: Compatible with Ie 6 to IE 8 (IE 6, IE 7, and IE 8). - Firefox > 20: Compatible with Firefox versions > 20. >=, <, and <= are also available. - iOS 7: compatible with iOS 7. - Firefox ESR: Compatible with the latest Firefox ESR version. - PhantomJS 2.1 and PhantomJS 1.9: compatible with PhantomJS 2.1 and 1.9 versions. - Unreleased Versions or unreleased Chrome versions: compatible with unreleased development versions. The latter specifies compatibility with unreleased versions of Chrome. - Last 2 Major versions or last 2 iOS Major versions: compatible with all minor versions of the latest two major versions. The latter specifies compatibility with all minor versions contained in the last two major versions of iOS. - Since 2015 or last 2 years: all releases released since 2015 or the last two years to the present. - Dead: the browser version is not maintained officially or has not been updated for more than two years. - last 2 Versions: indicates the latest two versions of each browser. - Last 2 Chrome versions: indicates the latest two versions of Chrome. - Defaults: Browserslist default rules (> 0.5%, last 2 Versions, Firefox ESR, not Dead). - not IE <= 8: the browser whose value is lower than or equal to IE 8 is excluded.Copy the code

When reading these rules, it is recommended to visit Browsersl. ist and type in the same command to test and get the correct browser version.

Once configured, use NPX Browserslist to test your configured Browserslist.

2.1.4 Test effect

With the Browserslist rule configured, we can scan our projects for API compatibility issues in conjunction with Eslint. Also, VSCode’s ESLint plugin can instantly alert you to incompatible API calls. The following figure

If we add the FETCH API to polyfills in the.eslintrc file, an error will not be generated as follows

// .eslintrc
{
    // ...
    "settings": {
        "polyfills": ["fetch"]
      }
}
Copy the code

2.2 use eslint – plugin – builtin – compat

The principle of eslint-plugin-compat is to verify API compatibility with data from caniuse’s dataset caniuse-DB and MDN’s dataset MDN-browser-compat-data for confirmed types and attributes. For uncertain power objects, however, eslint-plugin-compat chooses to skip this type of API to avoid false positives because it is difficult to determine the methods and compatibility of the instance.

For 🌰,

Const foo = [1,2,3] foo.includes(1)Copy the code

The compatibility of the includes methods is not scanned. To avoid this omission, we can combine another compatibility checking plugin, eslint-plugin-Builtin-compat. The plugin also uses MDN-Browser-compat-data for compatibility scanning, but it doesn’t let instance objects go. So it scans all the includes methods in foo.includes as if they were array.prototype.includes (). As you can imagine, this plugin could be a false positive. Therefore, you are advised to set it to the warning level.

2.2.1 eslint – the plugin installation – builtin – compat

$npm install eslint-plugin-builtin-compat --save-dev 或
$yarn add eslint-plugin-builtin-compat -dev
Copy the code

2.2.2 Modifying ESlint Configurations

Similar to eslint-plugin-compat, we can modify the configuration of ESLint plus the use of the plugin. However, because the plug-in is prone to false positives, you are advised to change its alarm level to Warning:

// .eslintrc.json { "extends": "eslint:recommended", "plugins": [ "compat", "builtin-compat" ], "rules": { //... "compat/compat": "error", "builtin-compat/no-incompatible-builtins": "warn" }, "env": { "browser": true // ... } / /... }Copy the code

The array.prototype.includes () method will be alerted by the plugin:

2.2.3 Configuring ESLint check scripts

In addition to the reminder of VScode plug-in, we can check the compatibility of the project code through the script, as shown below

// packages.json { //... "scripts": { "lint": "eslint .", // ... }}Copy the code

Now we can do ESLint checking via NPM run Lint.

Eslint. Represents the entire file of the project code. We can also specify the check folder. Such as esLint SRC and so on. There are many other configurations supported by ESLint, which can be found on the ESLint website

The eslint command will set the exit code to 1 if there are errors in the project. You can set eslint. If exit 0 exits with code 0, the task will not exit with an error during the Lint phase.

Here’s a question: What if we want to skip the inspection of certain files?

In fact, it is very simple to configure a.eslintignore file, the same configuration method.gitignore. Details can also be found on the ESLint website. In 🌰 below, I skipped lint checking for webpack, build folders.

// .eslintignore
webpack
build
Copy the code

⚠ one thing to note here is that if the files you set to skip in.eslintignore are all files that will be passed by Lint (by default, esLint uses.js as a unique file extension), the following error will be reported.

Note: Also for the React project, remember to use it--ext .jsx,.jsAdd file extension, otherwise check JSX API compatibility oh!!

⚠️ One last important point about esLint configuration, what if we don’t want to use the default.eslintrc file? For example, currently I have a project code that sets a lot of check rules in the.eslintrc file, but I don’t actually use them (and dare not delete 😭), so I want to separate my compatibility check from them, so that I can limit the submission in the submission stage. I had to create a separate file of my own, call it compat-eslint.json, and copy the compatibility Settings from above into that file. And make the following changes when esLint runs the script.

  "scripts": {
    "lint": "eslint --no-eslintrc -c compat-eslint.json . --ext .jsx,.js"
    }
Copy the code

–no-eslintrc indicates that the default. Eslintrc file is not used. If this parameter is not specified, the configuration will be merged. The options in subsequent configuration files take precedence over those in the.eslintrc and package.json files.

-c allows you to specify an additional configuration file for ESLint, followed by the desired configuration file.

One extra point to note: VScode’s rules for identifying incompatible apis are based on.eslintrc, and rules in our own eslint files will not be detected by VScode eslint!

The ESLint website has a lot of powerful rules that you don’t need to memorize, just look them up like a dictionary when you use them. Have a problem, check the documentation!

Iii. Submission restrictions for compatibility issues

Now that we have automated scanning for compatibility issues, how can we force developers to handle compatibility issues before committing? NPM run Lint = NPM run Lint = NPM run Lint = NPM run Lint = NPM run Lint = NPM run Lint = NPM run Lint

The Git hook tool I use is Huksy, which is very simple to use and configure.

3.1 Install Husky first

$npm install husky --save-dev 或
$yarn add husky -dev
Copy the code

3.2 configuration husky

// package.json { //... scripts: { "prepare": "husky install" // ... }}Copy the code

Run NPM prepare once. Husky: hook NPX husky add. husky/pre-commit “NPM run lint” and you will see something like the following (just focus on the.husky folder) :

NPM run Lint will be executed first, and if there is a compatibility problem, it will exit with an error, as shown in the following figure.

We won’t expand too much on Husky here, but this article will focus on js API compatibility issues. In a later section, we will address these compatibility issues to complete our code submission.

Iv. Use Polyfill to solve problems

By the time we reach this section, we have restricted code submissions with compatibility issues. But if we just find these problems and do not solve them, then our inspection is meaningless. Therefore, we need to fill in the corresponding polyfills for these API with compatibility problems. This not only provides support for incompatible browsers, but also allows us to use the appropriate API during development, thus improving our development efficiency.

4.1 Manually Installing patches

In the early days of front-end tools, we had to manually introduce things like ES6’s Object.assign, which still generated errors on IE11. So you need to manually introduce patch code, either by installing third-party packages or by introducing MDN code.

// insert object. assign = require('object-assign') // insert object. assign = require('object-assign')Copy the code

Or place patch code from MDN where needed

value: function assign(target, varArgs) { // .length of function is 2 'use strict'; if (target === null || target === undefined) { throw new TypeError('Cannot convert undefined or null to object'); } var to = Object(target); for (var index = 1; index < arguments.length; index++) { var nextSource = arguments[index]; if (nextSource ! == null && nextSource ! == undefined) { for (var nextKey in nextSource) { // Avoid bugs when hasOwnProperty is shadowed if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { to[nextKey] = nextSource[nextKey]; } } } } return to; }, writable: true, configurable: true }); }Copy the code

4.2 Automatic patching – babel-Polyfill & Polyfill. IO

Babel(babeljs.io/) is a widely used transcoding…

Since this is a polyfill (it needs to be run before source), we need to make it a dependency (live dependency) rather than a devDependency (development dependency).

4.2.1 Using the transformation plug-in: babel-plugin-transform-xxx

  • Method of use

    • Make up for what’s missing herepackage.jsonAdd the required dependenciesbabel-plugin-transform-xxx
    • in.babelrcIn thepluginsItem specified usebabel-plugin-transform-xxxThe plug-in
    • You don’t need to be explicit in your codeimport/require..babelrcDoes not need to be specifieduseBuiltIns.webpack.config.jsNo extra processing is required, and everything is transformed by Babel plug-in
  • advantages

    • The scope is module, avoiding global conflicts
    • It is introduced on demand to avoid unnecessary introduction and code bloat
  • disadvantages

    • Referring to and defining polyfill functions separately within each module creates duplicate definitions and redundancy in the code

After configuring a transformation plug-in, every place in your code that uses the API is converted to code that uses the implementation in Polyfill

4.2.2 Using babel-Runtime and babel-plugin-tranform-Runtime

Compared to method 1, it is equivalent to pulling out the common module, avoiding repeated import, importing the required polyfill from a library called core.js (ES5+ polyfill in ES3)

  • Method of use

    • package.jsonAdd a dependency tobabel-plugin-tranform-runtimeAs well asbabel-runtime
    • .babelrcConfig plug-in:"plugins": ["transform-runtime"]
    • Next, the new ES6+ features can be used directly in your code without the needimport/requireSomething extra,webpackNo additional configuration is required
  • advantages

    • No global pollution
    • Rely on uniform introduction on demand (polyfill is shared by each module), no repeated introduction, no redundant introduction
    • Suitable for writing lib(third-party library) type code
  • disadvantages

    • bepolyfillThe object is temporarily constructed and isimport/requireIs not really mounted globally
    • Since it is not globally valid, methods that instantiate objects such as[].include(x), depends on theArray.prototype.includeStill unavailable

4.2.3 Global Babel-polyfill (without useBuiltIns)

  • Method of use

    • Method 3.1: (browser environment) alone in HTML<head>Tag intobabel-polyfill.js(CDN or local file)
    • Method: 3.2 inpackage.jsonaddbabel-polyfillRely on,webpackAdd entry to configuration file: for exampleentry: ["babel-polyfill",'./src/app.js']The Polyfill will be packaged into the entry file and placed at the beginning of the file
    • Method: 3.3 inpackage.jsonaddbabel-polyfillRely on,webpackEntry file top useimport/requireIntroduced, such asimport 'babel-polyfill'
  • advantages

    • Resolve all compatibility issues once and for all, and be global, browser-specificconsoleYou can also use
  • disadvantages

    • If all polyfills of ES6+ are introduced at once, the js file size will be larger after packaging

    • For modern browsers, some do not need polyfill, resulting in wasted traffic

    • Contaminate the global object

    • Not suitable for framework or library development

4.2.4 Global babel-Polyfill (use babel-env and useBuiltIns)

@babel/preset-env is based on some cool projects: Browserslist, compat-table, electro-to-chromium,……

@babel/preset-env Automatically ADAPTS JS features required by the preset target environment, Babel conversion plugin (excluding stage-0/1/2/3), Corejs’ Poliyfill. Autoadaption can selectively pollute the whole world, and of course can be converted to ES5 indiscriminately, see babeljs. IO /docs/en/bab…

You need to know how to customize @babel/preset-env in Webpack and master @babel/preset-env + useBuiltIns instead of @babel/polyfill.

Three options:

  • Solution 1: install @babel/ Polyfill; in.babelrcIn the configurationuseBuiltIns: 'usage'
  • Solution 2: install @babel/ Polyfill; Introduce it at the app entrance@babel/polyfill; in.babelrcIn the configurationuseBuiltIns: 'entry'
  • Solution 3: Install @babel/ Polyfill; in.babelrcIn the configurationuseBuiltIns: 'false', this is the default behavior; Add @babel/ Polyfill to the webPack entry, as inimport 'babel-polyfill'

Advantages: Polyfills are introduced on demand (in accordance with the requirements of the specified browser environment), somewhat reducing the introduction of unnecessary polyfills, ⚠️ note that do not mix with the previous method, otherwise it will cause conflicts.

I used scheme 1 of this method in this project, and the configuration is as follows:

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

I don’t configure something like “target”: {” ie >= 9″]} in @babel/preset-env because Browserslist is already set in package.json during previous compatibility checks.

4.2.4 Online patch tool: Polyfill. IO

Dynamic patching still has one of the biggest drawbacks: patch redundancy. In the case of Object.assign, there is no need to introduce this patch on browsers that support this feature, thus creating a patch redundancy. The community has come up with a solution for dynamic patching based on browser features.

Polyfill. IO is one such service that returns a different patch code depending on the browser user-Agent. For example, if you want to load the Promise patch code, you can introduce it directly:

<script src="https://polyfill.io/v3/polyfill.js features=Promise"></script>

We can see the return of polyfill. IO by modifying user-Agent in Chrome.

In addition, Polyfill. IO also open source Polyfill – Service for our own construction and use.

This method can load the least amount of patches. If the loaded resources have extreme performance requirements, we can consider the self-built Polyfill service based on polyfill. IO to dynamically inject Polyfill. However, for my own project, the necessity is not high, and if I do not deploy a COPY to my own CDN, it always feels unreliable to rely on external services.

The above

Reference article:

  • Oedx. Making. IO / 2019/12/24 /…
  • Juejin. Cn/post / 684490…
  • Github.com/sorrycc/blo…
  • www.cnblogs.com/Jeely/p/112…
  • zhuanlan.zhihu.com/p/27777995
  • Segmentfault.com/a/119000003…
  • zhuanlan.zhihu.com/p/29058936