introduction
For front-end developers, the caniuse website is a very useful tool, which can help us query the compatibility of JavaScript APIs in different browser versions. Taking fetch as an example, we can check its compatibility on the website as shown in the figure below:
js复制代码fetch('http://domain/service', { method: 'GET' })
.then(response => response.json())
.then(json => console.log(json))
.catch(error => console.error('error:', error));
However, manually ensuring API compatibility is unreliable. Below I will share a real case to share the topic of this article, automated compatibility checking and solutions.
online accident
An online accident happened recently, which I deeply regret. The thing is like this, Xiaofei is a front-end fresher who just graduated, and July was supposed to be the day when he became a full-time employee. However, not long ago, due to a small problem of his, a white screen appeared on the H5 page of the online APP, causing some business losses. Xiaofei is mainly responsible for the loan business. This type of business involves C-end users and involves money, so the seriousness of the problem is self-evident. In the end, this accident caused his application for regularization to fail, which was a pity.
Cause of accident
Later, Xiaofei asked me how to avoid this white screen problem. He mentioned that during the regression testing phase, the test classmates told him that there was no problem, but online users reported the white screen problem. My first reaction was to ask him if there was any problem caused by JS error reporting. In fact, this is indeed the root of the problem. Because the low-end model does not support a certain API, the page reports an error, resulting in a white screen problem.
So, is there any way to scan for this kind of problem during the CICD process or during code development? Is there any other way besides monitoring the system? Actually there is. I wrote an article before where I added a performance guard plug-in to the project. My colleagues told me not to sleep too hard at night and mentioned how to use the eslint-plugin-compat plug-in to implement this mechanism to avoid similar online production accidents. occur. Using this plug-in, we can discover possible compatibility issues during the code development stage, allowing developers to fix them in time to avoid bringing problems into the online environment.
Automated compatibility checks
Use the eslint-plugin-compat plugin
eslint-plugin-compat is a powerful tool that can help us check the compatibility of features used in code in different browsers. Here are the steps for automated compatibility checking using eslint-plugin-compat:
Install plugin:
bash复制代码npm install eslint-plugin-compat --save-dev
Configure ESLint:
Add the plugin in the project's .eslintrc.js configuration file:
javascript复制代码module.exports = {
// ...
plugins: [
// ...
'compat'
],
// ...
};
Set browserslist:
By using browserslist configuration, you can ensure that your project can run normally in the target browsers, and automatically introduce the corresponding polyfill or compatibility warning during the development phase, thereby saving debugging time, improving development efficiency, and building cross-browser Friendly web application. Whether you're using the latest features in modern browsers or providing compatibility support in older browsers, browserslist helps you easily manage and configure your project's compatibility needs.
Example .browserslistrc configuration is as follows:
js复制代码# Browsers that have more than 5% global usage
> 5%
# Last 2 versions of major browsers (including Chrome, Firefox, Safari, and Edge)
last 2 major versions
# Specific browser versions
IE 11
iOS >= 11
not dead
# Browsers used in specific countries
and_chr 78
and_ff 68
# Browsers used in specific environments
maintained node versions
not IE 11
# Development and production environment specific targets
development
last 1 chrome version
last 1 firefox version
production
> 0.2%
Configuration options |
describe |
> 5% |
Browsers used by more than 5% of the world |
last 2 major versions |
The last two major versions of browsers |
IE 11 |
Includes Internet Explorer 11 only |
iOS >= 11 |
Browsers with iOS system version 11 or above |
not dead |
Exclude browsers that have been deemed no longer in use |
and_chr 78 |
Specific version of Chrome browser |
and_ff 68 |
Specific version of Firefox browser |
maintained node versions |
The currently maintained version of Node.js |
not IE 11 |
Exclude Internet Explorer 11 |
development |
Development environment configuration, specifying compatibility requirements |
production |
Production environment configuration, specifying compatibility requirements |
last 1 chrome version |
The latest version of the Chrome browser |
last 1 firefox version |
A recent version of the Firefox browser |
> 0.2% |
Browsers with a global usage rate of more than 0.2% |
Test results:
Suppose we have the following code example main.js:
javascript复制代码// main.js
const button = document.getElementById('my-button');
button.addEventListener('click', () => {
console.log('Button clicked!');
});
Run the eslint command to check code compatibility:
bash复制代码eslint main.js
If the browser version supports addEventListener, no error will be reported. Otherwise, the corresponding compatibility warning will be prompted.
Use the eslint-plugin-builtin-compat plugin
eslint-plugin-builtin-compat is another plugin for checking browser compatibility that provides some extra features.
Install plugin:
bash复制代码npm install eslint-plugin-builtin-compat --save-dev
Configure ESLint:
Add the plugin in the project's .eslintrc.js configuration file:
javascript复制代码module.exports = {
// ...
plugins: [
// ...
'builtin-compat'
],
// ...
};
Configuration check script:
Add the check script in package.json:
json复制代码{
"scripts": {
// ...
"compat-check": "eslint --no-eslintrc --ext .js main.js"
}
}
Run the check script:
bash复制代码npm run compat-check
git commit verification
In order to ensure that the code has passed the compatibility check before submission, we can use the husky tool to perform verification before commit.
Install husky:
bash复制代码npm install husky --save-dev
Configure husky:
Add a pre-commit hook in package.json to perform compatibility checks before committing:
json复制代码{
"husky": {
"hooks": {
"pre-commit": "npm run compat-check"
}
}
}
CICD check
In addition to verifying in git, we can also verify in the cicd process. Once it fails, the MR will not be passed.
js复制代码{
"scripts": {
"lint": "eslint . --ext .js"
}
}
js复制代码stage('Linting') {
steps {
sh 'npm run lint'
}
}
3. Use polyfill to solve compatibility issues
In some cases, even after compatibility checks, there may still be new features that are not supported by the browser. At this time, we can use polyfills to provide missing functionality for these browsers.
Manual polyfill
Install third-party libraries:
Install the required polyfill library in the project, such as core-js or polyfill.io.
bash复制代码npm install core-js --save
Write compatibility code:
Where compatibility support is needed, introduce the corresponding polyfill library and write the corresponding code.
javascript复制代码// main.js
import 'core-js/features/promise';
const button = document.getElementById('my-button');
button.addEventListener('click', () => {
// 使用新特性Promise
new Promise((resolve) => {
setTimeout(() => {
resolve('Button clicked!');
}, 1000);
}).then((message) => {
console.log(message);
});
});
Automatic polyfill
Use Babel to automatically convert code according to the target browser version, and use babel-runtime to extract common modules.
Install related libraries:
bash复制代码npm install @babel/preset-env @babel/plugin-transform-runtime --save-dev
Configure Babel:
Configure Babel in the project's .babelrc file:
json复制代码{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "usage",
"corejs": 3
}]
],
"plugins": ["@babel/plugin-transform-runtime"]
}
Introduce polyfill as needed:
Based on the new features actually used, Babel will automatically introduce the necessary polyfills according to the target browser version.
javascript复制代码// main.js
const button = document.getElementById('my-button');
button.addEventListener('click', () => {
// 使用新特性Promise
new Promise((resolve) => {
setTimeout(() => {
resolve('Button clicked!');
}, 1000);
}).then((message) => {
console.log(message);
});
});
Dynamic polyfill service:
Services such as polyfill.io can be used to dynamically provide polyfills for unsupported browsers to reduce the amount of data loaded by the page.
html复制代码<!DOCTYPE html>
<html>
<head>
<title>Polyfill Example</title>
</head>
<body>
<button id="my-button">Click Me</button>
<!-- 加载polyfill.io服务 -->
<script src="https://polyfill.io/v3/polyfill.min.js"></script>
<script src="main.js"></script>
</body>
</html>
Let’s talk about Babel again
Babel is a JavaScript compiler. Why do you say that? Because babel will not process Web API. I also thought that babel would automatically help us with polyfill. As a result, we ignored Web API. Babel will only process the features in the ECMAScript standard.
Babel is a tool chain mainly used to convert code written in ECMAScript 2015+ syntax into backward-compatible JavaScript syntax so that it can run in current and older versions of browsers or other environments.
- Grammar conversion
- Add missing features in the target environment through Polyfill (by introducing third-party polyfill modules, such as core-js)
- Source code conversion (codemods)
Problems encountered
In the chrome61 environment, an error is reported that ResizeObserver is not a function.
It was confirmed that the ResizeObserver feature supports at least chrome64, so the target version compiled by babel was set to chrome 61. However, the error was still not resolved after changing the error. After some searches, the reasons are as follows:
Babel only polyfills ECMAScript features.
What is ECMAScript? It is the core of the JavaScript that you can use in the browser. Browsers implements ECMAScript, and on top of it there are lots of expansions (Dom objects, service workers, Ajax, ...). ResizeObserver is one of those expansions.
How can you know if a feature is part of ECMAScript or not?
I'd suggest searching for that function on MDN, and look at the Specifications section (ArrayBuffer example).