NPM 與前端包管理

 

咱們很清楚,前端資源及其依賴管理一直是 npm 的重度使用場景,同時這也一直是 Node.js 普及的重要推進力。但這類應用場景到底有多重度?這是一個很難回答的問題。這份 「npm 最常下載的包的清單」 並不能提供有效的證據:由於像 async、minimist 和 request 這樣的包就像是 「生活必需品」,它們會被數以千計的其它包所 依賴,這樣一來它們固然會隨着那些依賴它們的包一塊兒被不停地下載。前端

 

更有意義也更接近真相的問題是:哪些包是人們主動安裝的?所謂 「主動安裝」,就是指某我的(或某個機器人)以實際運行 npm install thispackage 命令的方式來安裝一個包。不久前,咱們開始把日誌數據加入到Jut 中,隨後咱們終於能夠方便而快速地給出這個問題的答案了。最終,咱們獲得了 「最常主動安裝的 npm 包五十強榜單」,這份榜單畫風突變,頗有意思。在五十強中有 32% 的包(它們產生了 50% 的實際下載量)都是前端的工具或框架,攜 Grunt、Bower 和 Gulp 一塊兒遙遙領先(固然移動端也是一大重度應用,這裏暫且不表)。此外,這些包的使用量也在穩步增加:node

 

(客戶端工具的增加,2014 年 1 月~10 月)web

 

另外一個渠道也佐證了前端是重度使用場景的這一事實——咱們從 npm 用戶和 web 開發者那裏收到了大量關於如何用 npm 來管理好客戶端依賴的提問(和故障反饋)。這些問題一般都伴有極其主觀的偏見,令咱們感到至關詫異。好吧,那就讓咱們嚴肅認真地來澄清一下:數據庫

 

1. 「npm 只是爲 CommonJS 服務的!」npm

 

不對。npm 但願成爲 JavaScript 的包管理器,所以,只要是跟 JavaScript 相關的,都適合放入 npm 的包倉庫(registry)。雖然 Node.js 提供了一個 「CommonJS 式」 的模塊環境,但 npm 對此並不關心。json

 

2. 「npm 只是爲服務器端的 JavaScript 服務的!」api

 

一樣不對。你的包能夠包含任何內容,不管是 ES六、客戶端 JS,仍是 HTML 和 CSS。有不少東西天生就是跟 JavaScript 綁在一塊兒的,那就把它們都放進來吧。瀏覽器

 

npm 的 《行爲準則》 總結了一份很是簡短的列表,列出了咱們認爲不適合放進包裏的東西(簡單來講:不要把 npm 看成你的數據庫或多媒體服務器來用)。對此若有疑問,請經過 Twitter 或 Email 詢問,咱們樂於討論。緩存

 

npm 的哲學服務器

 

npm 的願景是幫助開發者減小摩擦。咱們傾向於經過 「循蹤闢徑」 的方式來實現這一點。這句話的意思是說:咱們不但願告訴用戶該怎麼作;咱們但願觀察用戶是怎麼作的,而後把障礙掃清。若是不少人都是在以各自不一樣的方式在行事,那咱們不會輕易地從中挑出一個勝者,除非最佳實踐已經昭然若揭。

 

那麼,在前端包管理的領域中,用戶遇到的阻力究竟在哪裏?用戶踩出的 「蹤」 又是怎樣的?

 

前端痛點

 

除了 GitHub issue 以及 IRC、Twitter、技術會議和線下聚會中的用戶之外,咱們還會跟一些大型前端包的開發者們直接對話——這其中包括 Angular 和 Ember 的開發者(這二者都位列五十強)。他們在解決方案上並不徹底一致,但他們的痛點倒是大致相同的。接下來咱們會一一展開,並討論如何攻克這些難題:

 

1. node_modules 目錄並非按照前端包所須要的方式來組織的

 

這是一個很是明顯的問題。node_modules 目錄是默認狀況下 npm 存放包的地方,它得名於 Node.js 的模塊加載行爲。根據你安裝的包的具體狀況,全部包最終會被存放在目錄樹的不一樣位置。這對於 Node 來講一切良好,但對於 HTML 和 CSS 來講,無論怎樣,咱們一般都指望全部東西能夠彙總在同一個地方,好比/static/mypackage 這樣的目錄下。確定有一些變通方法能夠繞過這個問題,但還算不上是最佳方案。

 

2. 前端依賴在解決衝突方面具備大相徑庭的需求

 

Node 模塊加載器的一個有意思的地方在於,它容許你同時使用同一個模塊的多個不兼容版本;而 npm 的一大有意思的地方在於,它能夠將包的這些不一樣版本放置在合適的地方,從而作到在想要的地方加載想要的版本。這種方式對於避免 「依賴地獄」 有很大幫助,同時這也是 Node 的 「大量小模塊」 的實踐模式如此實用且流行的緣由之一。

 

但前端依賴倒是沒法以這樣的方式來運做的。若是你在網頁中同時加載兩個版本的 jQuery,那其中只有一個會 「勝出」。若是你同時加載了兩個版本的 Bootstrap CSS 框架,它們會同時起做用,而後把頁面樣式搞得一團糟。在將來,HTML 將得到新的特性(好比 web components 和 Shadow DOM),也許有助於解決這類問題;但在眼下,前端依賴會發生衝突。那咱們如何優雅地判別並解決這個難題呢?

 

3. 同時維護多個包清單是很煩人的

 

前兩個問題其實已經有了一種解決方案,就是爲前端包額外配備其它的包管理方案。但這會產生這樣一種局面——單個項目可能會同時包含一個 package.json 文件、一個 bower.json、一個 component.json 等等。每當遇到哪怕是一丁點兒更新時,你都要把全部這些配置文件統統編輯一遍。跟全部的數據冗餘同樣,這種情形不只煩人,並且容易產生錯誤。

 

4. 找到兼容瀏覽器的包很痛苦

 

npm 是爲 JavaScript 服務的包倉庫,但目前庫中絕大多數的包都是 Node.js 包。在採用 Browserify 等工具作過適配以後,某些模塊是能夠在客戶端運行的,但還有不少仍然是不行的。目前,若是要判斷某個包是否在瀏覽器端可用,除了實測,彷佛尚未一種簡單易行的方法。

 

前端解決方案

 

在找出了以上四個難題以後,讓咱們來逐一討論如何解決。

 

上面提到的最後一個難題是最容易克服的,咱們已經開始爲解決方案奠基基礎了。這個解決方案就是:生態圈。

 

生態圈是指包倉庫的一些可搜索的子集,這些子集是經過程序化地篩選庫中的全部包而產生的,篩選條件是諸如 「可在瀏覽器中運行」 或 「可在 Windows 上運行」 或 「兼容 Express」 等數以百萬計種可能性。此功能一旦上線,必將會有一個叫做 「兼容 Browserify」 的生態圈,而其它名稱好比 「對客戶端友好」 也確定會出現。這將是一個很是棒的解決方案,咱們對此很是樂觀。接下來,讓咱們着手處理剩下的三個難題。

 

客戶端的包安裝與依賴解析

 

第三個問題——多套包管理系統——其實是前兩個問題的反作用。如今已經有一些第三方工具試圖緩解客戶端的包安裝和依賴解析問題,它們一般須要創建各自獨立的包倉庫和配置文件格式。這類解決方案層出不窮,每一種解決方案都有其長處和短處。不過,從上面的統計數據中能夠看出,目前爲止,在這方面最流行的解決方案是 Bower。那麼接下來,請容許咱們暫時忽略其它優秀的包管理器,重點關注一下 Bower 是如何工做的。

 

Bower 的解決方案

 

Bower 能夠經過名稱來安裝包,也能夠經過 Git URL 或任意 HTTP URL 來安裝,這些都跟 npm 是同樣的。但跟 npm 不一樣的是,Bower 會把每一個包都安裝到 bower_components 目錄下的獨立目錄中,整個目錄結構是扁平的。舉例來講,若是 backbone 依賴 underscore,那麼 bower install backbone 將會把 backbone 和 underscore 這二者都放置在 bower_components 目錄下。這意味着,從一個 web 應用中引用一個組件是很是簡單的,由於它老是會被安裝在相同的地方——這跟 npm 不一樣,由於 npm 包的實際安裝路徑並不固定。

 

扁平的目錄結構存在一個問題,若是你試圖安裝同一個庫的兩個不兼容版本(好比 jQuery 的 1.11.1 版和 2.1.1 版)時,它們將會被安裝到相同的位置,併發生衝突。若是發生了這種狀況,Bower 會要求你手工選擇哪一個版本是你想要的,而且能夠決定是否把此次選擇的結果保存到 bower.json 文件中。這個過程存在不肯定因素,它依賴人工干預,所以兩我的在安裝相同的依賴包時可能會得出不一樣的安裝結果。不過一旦你把你的選擇結果保存到了 bower.json 中,就不存在變數了——任何人在安裝你的項目時都會獲得相同的安裝結果。

 

這種體驗沒有 Node 環境那麼好,由於後者遇到的版本衝突能夠在無需人工干預的狀況下自動解決。總的來講,它照顧到了前端開發者的關注點,並且它確實也幹得挺不錯的。

 

如今還沒法選出勝者,但咱們仍是想減小摩擦

 

咱們並不想操之過急。儘管 Bower 已經十分流行了,但眼下仍然還有很多其它的包管理方案可用。同時,瀏覽器也在持續地快速演進,所以咱們認爲,如今就對前端包管理方案下結論還爲時過早。正是基於這種考量,咱們不久前在《npm 命令行界面(CLI)線路圖》一文中提出瞭如下重要策略。

 

咱們計劃把 npm CLI 模塊化,將其設計爲各個分離的部件。這些部件不只做爲 npm 客戶端的一部分而存在,還能夠獨立地被程序所調用。底層的目標是令其餘人能夠在 npm 這個基礎之上編寫工具——若是 npm 中已有對他們有用的部件,那他們就能夠重用;若是沒有,他們也能夠自行實現本身的解決方案。實現這個目標的方法,並非把 npm 改形成配置選項、開關、生命週期鉤子所組成的一坨大雜燴,而是將其模塊化。

 

模塊化 CLI 的完整設計還未定稿,但顯然會包含如下幾大部件:

 

  1. 一個用來從包倉庫中下載包的 API

  2. 一個能夠在本地存儲、讀取而且解壓縮的 「緩存」 API

  3. 一個安裝器 API,能夠把包放置到你的項目中的合適位置

 

咱們應該已經說得很是清楚了,相信任何前端包管理器都想用上第 1 和第 2 條,而後從新實現第 3 條。

 

使用 npm 來構建你本身的前端包管理系統

 

若是你打算在今天構建一個理想的前端包管理系統,那它會是什麼樣子的呢?

 

中期來看,咱們所能想像到的官戶端包管理系統將是這個樣子的:

 

1. 別去運營你本身的包倉庫了,直接用咱們的

 

這並不只是自私自利:除了咱們以外,還有一些人在運營着本身的包倉庫,但他們給咱們的反饋都是不再想繼續下去了。維持包倉庫的穩定、高效、以及必要的客戶支持都是十分昂貴、困難和耗費時間的。並且從任何意義上來講,「託管包」 都不是客戶端包管理器想要解決的問題。若是包是跟 JavaScript 相關的,那就託管到 npm 吧。一旦生態圈功能上線以後,就能夠經過它來在全局庫中建立 「微型庫」,經過自定義搜索的索引來充實其內容,並顯示其特徵。(譯註:我其實不肯定後半句在說什麼。)

 

2. 採用 package.json 做爲配置文件

 

若是你的工具須要一些配置信息才能工做,那就把它放進 package.json 文件中吧。彷佛未經詢問就這樣作稍顯粗魯,但咱們在此發出邀請:但作無妨。npm 的包倉庫是一個無模式限制的(schemaless)存儲空間,所以你添加的每一個字段都具備和其它字段同樣的地位,咱們既不會清除這些新字段,也不會由於存在新字段而報錯(只要新字段沒有跟現有的字段衝突就行)。

 

咱們也意識到這可能會帶來一種風險,產生一堆互不兼容的配置信息,所以,請適度使用:千萬要抵禦住誘惑,不要試圖搶佔一些通用的字段名,好比 "assets" 或 "frontend" 等等。用一個特定的、表明你的應用的標籤就好,好比 "mymanager-assets" 或 "mymanager-scripts"。在將來,若是咱們決定更加明確地支持你的功能,併爲你分配一個通用字段,那也是很容易實現對舊字段名的向後兼容的。

 

3. 採用咱們的緩存模塊

 

在規模化的狀況下,解壓縮、存儲並緩存包實際上是一個很是複雜的問題。所以,若是你是在使用咱們的包倉庫的話,那麼一旦緩存模塊可用,你就應該當即用上它。它將會節省你的精力、時間和帶寬。

 

4. 編寫你本身的前端包行爲

 

你的使用場景確定跟 npm 以 Node 爲中心的行爲截然不同,所以這是惟一一塊你須要本身搞定的部分。即使如此,咱們仍是會提供一些順手的模塊來幫助你。你能夠作到和 Bower 同樣的效果,好比把前端包下載並安裝到一個徹底不一樣的目錄中,而後自行處理依賴關係。或者你可讓 npm 把全部東西都安裝到node_modules 目錄中,而後利用一個 post-install 腳本或一個運行時鉤子來解析依賴,或以上策略的某種組合。咱們不肯定哪條路是最佳選擇,這也是咱們鼓勵你們在此深刻探索的緣由。

 

我何時能夠開始動手?

 

一旦咱們講清楚了這個計劃以後,接下來每一個人都會問出這個問題。咱們只能說:多是明年(譯註:2015 年)的某個時候。將 npm 改形成上述效果所須要的工做早已啓動了,但 npm 公司的首要任務是得先讓本身成爲一個自給自足的實體,這也是爲何咱們會在 2015 年早期專一於發佈 私有包 服務。在此以後,咱們的下一個專一點應該就是擴展包倉庫自身的實用性了,屆時將是客戶端包管理功能的登場之時。

 

我如今能夠作什麼?

 

咱們將對此提供支持,這確實沒錯,但這個問題如今就橫在你的面前啊!那你眼下能夠作些什麼呢?

 

1. 使用咱們的包倉庫

 

沒有理由不這麼作。它很快,它的可用性高達 99.99%,並且它對開源項目是(而且永遠都將是)免費的。

 

2. 採用 package.json 做爲配置文件

 

一樣,沒有理由不這麼作。它是你的包,就用你想要的方式來描述它吧。要注意避免數據重複(不要另外弄出一個你本身的 "name" 字段),而且避免通用的字段名,除此之外,你就放手去作吧。若是你發覺本身對package.json 的使用方式有些怪異或複雜,隨時能夠經過 IRC、Twitter 和 Email 找到咱們——若是你想先跟咱們通個氣的話。

 

3. 給你的包打標籤

 

目前 npm 的 "keywords" 字段在某種程度上利用得還不夠,其實它能夠用來清晰地聲明包與某個生態圈的從屬關係或兼容性,即便這個生態圈還不存在也不要緊。舉個例子,若是我給一個包打上 「ecosystem:hapi」 的標籤,那你就能夠用這個標籤搜到它了。這種方式明顯不能像一個真正的生態圈那樣好用,由於它不具有(未來生態圈功能將會提供的)自動的驗證機制,但這總比模糊不清的關鍵字要好。

 

4. 使用生命週期腳本,以及 Browserify

 

使用 生命週期腳本 來管理那些經過 npm 安裝的客戶端資源,並非一個完美的解決方案,但咱們認爲這個方向值得探索。好比說,你能夠設置一個 "postinstall" 腳本,用來把 npm 安裝的包移動到一個扁平的目錄結構中,並處理依賴關係。這種方式確定不夠完美,但若是你把它做爲救命稻草來用,咱們會樂於關注你在這條路上能走多遠,而你的痛點也將爲咱們接下來的行動帶來啓發。

 

咱們還認爲 Browserify 是很是棒的工具,但遠沒有獲得充分利用。若是在安裝時把它做爲一個端到端的解決方案來使用,將是一個很是有創意的想法。(請查閱 Browserify 的 舒適手冊,那裏有很是棒的文檔,會告訴你如何用好它。)

 

請再堅持一下

 

前端開發者但願再也不同時使用多個包管理器。包倉庫的運營者們也已經厭倦。目前 npm 對前端包管理的支持確實還不夠好。咱們知道、咱們贊成、咱們承諾會讓事情變得更好。前端開發者們,npm 愛大家,並且咱們關心大家的使用場景。咱們本身也使用 npm 來構建本身的網站,咱們也有着一樣的痛點。所以,請繼續向咱們提供反饋和建議。咱們正在爲之努力。

 

最終,勝者必現

 

咱們的最終觀點是有必要明確一下的:咱們指望有一個解決方案能浮出水面,它是如此直觀、如此易用,以至於咱們能夠 「仰慕」 它,甚至把它內建到 npm 中或將它綁定爲 npm 的一部分。當咱們這樣作的時候,不但願人們認爲咱們是在偷換概念,由於咱們曾許諾要維護一個良性的競爭生態,但結果咱們又挑出了一位勝者(咱們知道這個作法在其它公司身上曾出過問題)。但事實上最終必將出現一位勝者:咱們只不過是到了那個時候才知道它是誰,而已。

 

若是你已經強烈地預感到那個終極方案是個什麼樣子,就去實現並推廣它吧,這比在 GitHub issue 裏寫長篇評論要強一萬倍;一樣,對於每一個處在 Node 社區的人來講,這也是極其受用的。所以,大步向前,去構建解決方案吧,咱們會密切關注的!

 

譯者後記

 

最初同事將這篇文章推薦給我時,我沒有讀下去。當江湖傳聞 Bower 「要完」 時,我再次翻出了這篇文章,並將它翻譯了出來。

 

但譯完以後,坦白地說,我有些失望。npm 在這篇文章中並無提供任何有效的解決方案,只是指望 「美好的事情必將發生」。這篇文章發表於 2014 年末,但直到如今 npm 也拿出文中提到的 「生態圈」 功能;這一年多來,前端包管理領域也沒有浮現任何真命天子般的終級解決方案。

 

不過,在前端開發者這一端,包管理的實踐風向卻是發生了不小的轉變。最明顯的潮流就是 「放棄 Bower,直接採用 npm」。這背後的推力,一方面是愈來愈多的 npm 包採用 UMD 做爲發佈方式,網頁直接使用也無壓力(固然咱們也能夠認爲這一點與上述潮流互爲因果);另外一方面,前端資源的構建過程已成常態,在頁面中經過 <script> 標籤直接引入腳本的狀況愈來愈少了,Bower 的獨有價值也就少了不少。此外,npm3 的扁平化目錄結構也進一步瓦解了前端開發者的心理防線。

 

如此看來,npm 動做雖慢,但斗轉星移,本身卻被推到浪潮之巔。這篇文章已無時效,但讀起來仍然頗有意思,令咱們有機會一窺這家公司的思惟方式與價值觀。

相關文章
相關標籤/搜索