webpack中的浏览器兼容性问题和Polyfill的使用

287 阅读7分钟

浏览器兼容性问题

我们来思考一个问题:开发中,浏览器的兼容性问题,我们应该如何去解决和处理?

  • 当然这个问题很笼统,这里我说的兼容性问题不是指屏幕大小的变化适配;
  • 我这里指的兼容性是针对不同的浏览器支持的特性:比如css特性、js语法之间的兼容性;

我们知道市面上有大量的浏览器:

  • 有Chrome、Safari、 IE、 Edge、 Chrome for Android、UC Browser、QQ Browser等等; .
  • 它们的市场占率是多少?我们要不要兼容它们呢?

其实在很多的脚手架配置中,都能看到类似于这样的配置信息:

 > 1%
 last 2 versions
 not dead

image-20230111152234208

认识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中配置;

    image-20230111154823104

  • 方案二:单独的一个配置文件.browserslistrc文件;

    image-20230111160128282

默认配置和条件

如果没有配置,那么也会有一个默认配置:

image-20230111160258554

我们编写了多个条件之后,多个条件之间是什么关系呢?

image-20230111160313250

设置目标浏览器 browserslist

我们最终打包的JavaScript代码,是需要跑在目标浏览器上的,那么如何告知babel我们的目标浏览器呢?

  • browserslist工具
  • target属性

之前我们已经使用了browserslist工具,我们可以对比一下不同的配置, 打包的区别:

image-20230111161332397

            // 使用预设
            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来设置 ;

image-20230111162226425

认识Polyfill

Polyfill是什么呢?

  • 翻译: 一种用于衣物、床具等的聚酯填充材料,使这些物品更加温暖舒适;
  • 理解:更像是应该填充物(垫片),一个补丁,可以帮助我们更好的使用JavaScript;

为什么时候会用到polyfill呢?

  • 比如我们使用了一些语法特性 (例如: Promise, Generator, Symbol等以及实例方法例如Array.prototype.includes等)
  • 但是某些浏览器压根不认识这些特性,必然会报错;
  • 我们可以使用polyfill来填充或者说打一个补丁 ,那么就会包含该特性了;

image-20230111163830814

如何使用Polyfill?

babel7.4.0之前,可以使用@babel/polyfill的包,但是该包现在已经不推荐使用了:

image-20230111164350543

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"
    }]
  ]