Yarn 的 Plug'n'Play 特性

前言

Yarn 團隊在春節前公佈了 Yarn 2.0 的規劃。其中提到了一個以前沒據說過的名詞 「PnP」。發現 Yarn 的這個功能早在 18 年 9 月份就被提出實現了。因而花了一些時間瞭解了一下它的工做原理以及解決的問題並整理除了本篇文章。html

現狀與痛點

Yarn 團隊開發 PnP 特性最直接的緣由就是現有的依賴管理方式效率過低。引用依賴時慢,安裝依賴時也慢。node

先說說 Node 在處理依賴引用時的邏輯,這個流程會有以下兩種狀況:react

  • 若是咱們傳給 require() 調用的參數是一個核心模塊(例如 "fs"、"path"等)或者是一個本地相對路徑(例如 ./module-a.js/my-li/module-b.js),那麼 Node 會直接使用對應的文件。
  • 若是不是前面描述的狀況,那麼 Node 會開始尋找一個名爲 node_modules 的目錄:
    1. 首先 Node 會在當前目錄尋找 node_modules,若是沒有則到父目錄查找,以此類推直到系統根目錄。
    2. 找到 node_modules 目錄以後,再在該目錄中尋找名爲 moduleName.js 的文件或是名爲 moduleName 的子目錄。

此處旨在說明問題,對 Node 內部模塊解析邏輯作了簡化描述git

可見 Node 在解析依賴時須要進行大量的文件 I/O 操做,效率並不高。github

再來看看安裝依賴時發生了什麼,現階段 yarn install 操做會執行如下 4 個步驟:npm

  1. 將依賴包的版本區間解析爲某個具體的版本號
  2. 下載對應版本依賴的 tar 包到本地離線鏡像
  3. 將依賴從離線鏡像解壓到本地緩存
  4. 將依賴從緩存拷貝到當前目錄的 node_modules 目錄

其中第 4 步一樣涉及大量的文件 I/O,致使安裝依賴時效率不高(尤爲是在 CI 環境,每次都須要安裝所有依賴)。json

Facebook 的工程師受夠了這些問題決定尋找一個能完全解決問題同時還能夠與現有生態兼容的解決方案。這即是 Plug'n'Play 特性,簡稱 PnP。它已在 Facebook 內部測試了一段時間,如今 Yarn 團隊決定與社區分享並共同優化該方案。api

實現方案

PnP 的具體工做原理是,做爲把依賴從緩存拷貝到 node_modules 的替代方案,Yarn 會維護一張靜態映射表,該表中包含了如下信息:緩存

  • 當前依賴樹中包含了哪些依賴包的哪些版本
  • 這些依賴包是如何互相關聯的
  • 這些依賴包在文件系統中的具體位置

這個映射表在 Yarn 的 PnP 實現中對應項目目錄中的 .pnp.js 文件。bash

這個 .pnp.js 文件是如何生成,Yarn 又是如何利用它的呢?

在安裝依賴時,在第 3 步完成以後,Yarn 並不會拷貝依賴到 node_modules 目錄,而是會在 .pnp.js 中記錄下該依賴在緩存中的具體位置。這樣就避免了大量的 I/O 操做同時項目目錄也不會有 node_modules 目錄生成。

同時 .pnp.js 還包含了一個特殊的 resolver,Yarn 會利用這個特殊的 resolver 來處理 require() 請求,該 resolver 會根據 .pnp.js 文件中包含的靜態映射表直接肯定依賴在文件系統中的具體位置,從而避免了現有實如今處理依賴引用時的 I/O 操做。

帶來了哪些好處

從 PnP 的實現方案能夠看出,同一個系統上不一樣項目引用的相同依賴的相同版本實際都是指向的緩存中的同一個目錄。這帶來了幾個最直觀的好處:

  • 安裝依賴的速度獲得了空前的提高
  • CI 環境中多個 CI 實例能夠共享同一份緩存
  • 同一個系統中的多個項目再也不須要佔用多份磁盤空間

如何開始使用 Plug'n'Play 特性?

首先你須要 Yarn 1.12+ 版本。而後根據你的具體場景能夠選擇:

使用 create-react-app 建立項目時開啓 PnP

create-react-app 已經集成了對 PnP 的支持。只需在建立項目時添加 --use-pnp 參數便可。

npx create-react-app testapp --use-pnp 
複製代碼

在已有項目中開啓 PnP

只需在項目中執行:

yarn --pnp
複製代碼

便可開啓 PnP 特性。

注意事項

pkg.installConfig 字段

在項目中開啓 PnP 特性後,Yarn 會在 package.json 文件中建立一個 installConfig 字段:

{
  "installConfig": {
    "pnp": true
  }
}
複製代碼

只要 installConfig.pnp 的值是一個真值且當前版本的 Yarn 支持,PnP 特性就會被啓用。

執行 npm script 或是運行 .js 文件

因爲在開啓了 PnP 的項目中再也不有 node_modules 目錄,全部的依賴引用都必須由 .pnp.js 中的 resolver 處理。所以不管是執行 script 仍是用 node 直接執行一個 JS 文件,都必須經由 Yarn 處理。必須經過 yarn run 或是 yarn node 執行。

在項目中調試依賴

在開發過程當中咱們有時會直接修改 node_modules 目錄下的依賴來調試。但在 PnP 模式下,因爲依賴都指向了全局緩存,咱們再也不能夠直接修改這些依賴。

針對這種場景,Yarn 提供了 yarn unplug packageName 來將某個指定依賴拷貝到項目中的 .pnp/unplugged 目錄下。以後 .pnp.js 中的 resolver 就會自動加載這個 unplug 的版本。

調試完畢後,再執行 yarn unplug --clear packageName 可移除本地 .pnp/unplugged 中的對應依賴。

總結

目前 PnP 仍是一個相對比較新的特性,你們能夠嘗試在本地開發環境中啓用 PnP 來感覺一下它帶來的全新體驗。遇到問題能夠及時反饋到 Yarn 的 issue 列表中

參考連接:

更多文章,請關注咱們團隊的公衆號:全棧探索

二維碼
相關文章
相關標籤/搜索