7.浏览器兼容性browserslist

777 阅读4分钟

1.browserslist相匹配的浏览器列表

image.png

 package.json 文件里的 browserslist 字段 (或一个单独的 .browserslistrc 文件),指定了项目的目标浏览器的范围。browserslist会被 @babel/preset-env 和 Autoprefixer 用来确定需要转译的 JavaScript 特性和需要添加的 CSS 浏览器前缀。

image.png

image.png

查阅了解如何指定浏览器范围

  • > 5%: 基于全球使用率统计而选择的浏览器版本范围。>=,<,<=同样适用。

  • > 5% in US : 同上,只是使用地区变为美国。支持两个字母的国家码来指定地区。

  • > 5% in alt-AS : 同上,只是使用地区变为亚洲所有国家。这里列举了所有的地区码。

  • > 5% in my stats : 使用定制的浏览器统计数据

  • cover 99.5% : 使用率总和为99.5%的浏览器版本,前提是浏览器提供了使用覆盖率。

  • cover 99.5% in US : 同上,只是限制了地域,支持两个字母的国家码。

  • cover 99.5% in my stats :使用定制的浏览器统计数据

  • maintained node versions :所有还被 node 基金会维护的 node 版本。

  • node 10 and node 10.4 : 最新的 node 10.x.x 或者10.4.x 版本。

  • current node :当前被 browserslist 使用的 node 版本。

  • extends browserslist-config-mycompany :来自browserslist-config-mycompany包的查询设置

  • ie 6-8 : 选择一个浏览器的版本范围。

  • Firefox > 20 : 版本高于20的所有火狐浏览器版本。>=,<,<=同样适用。

  • ios 7 :ios 7自带的浏览器。

  • Firefox ESR :最新的火狐 ESR(长期支持版) 版本的浏览器。

  • unreleased versions or unreleased Chrome versions : alpha 和 beta 版本。

  • last 2 major versions or last 2 ios major versions :最近的两个发行版,包括所有的次版本号和补丁版本号变更的浏览器版本。

  • since 2015 or last 2 years :自某个时间以来更新的版本(也可以写的更具体since 2015-03或者since 2015-03-10

  • dead :通过last 2 versions筛选的浏览器版本中,全球使用率低于0.5%并且官方声明不在维护或者事实上已经两年没有再更新的版本。目前符合条件的有 IE10,IE_Mob 10,BlackBerry 10,BlackBerry 7,OperaMobile 12.1

  • last 2 versions :每个浏览器最近的两个版本。

  • last 2 Chrome versions :chrome 浏览器最近的两个版本。

  • defaults :默认配置> 0.5%, last 2 versions, Firefox ESR, not dead

  • not ie <= 8 : 浏览器范围的取反。

  • 可以添加not在任和查询条件前面,表示取反

2.Polyfill ——ES填充

image.png

Polyfill 是一块代码(通常是 Web 上的 JavaScript),用来为旧浏览器提供它没有原生支持的较新的功能。

2.1 useBuiltIns: 'usage'

一个默认的 Vue CLI 项目会使用 @vue/babel-preset-app,项目通过 @babel/preset-env 和 browserslist 配置来决定项目需要的 polyfill。

image.png

默认情况下,项目会把 useBuiltIns: 'usage' 传递给 @babel/preset-env,这样它会根据源代码中出现的语言特性自动检测需要的 polyfill。这确保了最终包里 polyfill 数量的最小化。然而,这也意味着如果其中一个依赖需要特殊的 polyfill,默认情况下 Babel 无法将其检测出来。

如果有依赖需要 polyfill,有以下几种选择:

image.png

  1. 如果该依赖基于一个目标环境不支持的 ES 版本撰写:  将其添加到 vue.config.js 中的 transpileDependencies 选项。这会为该依赖同时开启语法转换和根据使用情况检测 polyfill。

  2. 如果该依赖交付了 ES5 代码并显式地列出了需要的 polyfill:  你可以使用 @vue/babel-preset-app 的 polyfills 选项预包含所需要的 polyfill。注意 es.promise 将被默认包含,因为现在的库依赖 Promise 是非常普遍的。

    // babel.config.js
    module.exports = {
      presets: [
        ['@vue/app', {
          polyfills: [
            'es.promise',
            'es.symbol'
          ]
        }]
      ]
    }
    

    推荐以这种方式添加 polyfill 而不是在源代码中直接导入它们,因为如果这里列出的 polyfill 在 browserslist 的目标中不需要,则它会被自动排除。

  3. 如果该依赖交付 ES5 代码,但使用了 ES6+ 特性且没有显式地列出需要的 polyfill (例如 Vuetify):请使用 useBuiltIns: 'entry' 然后在入口文件添加 import 'core-js/stable'; import 'regenerator-runtime/runtime';。这会根据 browserslist 目标导入所有 polyfill,这样你就不用再担心依赖的 polyfill 问题了,但是因为包含了一些没有用到的 polyfill 所以最终的包大小可能会增加。

更多查阅 @babel/preset-env 文档

2.2 构建库或是 Web Component 时的 Polyfills

当使用 Vue CLI 来构建一个库或是 Web Component 时,推荐给 @vue/babel-preset-app 传入 useBuiltIns: false 选项。这能够确保你的库或是组件不包含不必要的 polyfills。通常来说,打包 polyfills 应当是最终使用你的库的应用的责任。

3. Vue CLI 现代模式vue-cli-service build --modern

有了 Babel 我们可以兼顾所有最新的 ES2015+ 语言特性,但也意味着需要交付转译和 polyfill 后的包以支持旧浏览器。这些转译后的包通常都比原生的 ES2015+ 代码会更冗长,运行更慢。现如今绝大多数现代浏览器都已经支持了原生的 ES2015,所以因为要支持更老的浏览器而为它们交付笨重的代码是一种浪费。

Vue CLI 提供了一个“现代模式”帮你解决这个问题。以如下命令为生产环境构建:

vue-cli-service build --modern

Vue CLI 会产生两个应用的版本:一个现代版的包,面向支持 ES modules 的现代浏览器,另一个旧版的包,面向不支持的旧浏览器。

最酷的是这里没有特殊的部署要求。其生成的 HTML 文件会自动使用 Phillip Walton 精彩的博文中讨论到的技术:

  • 现代版的包会通过 <script type="module"> 在被支持的浏览器中加载;它们还会使用 <link rel="modulepreload"> 进行预加载。
  • 旧版的包会通过 <script nomodule> 加载,并会被支持 ES modules 的浏览器忽略。
  • 一个针对 Safari 10 中 <script nomodule> 的修复会被自动注入。

image.png

image.png

1660662468182.jpg

对于一个 Hello World 应用来说,现代版的包已经小了 16%。在生产环境下,现代版的包通常都会表现出显著的解析速度和运算速度,从而改善应用的加载性能。

<script type="module"> 需要配合始终开启的 CORS 进行加载。这意味着你的服务器必须返回诸如 Access-Control-Allow-Origin: * 的有效的 CORS 头。如果想要通过认证来获取脚本,可使将 crossorigin 选项设置为 use-credentials

image.png

4. 现代版的包<script type="module">例子

image.png

// src/views/Test/Test-1.vue
<template>
  <div class="test"></div>
</template>

<script type="module">
import { addTextToBody } from "./utils.mjs";
addTextToBody("Modules are pretty cool.");
export default {
  name: "Test",
  data() {
    return {};
  }
};
</script>

<style></style>
// utils.mjs
export function addTextToBody(text) {
  const div = document.createElement("div");
  div.textContent = text;
  document.body.appendChild(div);
}