Preset-Env 按需 Polyfill 是怎么實現的?
本文轉載自微信公眾號「神光的編程秘籍」,作者神說要有光。轉載本文請聯系神光的編程秘籍公眾號。
開發時我們會使用一些新的 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
按需指的是按照目標運行環境是否支持,那怎么指定目標運行環境呢?
@babel/preset-env 支持通過 targets 來指定目標環境:
{
"presets": [
["@babel/preset-env", {
"targets": "> 0.25%, not dead"
}]
]
}
這里的 targets 是 browserslist 的查詢字符串,它可以解析查詢字符串返回對應的瀏覽器版本:
有了這些目標瀏覽器的版本,還需要知道各種特性是在什么版本支持的:
babel 維護了一個數據庫,在 @babel/compat-data 這個包里:
這樣就能根據目標瀏覽器的版本,過濾出哪些特性是支持的,哪些是不支持的。然后只對不支持的特性做語法轉換和 polyfill 即可。
這就是按需 polyfill 的實現原理。
css 的按需做兼容處理也差不多:
postcss-preset-env
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 這種粗暴的指定目標,確實聰明了很多。