Skip to content

前端浏览器兼容性处理 #21

@Hazlank

Description

@Hazlank

前端浏览器兼容性处理

引言

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 

接着执行 npx browserslist
image

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)

image

浏览器版本在72时,并不支持 Class private valuePromise.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。
image

使用 @babel/plugin-transform-runtime 后,实现函数变成了引用@babel/runtime的hleper函数来完成这些工作。产出的文件大小在4kb,缩小了3倍。
image

@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 。

注意:

  1. 经常用的 gap property for Flexbox 至少在chrome 84及以上,最好使用 Antd4.x 的 Space 组件,Antd4.x 在实现 Space 时会用一些 hack 手段检测是否支持该特性,在不支持的时候,会使用 margin 代替。

  2. Postcss 属于 AOT,在 runtime 之前就编译完成 ,Css in js 方案是在 runtime 的时候动态添加样式,所以 styled-component 里的样式并不会走 Postcss,无法完成兼容处理。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions