JavaScript 弱依賴項的使用場景

英文:Lea Verou,翻譯:前端大全 / 1bite前端

今天早上,我在構思寫一個庫來包裝及增強 querySelectorAll 時忽然想到,相比直接引入 Parsel,更好的作法是先檢測它是否已經加載,若是已經加載,就用它來作解析;若是沒有加載,就用本身手擼的正則表達式作解析(反正根據我這個庫要作的事情來看,這個方案足以覆蓋大部分場景)。正則表達式

之前,因爲每一個庫都會加載到全局名字空間中,因此用如下代碼就能搞定:緩存

if (window.Parsel) {
  let ast = Parsel.parse();
  // 根據AST能夠正確的重寫選擇器
}
else {
  // 正則表達式方案
}

然而,在 ESM 模塊系統裏,彷佛沒有辦法檢測某個庫是否已經導入過了,除非你本身的代碼顯式導入過。網絡

爲這事我還專門發過一條推:框架

ESM 模塊系統(及其它模塊系統)的一個缺點是,你沒法指定無關緊要的依賴項。編輯器

使用全局名字空間,你能夠先檢查某個全局變量是否存在,而後走相應的分支。而使用 ESM 模塊,就沒法檢測某個庫是否已經導入過,除非你已經在某處導入過。ide

— Lea Verou (@LeaVerou) 2020/11/19性能

我覺得這個情形很常見,你們都應該能理解這種作法的好處。然而,當我發現網上有很多人並不理解我要幹啥時,我仍是很驚訝的。他們中大部分人覺得我想作的是模塊條件導入或者模塊導入失敗後的錯誤恢復。測試

我想了一下,多是由於我是從庫做者的角度思考如何寫 JS 的。做爲一個庫做者,我是沒法控制宿主環境的。但對於大多數開發者而言,他們是以開發某個具體的 APP 或者網站的角度去思考如何寫 JS 的。網站

在 Kyle Simpson 要求我詳細闡述使用場景後,就有了這篇文章。

我描述的情形本質上是一種 「漸進式加強」(實際上,我曾經還想過把這篇博文取名爲 「JS 之漸進式加強」)。若庫 X 已經被其它代碼加載了,則用它完成更復雜但覆蓋了全部邊界狀況的功能;不然只保留一些基本功能。這套方案針對的是那些本身的代碼不真正「依賴」的依賴項,也就是那些錦上添花的依賴項。

咱們常常會看到這樣的現象,有些模塊功能雖然很是完備,但就是使用了一堆依賴項。把這樣的庫添加到即便是最簡單的項目中,也會讓項目忽然變得很龐大。究其緣由,是這些庫須要考慮各類意外狀況,而咱們的項目可能並不關心這些邊界狀況,甚至這些邊界狀況都不會出如今咱們項目中。咱們也常常看到另一種現象,有些模塊雖然是零依賴的,但又重造了不少現有的輪子,或者乾脆缺失某些功能。

而我提出的這種範式,則兼具上述兩個優勢:零依賴(或者少依賴),又能利用系統中已經導入的模塊增強本身尚未反作用。

使用這種範式,依賴項的大小就再也不是個問題,由於它們如今是無關緊要的同版本依賴,今後你能夠盡情挑選合適的庫,不再用被包體積束手束腳。並且,同時用多個也能夠!不止一個庫能助你實現需求,若是系統中已經存在一些更龐大、更完備的庫,就直接用它們;而若是它們尚未加載,就回退至那些微型庫。

再舉幾個場景:

  • 若是系統中已存在 Prism , Markdown 轉 HTML 的轉換器就能夠支持代碼語法高亮,甚至支持多個語法高亮器。

  • 若是系統中已存在 Icrementable ,代碼編輯器就能夠用它實現箭頭鍵控制數值自增。

  • 若是系統中已存在 Dragula ,模板庫就能夠用它來實現列表條目拖拽排序。

  • 若是系統中已存在 Tippy ,測試框架就能夠用它來實現更友好的提示信息彈出框。

  • 若是已經加載了某個能計算代碼體積的庫,代碼編輯器就能夠用它來顯示代碼體積(以 KB 爲單位);若是 gzip 庫能用,則這個代碼編輯器能夠用它來顯示經 gzip 壓縮事後的代碼體積。

  • 若是能用某自定義元素,UI 庫就能夠先嚐試使用該自定義元素,不然,就使用功能相近的原生元素(好比某個超炫酷日期選擇器 vs <input type="date">)。又好比,系統中已存在 Awesomplete ,則能夠用它來實現自動補全,不然就使用簡單的 。

  • 若是系統中已存在某日期格式化庫,那麼咱們的代碼就能夠用它來格式化日期;不然就使用 Intl.DateTimeFormat。

這個模式甚至能夠與條件加載相結合,例如:咱們能夠檢查全部已知的語法高亮器,若是都沒有加載,再加載 Prism。

回顧一下,這個模式優點主要體如今如下方面:

* 效率方面:好比使用網絡加載模塊,而 HTTP 請求代價很高的時候;好比直接打包到包裏,又會增長包體積的時候。就算包體積不是問題,若是在不須要的時候走雖然周全但相對較慢的路徑,也會影響運行時性能,由於這時簡單的邏輯就夠用了。

* 選擇性方面:比起一個功能就選用一個庫,如今能夠支持多庫備用。例如:多款語法高亮器,多款 Markdown 語法解析器等等。若是你要完成的功能必定得要某個庫,也能夠在其它能支持的庫都沒有加載的狀況下再加載它。

弱依賴是反模式嗎?
這篇文章發佈後,我收到了一些這樣的反饋:「弱依賴是一種反模式,由於你沒法預測使用了這種模式的模塊的行爲。若是你引入了某個庫,又不想其它庫使用它,這時該怎麼辦?這種狀況下,使用參數注入來顯式提供這些庫的引用會更好。」

對此,我有幾點不一樣的見解。

首先,若是弱依賴項運用得當,它們只會被用來增強缺省/基礎行爲,因此不太可能不使用弱依賴項而回退到缺省行爲。

其次,弱依賴項與參數注入的方式並不衝突。它們能夠一塊兒使用,並互相完善。好比弱依賴項能夠用來選擇更合理的缺省庫,而後再使用參數注入方式作進一步調整(或者徹底禁用)。只保留參數注入會給使用庫帶來高昂的前期認知成本(參考 約定優於配置)。好的 API 讓複雜的事變簡單,讓簡單的事變容易。 常見的例子是,若是加載了某語法高亮模塊,你確定但願用它來作語法高亮;若是加載了某解析器模塊,你確定是首選它作解析,而不會選擇正則表達式。而那些不太常見的邊界情形,好比你不想作語法高亮或者想用另一個解析器模塊,仍然能用參數注入方式實現,但並不表明其它方式就不能實現。

最後,最終開發者可能並不知道已經加載了全部庫,也就是說,開發者徹底有可能由於別的緣由引入了某個庫卻渾然不知。而弱依賴項模式是有能用的庫就用,沒有就不用,因此不存在引入多餘庫或者須要提早準備好相關庫的這類問題。

這種模式如何與 ESM 兼容?
仍是有人(大部分爲庫做者)很是理解我提的問題,他們也提出了一些方案。

方案1: 在底層實現一個全局模塊緩存,而 CJS 就自帶這種東西。

CommonJS 就暴露了緩存 … 也許 ESM 也能夠作到,緩存失效也容易作,代碼覆蓋率測試也不難 … 不過個人測試代碼全是 CJS 的,不太想改

相關文章
相關標籤/搜索