浏览器兼容性问题
我们来思考一个问题:开发中,浏览器的兼容性问题,我们应该如何去解决和处理?
- 当然这个问题很笼统,这里我说的兼容性问题不是指屏幕大小的变化适配;
- 我这里指的兼容性是针对不同的浏览器支持的特性:比如css特性、js语法之间的兼容性;
我们知道市面上有大量的浏览器:
- 有Chrome、Safari、 IE、 Edge、 Chrome for Android、UC Browser、QQ Browser等等; .
- 它们的市场占率是多少?我们要不要兼容它们呢?
其实在很多的脚手架配置中,都能看到类似于这样的配置信息:
> 1%
last 2 versions
not dead
认识Browserslist工具
但是有一个问题,我们如何可以在css兼容性和js兼容性下共享我们配置的兼容性条件呢?
- 就是当我们设置了一个条件: > 1%;
- 我们表达的意思是css要兼容市场占有率大于1%的浏览器,js也要兼容市场占有率大于1%的浏览器;
- 如果我们是通过工具来达到这种兼容性的,比如我们讲到的postcss-preset-env、 babel. autoprefixer等
如何可以让他们共享我们的配置呢?
- 这个问题的答案就是Browserslist;
Browserslist是什么? Browserslist是一个在不同的前端工具之间,共享目标浏览器和Node.js版本的配置:
- Autoprefixer
- Babel
- postcss-preset-env
- eslint-plugin-compat
- stylelint-no-unsupported-browser-features
- postcss-normalize
- obsolete-webpack-plugin
浏览器查询过程
我们可以编写类似于这样的配置:
> 1%
last 2 versions
not dead
那么之后,这些工具会根据我们的配置来获取相关的浏览器信息,以方便决定是否需要进行兼容性的支持:
- 条件查询使用的是caniuse-lite的工具,这个工具的数据来自于caniuse的网站上;
Browserslist编写规则
那么在开发中,我们可以编写的条件都有哪些呢? (加粗部分是最常用的)
- defaults: Browserslist的默认浏览器(> 0.5%, last 2 versions, Firefox ESR, not dead)。
- 5%:通过全局使用情况统计信息选择的浏览器版本。>=, <和<=工作过。
- dead: 24个月内没有官方支持或更新的浏览器。现在是IE 10, IE Mob 11, BlackBerry 10, BlackBerry 7,Samsung 4和OperaMobile 12.1。
- last 2 versions:每个浏览器的最后2个版本。
- node 10和node 10.4:选择最新的Node.js10.x.x 或10.4.x版本。
- ios 7:直接使用iOS浏览器版本7。
- extends browserslist-config-mycompany:从browserslist-config-mycompanynpm包中查询 。
- supports es6-module:支持特定功能的浏览器。
- browserslist config:在Browserslist配置中定义的浏览器。
- since 2015或last 2 years
- unreleased versions或unreleased Chrome versions: Alpha和Beta版本。
- not ie <= 8:排除先前查询选择的浏览器。
配置Browserslist
我们如何可以配置browserslist呢?两种方案:
-
方案一:在package.json中配置;
-
方案二:单独的一个配置文件.browserslistrc文件;
默认配置和条件
如果没有配置,那么也会有一个默认配置:
我们编写了多个条件之后,多个条件之间是什么关系呢?
设置目标浏览器 browserslist
我们最终打包的JavaScript代码,是需要跑在目标浏览器上的,那么如何告知babel我们的目标浏览器呢?
- browserslist工具
- target属性
之前我们已经使用了browserslist工具,我们可以对比一下不同的配置, 打包的区别:
// 使用预设
presets: [
["@babel/preset-env", {
// 在开发中针对babel的浏览器兼容查询使用browserslist工具,而不是设置target
// 因为browserslist工具,可以在多个前端工具之间进行共享浏览器兼容性(postcss/babel)
// targets: ">5%"
}]
]
那么,如果两个同时配置了,哪一个会生效呢?
- 配置的targets属性会覆盖browserslist;
- 但是在开发中,更推荐通过browserslist来配置,因为类似于postcss工具,也会使用browserslist, 进行统一浏览器的适配
Stage-X的preset(了解)
要了解Stage-X,我们需要先了解一下TC39的组织: .
- TC39是指技术委员会(Technical Committee)第39号;
- 它是ECMA的一部分,ECMA是"ECMAScript” 规范下的JavaScript语言标准化的机构;
- ECMAScript规范定义了JavaScript如何一步一步的进化、 发展;
TC39遵循的原则是:分阶段加入不同的语言特性,新流程涉及四个不同的Stage
- Stage 0: strawman (稻草人),任何尚未提交作为正式提案的讨论、想法变更或者补充都被认为是第0阶段的"稻草人";
- Stage 1: proposal (提议) ,提案已经被正式化,并期望解决此问题,还需要观察与其他提案的相互影响;
- Stage2: draft (草稿),Stage 2的提案应提供规范初稿、草稿。此时,语言的实现者开始观察runtime的具体实现是否合理;
- Stage 3: candidate (候补), Stage 3提案是建议的候选提案。在这个高级阶段,规范的编辑人员和评审人员必须在最终规范上签字。Stage 3的提案不会有太大的改变,在对外发布之前只是修正一些问题;
- Stage 4: finished (完成) ,进入Stage 4的提案将包含在ECMAScript的下一一个修订版中;
在babel7之前(比如babel6中) ,我们会经常看到这种设置方式:
- 它表达的含义是使用对应的babel-preset-stage-x 预设;
- 但是从babel7开始,已经不建议使用了,建议使用preset-env来设置 ;
认识Polyfill
Polyfill是什么呢?
- 翻译: 一种用于衣物、床具等的聚酯填充材料,使这些物品更加温暖舒适;
- 理解:更像是应该填充物(垫片),一个补丁,可以帮助我们更好的使用JavaScript;
为什么时候会用到polyfill呢?
- 比如我们使用了一些语法特性 (例如: Promise, Generator, Symbol等以及实例方法例如Array.prototype.includes等)
- 但是某些浏览器压根不认识这些特性,必然会报错;
- 我们可以使用polyfill来填充或者说打一个补丁 ,那么就会包含该特性了;
如何使用Polyfill?
babel7.4.0之前,可以使用@babel/polyfill的包,但是该包现在已经不推荐使用了:
babel7.4.0之后,可以通过单独引入core-js和regenerator-runtime来完成polyfill的使用:
npm install core-js regenerator-runtime --save
我们需要在babel.config.js文件中进行配置,给preset-env配置一些属性:
useBuiltIns: 设置以什么样的方式来使用polyil;
corejs: 设置corejs的版本,目前使用较多的是3.x的版本,比如我使用的是3.8.x的版本;
- 另外corejs可以设置是否对提议阶段的特性进行支持;
- 设置proposals属性为true即可;
useBuiltIns属性
useBuiltIns属性有三个常见的值 第一个值: false
- 打包后的文件不使用polyill来进行适配;
- 并且这个时候是不需要设置corejs属性的;
第二个值: usage (推荐)
- 会根据源代码中出现的语言特性,自动检测所需要的polyfill;
- 这样可以确保最终包里的polyfill数量的最小化,打包的包相对会小一些;
- 可以设置corejs属性来确定使用的corejs的版本;
presets: [
["@babel/preset-env", {
corejs: 3,
useBuiltIns: "usage"
}]
]
第三个值: entry
- 如果我们依赖的某一个库本身使用 了某些polyfill的特性,但是因为我们使用的是usage,所以之后用户浏览器可能会报错;
- 所以,如果你担心出现这种情况,可以使用entry;
- 并且需要在入口文件中添加import 'core-js/stable'; import 'regenerator-runtime/runtime'; .
- 这样做会根据browserslist 目标导入所有的polyfill,但是对应的包也会变大;
presets: [
["@babel/preset-env", {
corejs: 3,
useBuiltIns: "entry"
}]
]
useBuiltIns属性
useBuiltIns属性有三个常见的值 第一个值: false
- 打包后的文件不使用polyill来进行适配;
- 并且这个时候是不需要设置corejs属性的;
第二个值: usage (推荐)
- 会根据源代码中出现的语言特性,自动检测所需要的polyfill;
- 这样可以确保最终包里的polyfill数量的最小化,打包的包相对会小一些;
- 可以设置corejs属性来确定使用的corejs的版本;
presets: [
["@babel/preset-env", {
corejs: 3,
useBuiltIns: "usage"
}]
]
第三个值: entry
- 如果我们依赖的某一个库本身使用 了某些polyfill的特性,但是因为我们使用的是usage,所以之后用户浏览器可能会报错;
- 所以,如果你担心出现这种情况,可以使用entry;
- 并且需要在入口文件中添加import 'core-js/stable'; import 'regenerator-runtime/runtime'; .
- 这样做会根据browserslist 目标导入所有的polyfill,但是对应的包也会变大;
presets: [
["@babel/preset-env", {
corejs: 3,
useBuiltIns: "entry"
}]
]