NPM的flatten, dedupe和shrinkwrap

今天碰到了一些NPM相关的问题, 花了些时间搞清楚, 记录一下.

关于NPM包依赖的扁平化 (flatten)

接手的项目里面使用到了包flatten-packages, 这个包可以将npm包的嵌套依赖压扁. 对这方面之前没了解过, 于是做了一下调研.

为什么需要扁平化

在早期, npm包是以下形式保存的:

.
|--app
    |--node_modules
       |--sub_module
          |--node_modules
             |--sub_sub_module
                |-- ... and so on

即你的项目依赖包A, 那么node_modules里面就有一个A的文件夹, 在这个文件夹中, 又有一个node_modules, 所有A的依赖都放这个node_modules中, 以此类推. npm的包依赖就会成为一个非常深的文件夹结构, 深到有时你在windows系统直接删除该文件夹会失败, 因为文件目录超过了windows限制的256个字符. 此时你可以打开Git Bash什么的rm -rf.

这会导致一个问题: 不同的包(A和B)可能依赖同一个包(C), 这样npm install的时候, 安装A的时候会下载一次C, 安装B的时候又会下载一次C. 无形中多了很多没必要的下载, 导致npm install的速度变慢很多.

所以这也就有了扁平化包依赖的需求 -- 让重复的依赖尽量合并, 以加速包管理的速度.

扁平化的方法

flatten-packages

flatten-packages这个包可以解决这个问题. 跑一下flatten-packages即可.

但是, 新版本的npm会进行自动扁平化处理, 所以flatten-packages已经没有用了, 这个包也已经停止更新了. 而且在解决包冲突方面也不如npm dedupe. 相关Issue

npm3的自动扁平化

npm第3版会自动进行包的扁平化. 详见npm v3, 搜索flat.

npm3会尽可能地扁平化包依赖, 绝大多数情况下你的依赖包都会直接存在于node_modules里. 唯一的例外是, 某两个包依赖互相冲突的时候.

为什么包依赖会有冲突?

简单的例子就是同一个包C, A依赖于C 1.0.0, B依赖于C 2.0.0. 这样如果你的app同时依赖于A和B, 那就没法直接在node_modules里面放一个版本C来同时满足A和B的依赖了. 这里的版本号是SemVer.

SemVer

SemVer (Semantic Versioning, 语义化版本).

SemVer最基本的结构是major.minor.patch, 如1.2.3.
其中,

  • major为主版本号, 当有非向后兼容(即breaking change)的时候, 更新major.
  • minor为次版本号, 当有向后兼容的时候, 更新minor.
  • patch为补丁号, 当有向后兼容的bug fix时, 更新patch.

npm使用SemVer来标注包的版本, 这些配置写入到了package.json中.

除了指定固定的版本号, package.json中还可以指定版本号范围.

  • 1.2.x (x也可以用*代替), 相当于>=1.2.0 <1.3.0
  • 1.x.x, 相当于>=1.0.0 <2.0.0
  • 波浪线(Tilde), ~1.2.3相当于>=1.2.3 <1.3.0, 即minor不能增加.
  • 破折号(Caret), ^1.2.3相当于>=1.2.3 <2.0.0, 即major不能增加.

包去重 (dedupe)

想要去重只需运行npm dedupe

既然npm3自动进行了扁平化, 为什么还需要去重? 这个npm用了一篇文章npm3 Duplication and Deduplication进行讲解, 我就不翻译了. 简单来说就是:

  1. 由于历史原因, 某两个包A和B依赖于同一个包C的不同版本1.0.0和2.0.0, 这个冲突导致C的依赖无法被合并.
  2. 后来A和B的某次更新使得他们依赖于同一个版本的C2.0.0, 但是仍然由于你的app直接依赖C1.5.0, 导致A和B依赖的C2.0.0依然无法被合并, 只能各自存放在A和B的目录下.
    (问题: 如果这时候运行npm dedup是什么效果?)
  3. 后来你把直接依赖C1.5.0更新成了C2.0.0, 这会导致你有三个重复的C2.0.0, 需要运行npm dedupe去重.
    (问题: 为什么不能更新版本的时候自动运行dedupe呢?)

包依赖锁定(shrinkwrap/lock)

package.json中的依赖包的版本号可能是版本范围, 或者依赖包的依赖可能使用了版本范围, 这会导致一个问题: 你今天npm install安装了包A1.0.0, 一段时间后你的同事运行npm install, 可能就会安装A1.2.0, 导致你们的运行环境不完全一样. 想要将包依赖的版本号完全锁定住, 就需要shrinkwrap/lock.

package-lock.json

npm5引入了package-lock.json, 即在你运行npm install的时候自动会生成package-lock.json文件, 这是一个描述依赖树的文件, 它的好处是锁定了所有依赖的版本甚至下载地址, 而且结构清晰人能读懂(相对于错综复杂的node_modules目录结构).

示例package-lock.json

{
  "name": "A",
  "version": "0.1.0",
  ...metadata fields...
  "dependencies": {
    "B": {
      "version": "0.0.1",
      "resolved": "https://registry.npmjs.org/B/-/B-0.0.1.tgz",
      "integrity": "sha512-DeAdb33F+"
      "dependencies": {
        "C": {
          "version": "git://github.com/org/C.git#5c380ae319fc4efe9e7f2d9c78b0faa588fd99b4"
        }
      }
    }
  }
}

如果有package-lock.json, 安装过程会变成:

  1. 按照package-lock.json重建依赖包的树形结构. 如果有"resolved"字段则使用该字段指向的文件下载文件, 否则使用"version".
  2. 若最后还有缺失的依赖包, 则使用普通的package.json安装方法.

注意: npm install, npm rm, npm update都会自动更新package-lock.json. 如果你不想更新, 可以使用以下命令行参数:
--no-save: 不更新package.json也不更新package-lock.json
--no-shrinkwrap: 更新package.json, 不更新package-lock.json和npm-shrinkwrap.json.

npm非常建议将package-lock.json存入版本控制, 以确保组内所有成员, 持续集成(Continuous Integration, CI)和部署环境是用完全一致的依赖包.[package-locks]

npm-shrinkwrap.json

你可以使用npm shrinkwrap指令, 在package-lock.json的基础上生成一个名为npm-shrinkwrap.json的文件.

package-lock.json和npm-shrinkwrap.json的内容完全一样, 唯一区别是: 当发布包的时候, package-lock.json不会被包含在内, 但是npm-shrinkwrap.json会被一同发布. 如果两者同时存在, package-lock.json会被完全忽略.

参考

  1. Semver: A Primer
  2. 为什么我不使用 shrinkwrap(lock)
  3. package-locks
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350