在 JavaScript 社區中,工程師們互相分享成千上萬的代碼,幫助我們節省大量編寫基礎組件、類庫或框架的時間。每個代碼包可能都依賴於其他代碼,而代碼間的依賴關係則由包管理器負責維護。目前最流行的 JavaScript 包管理器是 npm 客戶端,在 npm 倉庫中提供了多達 30 萬的軟體包。據統計,已有超過 500 萬的工程師使用 npm 倉庫,其軟體包下載量達到了 50 億次/月。
在 Facebook 中,我們多年來一直在使用 npm 客戶端並取得了成功,但隨著代碼倉庫與團隊人數的增長,我們在一致性、安全性以及性能方面遇到了挑戰。在嘗試解決每個方面的問題後,我們最終決定著手打造一套新的客戶端解決方案,以幫助我們更可靠地管理依賴。我們把這個客戶端工具稱為Yarn —— 更加快速、可靠、安全的 npm 客戶端的替代品。
我們在此榮幸地宣布,我們與 Exponent、 Google 和 Tilde 進行了合作,並開源 Yarn 項目。工程師在使用 Yarn 時,依然需要訪問 npm 倉庫,但 Yarn 能夠更快速地安裝軟體包和管理依賴關係,並且可以在跨機器或者無網絡的安全環境中保持代碼的一致性。Yarn 提高了開發效率,並解決了共享代碼時面臨的一些問題,使得工程師們可以專注在構建新產品以及新特性上。
JavaScript 包管理方式在 Facebook 的演變在包管理工具出現之前,JavaScript 工程師們通常依賴的項目並不多,因此會把依賴直接存儲在工程目錄或上傳到 CDN 上。在 Node.js 出現後不久,第一個主流的 JavaScript 包管理工具 npm 被引入進來,並很快成為了最受歡迎的包管理工具之一。從此,新的開源項目不斷湧現,工程師們比起以前更加樂於分享代碼了。
在 Facebook 中,我們有很多項目都要依賴 npm 倉庫上的代碼,比如 React。但隨著內部規模的擴大,我們面臨著以下挑戰:在跨平臺與跨用戶之間安裝依賴時的代碼一致性問題、在安裝依賴時花費太長時間、以及 npm 客戶端自動執行某些依賴庫的代碼所導致的安全性問題。我們嘗試過尋找這些問題的解決方案,但在這個過程中通常又會引起一些新的問題。
嘗試修改 npm 客戶端在開始階段,我們遵循了最佳實踐,在代碼倉庫中只跟蹤了 package.json 文件的變化,並要求工程師手動運行 npm install 命令安裝依賴。這種模式在開發人員的電腦上沒有問題,但在持續集成環境中遇到了困難,因為出於安全與可靠性的考慮,持續集成環境需要進行沙箱隔離,不能進行聯網,因此也無法安裝依賴。
接下來,我們嘗試在代碼倉庫中跟蹤整個 node_modules 目錄的文件變化。雖然這種方式有效,卻使得一些簡單操作變得複雜化了。比如,對 babel 更新一個次要版本號時,會產生多達 800,000 行的提交記錄,此外由於 lint 規則的存在,引起無效的 utf-8 字節序列、windows 換行符、非 png 壓縮圖片等問題時,將會導致工程師經常需要花費一整天的時間合併 node_modules 目錄的文件。而我們負責源碼控制的團隊也指出,跟蹤 node_modules 目錄會引入過多的元數據。比如 React Native 的package.json 文件目前只列出了68項依賴,但在運行 npm install 後,node_modules 目錄整整包含了 121,358 個文件。
最後,為了有效組織 Facebook 逐漸增長的工程師人數以及管理需要安裝的代碼量,我們嘗試修改npm 客戶端。我們決定壓縮整個 node_modules 目錄,並上傳到內部 CDN,然後我們的工程師與持續集成系統都能從 CDN 上下載並解壓文件,從而保證了代碼一致性。這樣我們就可以從源碼控制系統中刪除數以萬計的文件了,但不足之處是工程師現在不僅在拉代碼時需要聯網了,構建也同樣需要聯網。
我們還試圖為 npm 的 shrinkwrap 功能尋求優化方案,這個工具是用來鎖定依賴版本號的。但Shrinkwrap 功能的文件默認不會生成,如果開發者忘記了生成這一步驟,文件就不會被同步更新,因此我們編寫了一個工具,以確定 Shrinkwrap 的文件內容和 node_modules 目錄中的文件相符。這些文件由大量的 JSON 塊組成,並且鍵名是無序的,因此每次更改通常會導致 Shrinkwrap 文件的內容大幅變化,難以進行代碼審查。為減緩這一問題,我們還需要藉助一個額外的腳本,對所有條目進行排序。
最後,通過 npm 升級單個依賴包時,基於 語義化版本號 規則,npm 通常會連同其他無關依賴一起更新。這使得每次更新都會比預期產生更多的變化,工程師們認為這樣把 node_modules 提交上傳到 CDN 的過程,難以達到預期的效果。
構建新客戶端與其圍繞 npm 客戶端繼續構建基礎設施,不如從整體上再次回顧這些問題。倫敦辦公室的 Sebastian McKenzie 提出,如果我們建立一個新客戶端工具以代替 npm 客戶端,從而解決我們的核心問題呢?這一構思很快得到了我們的認同,團隊對於這個主意也感到非常興奮。
在開發過程中,我們與業界的工程師們進行了交流討論,發現他們也面臨著類似的問題,也嘗試過許多類似的解決方案,通常只能把這些問題逐一解決。很明顯,有必要把整個 JavaScript 社區正在面臨的問題集合起來,然後我們就可以開發一個主流的解決方案了。在此感謝 Exponent、 Google 與 Tilde 的工程師們的協助,我們共同建立了 Yarn 客戶端,並在每一個主流 JS 框架以及 Facebook 外的使用場景中測試驗證了 Yarn 的性能。今天(2016-10-11),我們很榮幸把這個工具開源分享到社區中。
介紹 YarnYarn 是一個新的包管理器,用於替代現有的 npm 客戶端或者其他兼容 npm 倉庫的包管理工具。Yarn 保留了現有工作流的特性,優點是更快、更安全、更可靠。
任何包管理器的主要功能都是安裝某些軟體包,軟體包即用於特定功能的某段代碼,通常是從一個全局的倉庫安裝到工程師的本地環境。每個軟體包可以依賴於其他包,也可以不依賴。一個典型的項目結構的依賴樹通常會包含數十個、數百個甚至上千個軟體包。
這些依賴包通常是帶版本號的,通過語義化版本控制(semver)安裝。Semver 定義的版本號反映了每個新版本更改的類型,到底是進行了不兼容的API改動(MAJOR),還是添加了向後兼容的新特性(MINOR),還是進行了向後兼容的 bug 修復(PATCH)。然而,semver 依賴於軟體包的開發者不能犯錯誤——如果依賴關係沒有加鎖,可能會引入一些破壞性更改或者產生新的 bug。
結構在 Node 生態系統中,依賴通常安裝在項目的 node_modules 文件夾中。然而,這個文件的結構和實際依賴樹可能有所區別,因為重複的依賴可以合併到一起。npm 客戶端把依賴安裝到 node_modules目錄的過程具有不確定性。這意味著當依賴的安裝順序不同時,node_modules 目錄的結構可能會發生變化。這種差異可能會導致類似「我的機子上可以運行,別的機子不行」的情況,並且通常要花費大量時間定位與解決。
Yarn 通過 lockfiles 文件以及一個確定性的、可靠的安裝算法,解決了版本問題和 npm 的不確定性問題。Lockfile 文件把安裝的軟體包版本鎖定在某個特定版本,並保證 node_modules 目錄在所有機器上的安裝結果都是相同的。Lockfile 還使用簡潔的有序鍵名的格式,保證了每次的文件變化最小化,進行代碼審查也更為簡單。
安裝過程分為以下三個步驟:
處理: Yarn 通過向代碼倉庫發送請求,並遞歸查找每個依賴項,從而解決依賴關係。
抓取: 接下來,Yarn 會查找全局的緩存目錄,檢查所需的軟體包是否已被下載。如果沒有,Yarn 會抓取對應的壓縮包,並放置在全局的緩存目錄中,因此 Yarn 支持離線安裝,同一個安裝包不需要下載多次。依賴也可以通過 tarball 的壓縮形式放置在源碼控制系統中,以支持完整的離線安裝。
生成: 最後,Yarn 從全局緩存中把需要用到的所有文件複製到本地的 node_modules 目錄中。
通過清晰地細分這些步驟,以及確定性的算法支持,使得 Yarn 支持並行操作,從而最大化地利用資源,並加速安裝進程。在一些 Facebook 的項目上,Yarn 甚至可以把安裝過程降低一個數量級,從幾分鐘到只需幾秒鐘。Yarn 還使用了互斥鎖,以確保多個 CLI 實例同時運行時不會互相衝突與影響。
縱觀整個過程,Yarn 對於軟體包安裝加上了嚴格的限制。你可以對哪個生命周期腳本作用於哪個軟體包進行控制。軟體包的 checksum 也會存儲在 lockfile 中,以確保每一次安裝都可以得到同一個包。
特性Yarn 除了讓安裝過程變得更快與更可靠,還添加了一些額外的特性,從而進一步簡化依賴管理的工作流。
Yarn 用於生產環境我們已經在 Facebook 中把 Yarn 用於生產環境,並且效果非常理想。Yarn 有效地管理了許多 JavaScript 項目的包依賴關係。在每次遷移時,構建都可以離線進行,因此加速了工作流程。我們基於 React Native 在不同條件下進行安裝時間測試,比較了 Yarn 與 npm 的性能,具體參見這裡。
起步最簡單的起步方法是:
npm install -g yarnpkgyarn
yarn CLI 代替了原有開發工作流中 npm CLI 的作用,用法可能是單純的替代,也可能是一個新的、相似的命令:
npm install → yarn
不需要帶參數,yarn 命令會讀取 package.json 文件,然後從 npm 倉庫中抓取軟體包,並放置到 node_modules 目錄中。等價於運行 npm install。
npm install --save <name> → yarn add <name>
我們避免了 npm install <name> 命令中安裝「不可見的依賴」的行為,並分離出一個新命令。運行 yarn add <name> 等價於運行 npm install --save <name>。
未來目前已經有許多成員一起參與到 Yarn 的構建中,以解決我們的共同問題,我們也希望 Yarn 未來能真正成為一個大眾化的社區項目。Yarn 目前已經 在 GitHub 開源 ,我們也已經準備好向 Node 社區進行推廣:使用 Yarn、分享構思、編寫文檔、互相支持,並幫助構建一個很棒的社區來進行長期維護。我們相信 Yarn 已經擁有一個良好的開局,如果有你的幫助,Yarn 的未來將會更加美好。
原文連結 : Yarn: A new package manager for JavaScript
稿源:達仔blog