致咱們學前端的小時光—corejs與env、runtime的不解之緣

前言

最近在看致咱們暖暖的小時光,有點不可自拔。ios

文章具備翻譯向,具體的能夠看文末的連接。git

隨着ES6的正式發佈,以及ES201六、ES2017...每一年的穩定更新,還有新提案的不斷出現,使得JavaScript愈來愈成熟。可是對於一些新的語法和API,老版本的瀏覽器沒法全面兼容。babel的出現,解決了在老版本瀏覽器使用新語法的問題,它如今不只僅是一個ES6 to ES5單純的語法轉換工具,更是一個規範和生態,幫咱們去更高效的處理JavaScript。es6

@babel/preset-env登場

@babel/preset-env是做爲babel-preset-es2015的替代品出現的,主要的做用是用來轉換那些已經被正式歸入TC39中的語法。因此它沒法對那些還在提案中的語法進行處理,對於處在stage中的語法,須要安裝對應的plugin進行處理。github

除了語法轉換,@babel/preset-env另外一個重要的功能是對polyfill的處理。新加入標準庫的,多是一些語法特性,好比箭頭函數等,還有多是一些新的API,好比promise、set、inclues等。web

對於語法,babel能夠經過生成靜態語法樹,去作一些轉換,生成對應的ES5的代碼。npm

可是對於新的API,須要瀏覽器去原生支持,或者使用大量的代碼去進行API的模擬。@babel/polyfill就是API的墊片,經過引入這些墊片,使得低版本的瀏覽器能模擬實現那些新的API。api

而今天的主角core-js就是@babel/polyfill的核心依賴,它如今已經發布了3.0的版本,並且@babel/preset-env在7.4.0的版本已經支持這個最新的版本。大版本的升級,會帶來一些破壞性,可是相應的也會帶來不少優點。數組

core-js 被遺忘的包

core-js是什麼

  • 它是JavaScript標準庫的polyfill
  • 它儘量的進行模塊化,讓你能選擇你須要的功能
  • 它能夠不污染全局空間
  • 它和babel高度集成,能夠對core-js的引入進行最大程度的優化

core-js升級的動機

  • core-js中的破壞性變動只能在主版本的升級中進行
  • core-js@2.0的版本已經在一年半以前凍結,全部的新特性只會添加到3.0的分支中

core-js@3的重要改變

  • 對於ECMAScript中已經穩定的功能,core-js已經幾乎徹底支持,並在core-js@3中引入了一些新的功能
  • 對於一些已經加入到ES2016-ES2019中的提案,如今已經被標記爲穩定功能
  • 增長了proposals配置項,對處在提案階段的api提供支持,可是由於提案階段並不穩定,在正式加入標準以前,可能會有大的改動,須要謹慎使用;對於一些改變巨大的提案,也進行了對應的更新
  • 增長了對一些web標準的支持,好比URL 和 URLSearchParams
  • 刪除了一些過期的特性

monorepos 包的拆分

core-js@2一個最多見的問題就是包的體積太大(~2M),而且有不少重複的文件被引用。基於這個緣由,core-js@3對包進行拆分,三個核心的包分別是promise

  • core-js:定義全局的polyfill(~500k, 40k minified and gzipped)
  • core-js-pure:提供不污染全局環境的polyfill,等價於core-js@2/library(~440k)
  • core-js-compat:包含了core-js模塊和API必要的數據,經過browserslist來生成所須要的core-js模塊的列表

在之前的版本中,已進入ECMAScript標準的特性用es6.的前綴來表示,提案階段的特性用es7.的前綴來表示,選擇這個前綴的緣由是在2014年的時候ES6之後的全部特性都考慮使用ES7來進行命名。瀏覽器

在cores-js@3的版本中,因此規範中的特性都使用es.這個前綴,而提案中的特性使用esnext.這個前綴。

幾乎全部的CommonJS的入口文件都已經發生改變。在core-js@3中,包含了更多的模塊入口。這使得對於目標瀏覽器的按需支持更加的具備靈活性,同時能夠帶來文件大小方面的優化。

在core-js@2中,@babel/preset-evn在插件內部有一個data-table,維護了不一樣瀏覽器對於特定API的支持,經過這個data-table來實現不一樣targets按需加載所須要的core-js模塊。因爲這個compat-table存在一些固有的問題,做者從新維護了一個包,即core-js-compat,用來提供不用目標引擎所須要的core-js的模塊信息。

const {
  list,              // array of required modules
  targets,           // object with targets for each module
} = require('core-js-compat')({
  targets: '> 2.5%', // browserslist query
  filter: 'es.',     // optional filter - string-prefix, regexp or list of modules
});

console.log(targets);

/* =>
{
  'es.symbol.description': { ios: '12.0-12.1' },
  'es.array.reverse': { ios: '12.0-12.1' },
  'es.string.replace': { firefox: '63', ios: '12.0-12.1' },
  'es.string.trim': { ios: '12.0-12.1' },
  'es.promise': { firefox: '63' },
  'es.promise.finally': { firefox: '63' },
  'es.array-buffer.slice': { ios: '12.0-12.1' },
  'es.typed-array.int8-array': { ios: '12.0-12.1' },
  'es.typed-array.uint8-array': { ios: '12.0-12.1' },
  'es.typed-array.uint8-clamped-array': { ios: '12.0-12.1' },
  'es.typed-array.int16-array': { ios: '12.0-12.1' },
  'es.typed-array.uint16-array': { ios: '12.0-12.1' },
  'es.typed-array.int32-array': { ios: '12.0-12.1' },
  'es.typed-array.uint32-array': { ios: '12.0-12.1' },
  'es.typed-array.float32-array': { ios: '12.0-12.1' },
  'es.typed-array.float64-array': { ios: '12.0-12.1' },
  'es.typed-array.from': { ios: '12.0-12.1' },
  'es.typed-array.of': { ios: '12.0-12.1' }
}
*/
複製代碼

對於core-js@3新的入口文件,下面有一些簡單的例子

// polyfill all `core-js` features:
import "core-js";
// polyfill only stable `core-js` features - ES and web standards:
import "core-js/stable";
// polyfill only stable ES features:
import "core-js/es";

// if you want to polyfill `Set`:
// all `Set`-related features, with ES proposals:
import "core-js/features/set";
// stable required for `Set` ES features and features from web standards
// (DOM collections iterator in this case):
import "core-js/stable/set";
// only stable ES features required for `Set`:
import "core-js/es/set";
// the same without global namespace pollution:
import Set from "core-js-pure/features/set";
import Set from "core-js-pure/stable/set";
import Set from "core-js-pure/es/set";

// if you want to polyfill just required methods:
import "core-js/features/set/intersection";
import "core-js/stable/queue-microtask";
import "core-js/es/array/from";

// polyfill reflect metadata proposal:
import "core-js/proposals/reflect-metadata";
// polyfill all stage 2+ proposals:
import "core-js/stage/2";
複製代碼

core-js@3 與 babel

如上面提到的,core-js與babel是高度集成的,babel的集成給core-js的按需加載提供了可能。在babel7.4.0的版本中已經支持core-js@3的版本。

@babel/prest-env

在升級到7.4.0以上的版本之後,既支持core-js@2,也支持core-js@3。因此增長了corejs的配置,來控制所需的版本,默認是core-js@2而且會有文字輸出提示升級到3的版本。

@babel/prest-env能夠經過配置useBuiltIns來根據targets加載@babel/polyfill。

@babel/polyfill的改動

@babel/polyfill是一個簡單的包,包含core-js和regenerator-runtime這兩個包。當core-js升級到3.0的版本後,將放棄使用@babel/polyfill,由於它只包含core-js 2.0的版本。

因此在@babel/prest-env升級到7.4.0而且使用core-js@3,須要作以下的替換工做

// 安裝core-js@3.0 和 regenerator-runtime
yarn add core-js@3
yarn add regenerator-runtime


// babel.config.js
presets: [
  ["@babel/preset-env", {
    useBuiltIns: "entry", // or "usage"
    corejs: 3,
  }]
]


// 入口文件index.js
// before
import "@babel/polyfill";

// after
import "core-js/stable";
import "regenerator-runtime/runtime";

複製代碼

@babel/runtime

當使用core-js@3的時候,@babel/transform-runtime會從core-js-pure這個包裏去加載對應的polyfill代碼,core-js-pure裏面的代碼不會污染全局變量,適合第三方庫的開發。

在@babel/transform-runtime的最新版本中,已經支持core-js@3,需做以下操做。

yarn remove @babel/runtime-corejs2
yarn add @babel/runtime-corejs3

//babel.config.js
plugins: [
  ["@babel/transform-runtime", {
    corejs: 3,
  }]
]

複製代碼

改變一

在以前的版本中,@babel/runtime最大的問題就是沒法模擬實例上的方法,好比數組的includes方法就沒法被polyfill。

可是在core-js@3的版本中,全部的實例方法均可以被polyfill了。

array.includes(something)

↓ ↓ ↓ ↓ ↓ ↓

import _includesInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/includes";
_includesInstanceProperty(array).call(array, something);

複製代碼

改變二

core-js@3支持對ECMAScript提案的API進行模擬。

@babel/plugin-transform-runtime的默認配置中,是不會注入對提案的polyfill代碼。若是想要支持提案中的API,只須要增長和@babel/preset-env相似的配置項。

plugins: [
  ["@babel/transform-runtime", {
    corejs: { version: 3, proposals: true },
  }]
]
複製代碼
new Set([1, 2, 3, 2, 1]);
string.matchAll(/something/g);

↓ ↓ ↓ ↓ ↓ ↓

// without proposals flag
import _Set from "@babel/runtime-corejs3/core-js-stable/set";
new _Set([1, 2, 3, 2, 1]);
string.matchAll(/something/g);


// with proposals: true
import _Set from "@babel/runtime-corejs3/core-js/set";
import _matchAllInstanceProperty from "@babel/runtime-corejs3/core-js/instance/match-all";
new _Set([1, 2, 3, 2, 1]);
_matchAllInstanceProperty(string).call(string, /something/g);

複製代碼

展望將來

對老版本瀏覽器的支持

core-js支持儘可能多的瀏覽器和平臺,甚至是IE8-和一些老版本的Firefox瀏覽器。可是支持如此多的低版本瀏覽器,必然會形成polyfill文件變大,增大包的體積。

最大的問題主要來自於一些只支持ES3的瀏覽器,好比IE8-。大多數ES的新特性,都是基於ES5的語法去實現的,這就致使爲了使低版本的瀏覽器可以支持這些新的特性,須要用大量的填充代碼去抹平ES5與ES3的差別。

雖然在某些地區IE8仍是很是流行,可是爲了語言的發展和進步,應該容許某些瀏覽器退出歷史的舞臺。core-js@3已經放棄支持IE6,在下個大版本中,core-js@將再也不支持IE8,只支持那些基於ES5語法的瀏覽器。

ECMAScript 模塊

core-js的模塊都是基於CommonJS規範的。隨着ECMAScript模塊的發佈和普及,core-js應該提供一個ECMAScript模塊規範的版本以供選擇。

更好的優化polyfill的加載問題

在使用@babel/preset-env的useBuiltIns:usage這個配置項是,仍是會存在一些問題。好比當項目的文件沒法進行靜態分析時,須要提供一種方案來進行polyfill的加載。另外一個問題是useBuiltIns:usage可能會在一個文件頭注入數十個core-js的導入語句。當項目中有幾百上千個文件的時候,這些注入的語法會佔據數量客觀的體積。咱們須要一個機制來收集全部須要用到的模塊,並進行去重操做,最後統一注入到項目裏。

對於那些須要支持低版本瀏覽器的開發人員來講,爲了支持IE11這種瀏覽器,polyfill文件的大小會急劇膨脹。一種解決方案是使用type = module / nomodules屬性,生成兩個不一樣的包,一個用來支持現代瀏覽器,一個用來支持低版本的瀏覽器,但這並非一個完美的解決方案;另外一種解決方式是提供一個polyfill的服務,根據請求中的UA來判斷瀏覽器的型號,返回這個瀏覽器須要的polyfill文件,相似的服務有polyfill.io。可是polyfill.io的返回並不許確,可用性不是很高。

others

  • 增長對web標準的支持,好比fetch
  • @babel/runtime提供對目標環境的支持,相似@babel/preset-env中targets字段

連接

core-js

core-js@3, babel and a look into the future

@babel/prest-env 7.4.0 Released: core-js 3, static private methods and partial application

相關文章
相關標籤/搜索