2

Cocos Creator development, this article carefully explores the necessity of paying attention to the JavaScript API , and how to use tools and Polyfill to circumvent the compatibility problem of the Cocos Creator

1. Introduction: The difference of JavaScript

JavaScript virtual machine ( VM ) used by different browsers and mobile devices is very different, and the supported API is also very different.

Let's take a look at the Cocos Creator JavaScript VM at each end:

  • For iOS client and Mac clients: in Cocos Creator 1.6 and earlier, Cocos Creator has been to use non-system native SpiderMonkey as JS VM ; from 1.7 start, Cocos Creator introduced JSB 2.0 , support V8、JavaScriptCore other JS VM . Thus Cocos Creator put iOS end and Mac end JS VM have changed the system comes JavaScriptCore , in order to achieve savings package body; to 2.1.3 , Cocos Creator turn Mac end JS VM switch to V8 , to improve application performance.
  • For Android client and Windows clients: in Cocos Creator 1.6 and earlier, Cocos Creator also use SpiderMonkey as JS VM ; from 1.7 start, thanks to JSB 2.0 , V8 became Android and Windows client JS VM .
  • For Web : Use the browser's JavaScript VM to parse the JavaScript code.

api-compat-jsb2-arch.png

It can be seen from the above that due to the JS VM , the same code may be very different when it runs on different platforms. In order to allow our products to be used by as many users as possible, we need to always pay attention to the compatibility of JavaScript API

For example: The fetch() method is an API XMLHTTPRequest Compared with the latter, its advantage is that it is more readable and can easily use Promise to write more elegant code.

fetch(
    'http://domain/service',
    { method: 'GET' }
)
.then( response => response.json() )
.then( json => console.log(json) )
.catch( error => console.error('error:', error) );

However, fetch() method does not support all of the IE browsers, can not in 2017 years ago Chrome、Firefox and Safari run on version. When a large part of your users are the above-mentioned users, you need to consider prohibiting the use of fetch() API , and return to the embrace of XMLHTTPRequest

In the development stage, it is unreliable to API A more reliable way is to use tools to automate scanning. For example, eslint-plugin-compat described below.

Two, use eslint-plugin-compat

eslint-plugin-compat is ESLint a widget by former uber engineer Amila Welihinda development. It can help find incompatibility API in the code.

api-compat-1.png

The following describes how to access eslint-plugin-compat in the project.

2.1 Installation eslint-plugin-compat

Installing eslint-plugin-compat similar to installing other ESLint plug-ins:

$ npm install eslint-plugin-compat --save-dev

You can also install the browserslist and caniuse-lite

$ npm install browserslist caniuse-lite --save-dev

2.2 Modify ESLint configuration

After that, we need to modify ESLint , plus the use of the plug-in:

// .eslintrc.json
{
  "extends": "eslint:recommended",
  "plugins": [
    "compat"
  ],
  "rules": {
    //...
    "compat/compat": 2
  },
  "env": {
    "browser": true
    // ...
  },
  
  // ...
}

2.3 Configure the target operating environment

Configure the target operating environment by adding the browserslist field in package.json Example:

{
  // ...
  "browserslist": ["chrome 70", "last 1 versions", "not ie <= 8"]
}

The above value means Chrome version 70 or above, or the latest version of each browser, or non- ie 8 and below. The filling format here follows a set of description specifications defined browserslist ( https://github.com/browserslist/browserslist browserslist is a set of tools that describe the target operating environment of the product. It is widely used in various browser/mobile compatibility support tools, such as eslint-plugin-compat , babel、Autoprefixer etc. Let's take a browserslist look at the description specification of 06125c0496a495.

browserslist supports specifying the target browser type, and can flexibly combine a variety of specified conditions.

Specify the target browser type

browserslist contains the following browsers, which can be used in the conditions (note the case sensitivity):

  • Android : used for Android WebView .
  • Baidu : Used in Baidu browser.
  • BlackBerry or bb : for the BlackBerry browser.
  • Chrome : used for Google Chrome .
  • ChromeAndroid or and_chr : for Android Chrome .
  • Edge : for Microsoft Edge .
  • Electron : for Electron framework . Will be converted to Chrome version.
  • Explorer or ie : for Internet Explorer .
  • ExplorerMobile or ie_mob : used for Internet Explorer Mobile .
  • Firefox or ff : for Mozilla Firefox .
  • FirefoxAndroid or and_ff : for Android Firefox .
  • iOS or ios_saf : for iOS Safari .
  • Node : for Node.js .
  • Opera : for Opera .
  • OperaMini or op_mini : for Opera Mini .
  • OperaMobile or op_mob : for Opera Mobile .
  • QQAndroid or and_qq : for the Android QQ browser.
  • Safari : For desktop Safari.
  • Samsung : For Samsung Internet.
  • UCAndroid or and_uc : For Android UC browser.
  • kaios : used in KaiOS browser.

Conditional syntax of browseslist

06125c0496aaaa supports very flexible conditional syntax. Here are some examples for reference (note the case sensitivity) for readers to browserslist

  • > 5% : Means to be compatible with browser versions with global user statistics> 5%. >= , < and <= are also available.
  • > 5% in US : Indicates that it is compatible with browser versions with a statistical proportion of US users> 5%. Here US is the American Alpha-2 code 1 . It can also be changed to Alpha-2 codes of other countries/regions. For example, China is CN .
  • > 5% in alt-AS : Indicates that it is compatible with browser versions that have a percentage of Asian users> 5%. Here, alt-AS represents the Asian region 1 .
  • > 5% in my stats : Indicates to be compatible with a browser version with a custom user statistics ratio> 5%.
  • cover 99.5% : Means to be compatible with the top 99.5% browser version of the cumulative user share.
  • cover 99.5% in US : Same as above, but with Alpha-2 encoding to add country/region restrictions.
  • cover 99.5% in my stats : Use user data.
  • maintained node versions : All official versions Node.js
  • current node Node.js version currently being used by Browserslist.
  • extends browserslist-config-mycompany : said to be compatible browserslist-config-mycompany this npm query results package.
  • ie 6-8 : Indicates to be compatible with IE 6 ~ IE 8 versions (ie IE 6, IE 7 and IE 8).
  • Firefox > 20 : Indicates to be compatible with Firefox version> 20. >= , < and <= are also available.
  • iOS 7 : Indicates to be compatible with iOS 7 .
  • Firefox ESR : Indicates to be compatible with the latest Firefox ESR version.
  • PhantomJS 2.1 and PhantomJS 1.9 : Indicates to be compatible with PhantomJS 2.1 and version 1.9.
  • unreleased versions or unreleased Chrome versions : Indicates to be compatible with the unreleased development version. The latter specifies that it is compatible with the unreleased Chrome version.
  • last 2 major versions or last 2 iOS major versions : Indicates to be compatible with all minor versions included in the last two major versions. The latter specifies that it is compatible with all the minor versions included in the last two major versions of iOS
  • since 2015 or last 2 years : All versions released since 2015 or the last two years to the present.
  • dead : The official browser version is no longer maintained or has not been updated for more than two years.
  • last 2 versions : The latest two versions of each browser.
  • last 2 Chrome versions : The latest two versions of the Chrome
  • defaults : Browserslist default rules ( > 0.5%, last 2 versions, Firefox ESR, not dead ).
  • not ie <= 8 : Exclude browsers lower than or equal to IE 8 from the previous conditions.

When reading these rules, it is recommended to visit http://browsersl.ist enter the same command to test, you can directly get the browser version that meets the conditions.

api-compat-browserslist.png

Test conditions on browserlist

Careful readers may find that the last query result will report an error. This is because the not operation needs to be placed after a query condition (described below). You can pick a rule from other rules and combine it. For example, ie 6-10, not ie <= 8 will filter out IE 9 and IE 10.

Conditional combination of browseslist

browserslist supports the combination of multiple conditions, let's understand the condition combination method of browseslist

  • , and or can be used to represent logical "or". For example, last 1 version or > 1% and last 1 version, > 1% equivalent, and both represent the latest version of each browser, or> 1% market share. The "or" operation is equivalent to the union in set theory.
  • and used to represent logical "and". For example, last 1 version and > 1% represents the most recent version of each browser and has a market share of> 1%. The "and" operation is equivalent to the intersection in set theory.
  • not used to represent logical "not". For example, > .5% and not ie <= 8 means> 1% market share and excludes ie 8 and below versions. The "not" operation is equivalent to the complement set in set theory, so not cannot be used as the first condition, because you always need to know what the "set" is.

The three types of condition combinations can be illustrated by the following table:

Condition combination typeSchematic diagramExample
or / , combination (union)api-compat-union.png> .5% or last 2 versions > .5%, last 2 versions
and combination (intersection)api-compat-intersection.png> .5% and last 2 versions
not combination (supplement)api-compat-complement.png> .5% and not last 2 versions > .5% or not last 2 versions > .5%, not last 2 versions

Configure your browserslist

After understanding the above rules, we can configure browserslist suitable for our project.

For example: if our project wants to run on iOS 8 Chrome desktop browser with version number 49 and above and market share greater than 0.2%, then the following rules can be used:

// ...
"browserslist": [
  ">.2% and chrome >= 49",
  "iOS >= 8"
],

After completion, you can use npx browserslist to test your configured browserslist .

$ npx browserslist
chrome 78
chrome 77
chrome 76
chrome 75
chrome 74
chrome 73
chrome 72
chrome 63
chrome 49
ios_saf 13.0-13.2
ios_saf 12.2-12.4
ios_saf 12.0-12.1
ios_saf 11.3-11.4
ios_saf 11.0-11.2
ios_saf 10.3
ios_saf 10.0-10.2
ios_saf 9.3
ios_saf 9.0-9.2
ios_saf 8.1-8.4
ios_saf 8

You can also visit https://browsersl.ist/ to enter conditional test results.

Test effect

Completed browserslist after configuration rules, we can combine ESLint scanning project API compatibility issues. At the same time, the VS Code plug-in can also prompt the incompatible API call immediately.

api-compat-vscode.png

Three, use eslint-plugin-builtin-compat

The principle of eslint-plugin-compat caniuse-db caniuse ( http://caniuse.com ) and the data of MDN ( https://developer.mozilla.org/ https://developer.mozilla.org/cen-US/ 16125c0496b33b) mdn-browser-compat-data the data in API to confirm the compatibility of 06125c0496b331. However, for uncertain instance objects, because it is difficult to judge the compatibility of the method of the instance, in order to avoid false alarms, eslint-plugin-compat chose to skip the check of API

For example, when foo.includes is not sure whether foo is an array type, it cannot determine the compatibility of the includes In the following figure, when we use the above browserslint configuration, includes method has not been scanned:

api-compat-includes-1.png

However, it can be found caniuse Array.prototype.includes() method is not compatible with iOS 8

api-compat-includes-2.png

In fact, the engine project of Cocos Creator has Array.prototype.includes() method since version 2.1.3, thus completely circumventing the API compatibility problem. When we introduce Polyfill later in this section, we will introduce how to avoid false positives of this API.

In order to avoid false negatives, we can combine another compatibility check plug-in eslint-plugin-builtin-compat . Also by means of the plug mdn-browser-compat-data to scan compatible with eslint-plugin-compat difference is that the plug will not miss the object instance, it will all foo.includes the includes method as is Array.prototype.includes() method to scan. It is conceivable that this plug-in may cause false positives. Therefore, it is recommended to change its alarm level to warning .

3.1 Installation eslint-plugin-builtin-compat

$ npm install eslint-plugin-builtin-compat --save-dev

3.2 Modify ESLint configuration

Similar to eslint-plugin-compat , we can modify ESLint , plus the use of the plug-in. However, because the plug-in is prone to false alarms, it is only recommended to change its alarm level to warning :

// .eslintrc.json
{
  "extends": "eslint:recommended",
  "plugins": [
    "compat",
    "builtin-compat"
  ],
  "rules": {
    //...
    "compat/compat": 2,
    "builtin-compat/no-incompatible-builtins": 1
  },
  "env": {
    "browser": true
    // ...
  },
  
  // ...
}

After joining the plug-in, you can find that the Array.prototype.includes() method will be alerted by the plug-in:

api-compat-includes-3.png

Fourth, use Polyfill solve compatibility problems

ESLint on 06125c0496b5c6 in the development stage to scan out API compatibility problems is certainly a means to prevent compatibility problems, but if colleagues in the team do not pay attention to the scan results of ESLint ESLint as a code part of the scan, There may be fish that slip through the net and continue to wreak havoc.

Thus, once a more common approach is to provide some of the API the respective complement Polyfill . This way, on the one hand, it can add support for incompatible browser versions, and on the other hand, it can make team members feel at ease to use the new API and improve development efficiency.

4.1 Cocos Creator engine in Polyfill

In fact, Cocos Creator of engine project also built a lot of common API of Polyfill :

api-compat-polyfill.png

Which includes Array.prototype.includes() :

api-compat-polyfill-array.png

Therefore, if you use Cocos Creator version 2.1.3 or higher to build a Array.prototype.includes() method, the compiled application will run smoothly on the iOS 8 machine. This is because Array.prototype.includes() was unified and "translated" into the method provided in the engine

Accordingly, in order to avoid Polyfill Lane isArray , find , includes other API is eslint-plugin-builtin-compat false positives, it can be .eslintrc will these API added to the exclusion list widget:

// .eslintrc.json
{
  "extends": "eslint:recommended",
  "plugins": [
    "compat",
    "builtin-compat"
  ],
  // ...
  "settings": {
    "builtin-compat-ignore": ["ArrayBuffer", "find", "log2", "parseFloat", "parseInt", "assign", "values", "trimLeft", "startsWith", "endsWith", "repeat"]
  }
  // ...
}

4.2 Add Polyfill

engine project in the Polyfill not cover all API . If you want to use an incompatible API is not included in the engine project, then you have to consider adding the API of the Polyfill to your own project.

For example, string.prototype.padStart() and string.prototype.padEnd() API respectively provide convenient methods for the head and tail completion of strings:

'x'.padStart(5, 'ab')  // 'ababx'
'x'.padStart(4, 'ab')  // 'abax'
'x'.padEnd(5, 'ab')  // 'xabab'
'x'.padEnd(4, 'ab')  // 'xaba'

These two methods are iOS 10 and above:

api-compat-padstart (1123).png

Looking for Polyfill

How to find Polyfill these two methods? One of the most authoritative sources is the MDN site ( https://developer.mozilla.org/en-US/ ). Taking string.prototype.padStart() padStart in the search box at the upper right corner of the site:

api-compat-padstart-search.png

Then hit enter to enter the search, and click the most matching result in the search results:

api-compat-padstart-search-2.png

You enter the string-prototype-padStart , and you can see the column of Polyfill in the navigation bar on the left:

api-compat-polyfill-padstart (1).png

Click it to jump to the corresponding Polyfill implementation:

!api-compat-polyfill-padstart.png

Write a custom Polyfill script

I found string.prototype.padStart() and string.prototype.padEnd() two API of Polyfill , we write a custom in their own projects Polyfill script. For example, it is called ABCPolyfill.js :

/**
 * ABCPolyfill.js
 * 补一些 polyfill,解决若干兼容问题
 */

var ABCPolyfill = function () {

    console.log('ABC polyfill');

    if (!String.prototype.padStart) {
        String.prototype.padStart = function padStart(targetLength, padString) {
            targetLength = targetLength >> 0; //truncate if number, or convert non-number to 0;
            padString = String(typeof padString !== 'undefined' ? padString : ' ');
            if (this.length >= targetLength) {
                return String(this);
            } else {
                targetLength = targetLength - this.length;
                if (targetLength > padString.length) {
                    padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed
                }
                return padString.slice(0, targetLength) + String(this);
            }
        };
    }

    if (!String.prototype.padEnd) {
        String.prototype.padEnd = function padEnd(targetLength,padString) {
            targetLength = targetLength>>0; //floor if number or convert non-number to 0;
            padString = String((typeof padString !== 'undefined' ? padString : ' '));
            if (this.length > targetLength) {
                return String(this);
            }
            else {
                targetLength = targetLength-this.length;
                if (targetLength > padString.length) {
                    padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed
                }
                return String(this) + padString.slice(0,targetLength);
            }
        };
    }

};

module.exports.ABCPolyfill = ABCPolyfill;

Next, we want to execute the load after the application starts Polyfill script of ABCPolyfill() method automatically marked with two API of Polyfill . We can write another application initialization script, for example called ABCInit.js , which is used to perform some specified tasks when the application is initialized.

/**
 * ABCInit.js
 * 应用启动时的一些初始化工作
 */
import ABCPolyfill from 'ABCPolyfill';

// 初始化操作
function doInit() {
  ABCPolyfill.ABCPolyfill();
}

(function () {
    doInit();
})();

Then you can refer to the script in the script component in the initial scene of your project to take effect:

/**
 * 工程的初始场景挂载的脚本组件
 */

require('ABCInit');

// ...

In order to avoid false positives of eslint-plugin-builtin-compat padStart and padEnd can also be added to the exclusion list:

// .eslintrc.json
{
  "extends": "eslint:recommended",
  "plugins": [
    "compat",
    "builtin-compat"
  ],
  // ...
  "settings": {
    "builtin-compat-ignore": ["ArrayBuffer", "find", "log2", "parseFloat", "parseInt", "assign", "values", "trimLeft", "startsWith", "endsWith", "repeat", "padStart", "padEnd"]
  }
  // ...
}

V. Summary

  1. Always pay attention to API compatibility;
  2. Use eslint-plugin-compat check the static type incompatibility API , and set the alarm level to error;
  3. Use eslint-plugin-builtin-compat check the incompatibility of the dynamic type API , and set the alarm level to warning;
  4. Considered as incompatible API increase Polyfill .

  1. 1

浪遏飞舟
1.9k 声望4.5k 粉丝