开发时我们会使用一些新的 api,但用户的浏览器各种版本都有,可能并不支持这些 api,但我们也不能因此就不用了,这时候就可以通过 polyfill 来解决。
polyfill 是垫片的意思,也就是在运行业务代码之前,在全局注入一些 api 的实现,这样之后运行的业务代码用到该 api 时就有了,也就不会有兼容问题。
但用户的浏览器可能是各种各样的,有可能我们 polyfill 的 api 本来就支持,这时候加载 polyfill 就很没必要,而且也浪费了性能。
有没有什么办法能够既解决不支持这个 api 的运行环境的兼容问题,又不会在支持这个 api 的环境引入不必要的代码呢?
答案就是 preset-env,它实现了按需引入 polyfill。
这里的 preset-env 指的是 babel 的 @babel/preset-env 和 postcss 的 postcss-preset-env,它们一个是按需做语法转换、按需引入 JS 的 polyfill,一个是按需做添加 prefix 等 css 兼容处理。
虽然分别是针对 JS 和 CSS 的,但他们两个的原理差不多,我们分别来看一下。
按需指的是按照目标运行环境是否支持,那怎么指定目标运行环境呢?
@babel/preset-env 支持通过 targets 来指定目标环境:
{
"presets": [
["@babel/preset-env", {
"targets": "> 0.25%, not dead"
}]
]
}
这里的 targets 是 browserslist 的查询字符串,它可以解析查询字符串返回对应的浏览器版本:
有了这些目标浏览器的版本,还需要知道各种特性是在什么版本支持的:
babel 维护了一个数据库,在 @babel/compat-data 这个包里:
这样就能根据目标浏览器的版本,过滤出哪些特性是支持的,哪些是不支持的。然后只对不支持的特性做语法转换和 polyfill 即可。
这就是按需 polyfill 的实现原理。
css 的按需做兼容处理也差不多:
postcss 是通过 postcss-preset-env 来做按需处理的,同样支持配置目标环境,也就是 browsers:
postcssPresetEnv({ browsers: 'last 2 versions' })
同样也是用 browserslist 来把查询字符串转换成对应的浏览器版本。
然后再根据哪些 css 特性是从什么浏览器版本开始支持的来过滤出不支持的 css 特性。
这里用的是 caniuse 的数据,在 cssdb 这个包里:
知道了目标浏览器的版本,知道了这些特性在什么浏览器版本支持,那自然就可以过滤出不支持的 css 特性。然后引入不同的 postcss 插件来处理这些特性的 prefix 等就可以了。
这就是 css 的按需 prefix 等处理的原理。
可以看到 babel 和 potcss 都依赖了 browerslist 这个包来查询目标浏览器版本,那自然可以统一成一个,也就是在根目录下的 .browserslistrc 的配置文件,通过指定同一个的环境来按需做 JS 和 CSS 的兼容处理。
用户的浏览器可能是各种各样的,我们开发时用的 JS 和 CSS 的特性在用户的浏览器不一定支持,为了保证功能正常就要做兼容处理。
JS 的兼容处理就是 polyfill,CSS 的兼容处理就是添加 prefix 等。
babel 是通过 @babel/preset-env 来做按需 polyfill 和转换的,原理是通过 browserslist 来查询出目标浏览器版本,然后根据 @babel/compat-data 的数据库来过滤出这些浏览器版本里哪些特性不支持,之后引入对应的插件处理。
postcss 是通过 postcss-preset-env 来做按需 prefix 等的,原理也是通过 browserslist 来查询出目标浏览器版本,然后根据 cssdb 的数据库(来自 caniuse)来过滤出不支持的 CSS 特性,然后对这些 CSS 做处理即可。
就像 preset-env 的名字一样,它们的意义就是根据目标环境来按需做处理的,也被叫做智能处理,比之前的 es5、es6 这种粗暴的指定目标,确实聪明了很多。