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 动做虽慢,但斗转星移,本身却被推到浪潮之巅。这篇文章已无时效,但读起来仍然颇有意思,令咱们有机会一窥这家公司的思惟方式与价值观。

相关文章
相关标签/搜索