-
Notifications
You must be signed in to change notification settings - Fork 2
Description
前端浏览器兼容性处理
引言
js 和 css在日益发展,各浏览器厂商也在迭代更新以支持开发使用新的语法和方法,但依然存在使用一些低版本浏览器的用户,导致项目在其浏览器环境无法运行。
在开发过程中无法保证兼容所有版本的浏览器,也不考虑太低版本的用户,但如何最大程度的保证代码能够运行在所需求的目标浏览器?
定义支持的浏览器及版本
在开发的过程中,需要对 项目各依赖进行分析 及 收集用户来决策需要支持什么浏览器及其版本。
browserslist
browserslist 用于配制目标浏览器并被许多工具使用,例如:
- Autoprefixer
- Babel
- Postcss-perset-env
它被使用于各 js,css 工具, 能够根据具体规则解析出需要运行的浏览器环境。
在 package.json 或 .browserslistrc 定义相应的语法后
// package.json
"browserslist": [
"last 2 version",
"> 1%"
"not dead",
"chrome > 72"
]
//.browserslistrc
last 2 version or > 1% or not dead or chrome > 72
browserslist 对配置进行分析并列出浏览器列表,具体的语法规则可以查看 https://github.com/browserslist/browserslist#queries
browserslistrc 的数据都存在 caniuse-lite 库中,所以有时候在用 browserslistrc 的时候会命令行会提示更新 caniuse-lite 库,这里面包括 浏览器信息,Can I use 数据。
Js 预处理器
babel
babel 是一个 JavaScript 编译器, 让你在项目里能够使用 高版本浏览器已实现的新特性 或 一些 js 草案,需要进行编译转化的部分分为 语法 和 Api。
1. @babel/preset-env
@babel/preset-env 是一个预设工具,它能让你使用最新的 JavaScript 语法,并无需操心对目标环境所支持的语法 并设置相应的语法转换插件。
当配置了 @babel/preset-env 和 browserslist,babel 会通过 browserslist 结果 检测代码语法 和 Api 是否兼容到目标浏览器 并进行转化。
比如使用不支持72版本以上的 语法 和 api 时:
babel.config.js
[
'@babel/preset-env',
{
// 注入垫片
corejs: { version: 3, proposals: true },
// useBuiltIns: 'entry' | 'usage' 。
// 当为entry时,会引用所有的垫片库。
useBuiltIns: 'usage'
}
]
.browserslistrc
last 2 version and > 1% and not dead or chrome > 72
index.js
class Shape {
#color;
constructor() {
this.#color = 'red';
}
}
console.log(Promise.allSettled)
浏览器版本在72时,并不支持 Class private value
和 Promise.allSettled
, 但是 babel 转化了语法 和 引入了 垫片库,使其能够在低版本浏览器也能跑起来。
缺点: @babel/preset-env 在转化语法的时候重复转化的过程会导致包不必要的变大,在引入垫片库的时候会直接修改 基础数据类型的原型方法 或 windows 导致污染了全局,可能会影响其他共存的项目。
2. @babel/plugin-transform-runtime
@babel/plugin-transform-runtime是 babel 的一个插件,它主要解决两个问题。
一是用于避免重复编译输出的问题, 能够生成更小的包
.babelrc-without-runtime-plugin.js
// 没有配置 babelrc-without-runtime-plugin
module.exports = {
presets: ['@babel/preset-env']
}
.babelrc-with-runtime-plugin.js
// 配置了 babelrc-without-runtime-plugin
module.exports = {
presets: ['@babel/preset-env'],
plugins: [
'@babel/plugin-transform-runtime',
],
}
input.js
class Shape {
#color;
constructor() {
this.#color = 1;
}
}
async function stuff() {
const greeting = await Promise.resolve('hi')
return greeting
}
当使用 .babelrc-without-runtime-plugin 配置时,输出的文件会产生一些函数帮助我们兼容语法,产出的文件大小在12kb。
使用 @babel/plugin-transform-runtime 后,实现函数变成了引用@babel/runtime的hleper函数来完成这些工作。产出的文件大小在4kb,缩小了3倍。
@babel/plugin-transform-runtime 的第二个作用是让 polyfill 沙箱化。
当用一些 Promise , Set ,Map 的一些内置函数,引入的 polyfill 代码会直接挂载在方法的 Prototype 上,导致污染全局API,如果不是独立的应用时,就可能会影响到其他应用。
加上 @babel/plugin-transform-runtime 后会使生成的代码 沙箱化 ,就解决了污染全局 API 的问题。
@babel/preset-env 和 @babel/plugin-transform-runtime 的最佳实践方案
该 issues 有个回答对 @babel/preset-env 和 @babel/plugin-transform-runtime 机制进行了解释,并提出了配置他们的最佳方案。
如果是独立应用时,将 @babel/preset-env 的 useBuiltIns 设置为 entry,@babel/transform-runtime 只作与 helper 函数的导入。
如果是第三方库,将 @babel/preset-env 的 useBuiltIns 设置为 false, @babel/transform-runtime 配置 corejs的选项。
注意: @babel/preset-env 的 useBuiltIns 不能 和 @babel/transform-runtime 的corejs 同时开启。
css 预处理器
PostCSS
PostCSS是一个通过 JS 转换样式的工具,它有很多的插件来提供能力,Css 检查,支持 variables 和 mixins , 也能够转译新特性的 CSS 语法。
postcss-preset-env
postcss-preset-env 是一个 PostCSS 插件, 和 @babel/preset-env 所需要做的事情一样, 能根据目标浏览器或运行时环境确定所需 polyfill。
该 文档 列出了postcss-preset-env 所支持的新特性, 里面也包含了最常用的 autoprefix。
配置 postcss-preset-env 插件
可以在 postcss.config.js 或 postcss-loader 增加 postcss-preset-env
// postcss.config.js
module.exports = {
plugins: [
[
'postcss-preset-env',
{
// plugin options
},
],
// maybe a minifier?
],
};
增加插件后,需要 PostCSS 处理器处理的的文件都会用到 postcss-preset-env 插件。
比如,使用:is() CSS伪类函数时(该特性实现于 chrome88 及以上的浏览器):
body {
& :is(div, span, section) {
background: red;
}
}
会被编译成
body div, body span, body section {
background: red;
}
总结
在平常不经意间用了高特性时会导致在低版本浏览器报错,在使用 js 和 css 预处理器后 能够提高代码运行的下限,但也不是所有的特性都能被 polyfill, 比如 js 的 Proxy,css 的 gap property for Flexbox, 写代码的时候要注意兼容性和是否能被 polyfill 。
注意:
-
经常用的 gap property for Flexbox 至少在chrome 84及以上,最好使用 Antd4.x 的 Space 组件,Antd4.x 在实现
Space
时会用一些 hack 手段检测是否支持该特性,在不支持的时候,会使用 margin 代替。 -
Postcss 属于 AOT,在 runtime 之前就编译完成 ,Css in js 方案是在 runtime 的时候动态添加样式,所以
styled-component
里的样式并不会走 Postcss,无法完成兼容处理。