兼容方案的背景

面临的问题

  • 虽然JS很多语法已经到了正式发布的阶段,但是由于浏览器的支持程度不同导致我们无法放心的使用。
  • 有些已经到了 stage3 的语法我们也想尝试使用,如何正常运行?

理想的场景

我们可以放心的编写js代码,不用考虑其他问题。

如何实现

在编译的时候通过一种方法让浏览器不支持的语法转换成浏览器支持的语法,这样代码就可以正常运行。

通过语法转换实现代码兼容

前端项目通常采用 babel 来转换 js 代码,写这篇文章的时候最新的版本是 babel v7, 下面的方案也会只介绍在这个版本下的。

明确目标

在实现方案之前我们需要想清楚几个问题:

  1. 需要兼容哪些语法: 根据 TC39,你用到了 stage-x(x: 1-4) 的语法
  2. 不同浏览器对语法支持程度不同, 你要处理哪些浏览器的兼容
  3. 代码兼容的处理方式是按需引入还是全部引入(各有优劣,接下来会介绍)

思考🤔 3 min ...

方案实现

polyfill 方案

方案
@babel/preset-env + corejs@3

特点

  • 通过 useBuiltIns配置,可以采用按需加载和全部加载的方式实现兼容
  • 会污染全局:在全局和实例上添加api
  • 支持目标浏览器设置: 通过 targets 或 browserslist, 可是实现特定浏览器下的兼容,减少代码体积

介绍
core-js:Javascript 标准语法实现库
@babel/preset-env

  • 一系列语法转换 plugin 的集合,它支持的 plugin 可以参考这里。通过这些plugin 可以实现语法兼容。
  • 可以通过 browserslist 实现针对特定浏览器下的 api 兼容: 简单来说就是 browserslist 会根据 caniuse 上的数据和目标浏览器对比得出需要兼容哪些api。详细的可以深入理解 browserslist

注意事项

  • @babel/preset-env 兼容到 stage4也就是正式版。如果要使用 stage3这种语法,需要在 bablerc plugins里面再引入对应 plugin
  • 你需要配置 targets 或 browserslist 才能实现特定浏览器语法兼容

方式一: 全部引入
此时 babel 会根据当前 targets 描述,把需要的所有的 polyfills 全部引入到你的入口文件(注意是全部,不管你是否有用到高级的 API)。

第一步:.babelrc 文件内容:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "entry",
        "targets":"> 1%, not dead", // 根据情况自己设置
        "corejs": {
          "version": 3,// 使用 corejs@3
          "proposals": true
        }
      }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime", // 如果使用polyfill方案,这里配置可选
      {
        "corejs": false // 关闭 runtime
      }
    ]
  ]
}

第二步:入口文件 index.js:

import 'core-js/stable';
import 'regenerator-runtime/runtime';
// 入口文件代码
  • core-js/stable 会引入所有的polyfill,好处是不用担心以来的库有语法不支持的情况,缺点是代码体积大。想减少体积也可以按需引入,例如 import 'core-js/stable/array/find';
  • regenerator-runtime/runtime : generatorasync functionruntime

方式二: 按需引入
我们在项目的入口文件处不需要 import 对应的 polyfills 相关库。babel 会根据用户代码的使用情况,并根据 targets 自行注入相关 polyfills。

.babelrc 文件内容:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "targets":"> 1%, not dead", // 根据情况自己设置
        "corejs": {
          "version": 3, // 使用 corejs@3
          "proposals": true
        }
      }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",// 如果使用polyfill方案,这里配置可选
      {
        "corejs": false // 关闭 runtime
      }
    ]
  ]
}

runtime 方案

方案
@babel/preset-env + @babel/runtime-corejs3 + @babel/plugin-transform-runtime

特点

  • 不会污染全局的:@babel/plugin-transform-runtime 插件通过模拟 api 实现兼容,不会污染全局的,所以更适合 Library 作者使用
  • 仅支持按需引入
  • 使用到的api都会被替换, 不支持 targets 和 browserslist

介绍

  • 仍然需要 @babel/preset-env 对语法进行转换
  • @babel/runtime-corejs3: 主用来模拟实现 api 功能的库
  • @babel/plugin-transform-runtime:实现按需引用 @babel/runtime-corejs3 里面的 module

注意
切换到 corejs@3 之后, runtime 方案也可以模拟实例方法了,像 array.includes 这种已经可以实现,不需要引入额外的 polyfill

具体实现

.babelrc 文件内容:

{
  "presets": [
    [
      "@babel/preset-env",
     {
        "useBuiltIns": false, // false 这种方式下,不会引入 polyfills
        "targets":"> 1%, not dead", // 根据情况自己设置
     }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime", // runtime 方案下,必须设置
      {
        "corejs": {
          "version": 3, // 使用 runtime-corejs@3
          "proposals": true
        }
      }
    ]
  ]
}

总结

目前,babel处理兼容性问题有两种方案:

  • @babel/preset-env + corejs@3实现语法转换、在全局和实例上添加api,支持全量加载和按需加载,我们简称polyfill方案;
  • @babel/preset-env + @babel/runtime-corejs3 + @babel/plugin-transform-runtime实现语法转换、模拟替换api,只支持按需加载,我们简称runtime方案。

两种方案都依赖核心包corejs@3,只不过依赖的模块不同,导致实现方式不同。两种方案各有优缺点:

  • polyfill方案很明显的缺点就是会造成全局污染,而且会注入冗余的工具代码;优点是可以根据浏览器对新特性的支持度来选择性的进行兼容性处理;
  • runtime方案虽然解决了polyfill方案的那些缺点,但是不能根据浏览器对新特性的支持度来选择性的进行兼容性处理,也就是说只要在代码中识别到的api,并且该api也存在core-js-pure包中,就会自动替换,这样一来就会造成一些不必要的转换,从而增加代码体积。

所以,polyfill方案比较适合单独运行的业务项目,如果你是想开发一些供别人使用的第三方工具库,则建议你使用runtime方案来处理兼容性方案,以免影响使用者的运行环境。

稍等,思考一种更好的方案: runtime 方案也支持 api targets 不就完美了吗🤔... 还真有一个正在试验阶段的:babel-polyfills

QA

babelrc 文件 tagets、ignoreBrowserConfig 和 browserslist 的优先级和默认配置

  • 配置:targets > browserslist
  • 设置了 ignoreBrowserConfig:true 将默认不再读取 browserslist 配置
  • 两个都不存在时, 参考 No targets【大概意思是全部的ES2015之后的语法都会转换成 ES5】

@babel/polyfill 与 @babel/preset-env 的关系

  • @babel/polyfill 是 babel@6 and core-js@2时代的产物, 类似 @babel/preset-env 的 "useBuiltIns":"entry",但是不支持 browserslist,目前已经被弃用。
  • @babel/preset-env 支持 browserslist,还有很多新特性,兼容必备

babel-plugin-transform-runtime 的作用

主要有两个作用:

  • 复用通过 Babel 注入的 helper 的代码,减少代码冗余
  • 通过模拟 api 的方式实现代码兼容【runtime方案】

两种方案的具体配置

如上文,也可以看这里:https://developer.aliyun.com/...

runtime 方案可以使用 browserslist 配置吗?

  • runtime方案的完整方法是:@babel/preset-env + @babel/runtime-corejs3 + @babel/plugin-transform-runtime
  • @babel/plugin-transform-runtime 是处理 api 部分,语法转换仍然需要 @babel/preset-env, 所以在runtime方案下, target 对 语法转换仍是起作用的,但是对API部分是无效的

参考文章


specialcoder
2.2k 声望170 粉丝

前端 设计 摄影 文学