{"action":"created","issue":{"url":"https://api.github.com/repos/wy-ei/notebook/issues/42","repository_url":"https://api.github.com/repos/wy-ei/notebook","labels_url":"https://api.github.com/repos/wy-ei/notebook/issues/42/labels{/name}","comments_url":"https://api.github.com/repos/wy-ei/notebook/issues/42/comments","events_url":"https://api.github.com/repos/wy-ei/notebook/issues/42/events","html_url":"https://github.com/wy-ei/notebook/issues/42","id":194824069,"number":42,"title":"NPM 相关知识","user":{"login":"wy-ei","id":7794103,"avatar_url":"https://avatars.githubusercontent.com/u/7794103?v=3","gravatar_id":"","url":"https://api.github.com/users/wy-ei","html_url":"https://github.com/wy-ei","followers_url":"https://api.github.com/users/wy-ei/followers","following_url":"https://api.github.com/users/wy-ei/following{/other_user}","gists_url":"https://api.github.com/users/wy-ei/gists{/gist_id}","starred_url":"https://api.github.com/users/wy-ei/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wy-ei/subscriptions","organizations_url":"https://api.github.com/users/wy-ei/orgs","repos_url":"https://api.github.com/users/wy-ei/repos","events_url":"https://api.github.com/users/wy-ei/events{/privacy}","received_events_url":"https://api.github.com/users/wy-ei/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":4,"created_at":"2016-12-11T10:19:19Z","updated_at":"2016-12-14T05:34:16Z","closed_at":null,"body":"## 系列文章\r\n\r\n- [npm 基本用法](#issuecomment-266274004)\r\n- [常用的 npm 命令](#issuecomment-266274086)\r\n- [npm script 用法详解](#issuecomment-266274139)"},"comment":{"url":"https://api.github.com/repos/wy-ei/notebook/issues/comments/266946999","html_url":"https://github.com/wy-ei/notebook/issues/42#issuecomment-266946999","issue_url":"https://api.github.com/repos/wy-ei/notebook/issues/42","id":266946999,"user":{"login":"wy-ei","id":7794103,"avatar_url":"https://avatars.githubusercontent.com/u/7794103?v=3","gravatar_id":"","url":"https://api.github.com/users/wy-ei","html_url":"https://github.com/wy-ei","followers_url":"https://api.github.com/users/wy-ei/followers","following_url":"https://api.github.com/users/wy-ei/following{/other_user}","gists_url":"https://api.github.com/users/wy-ei/gists{/gist_id}","starred_url":"https://api.github.com/users/wy-ei/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wy-ei/subscriptions","organizations_url":"https://api.github.com/users/wy-ei/orgs","repos_url":"https://api.github.com/users/wy-ei/repos","events_url":"https://api.github.com/users/wy-ei/events{/privacy}","received_events_url":"https://api.github.com/users/wy-ei/received_events","type":"User","site_admin":false},"created_at":"2016-12-14T05:34:16Z","updated_at":"2016-12-14T05:34:16Z","body":"## npm 是如何影响 node_modules 的目录结构的 ？\r\n\r\n一个大型项目常常要依赖很多第三方的模块，而第三方的模块又有自己的依赖，假如其中有两个模块依赖了同一个模块的不同版本，这个时候该模块就要存在两个不同版本，那么它们在 node_modules 中是如何存在的呢？ npm 的大量工作都是在处理这样的版本依赖问题。\r\n\r\n比如你的项目 yxxx，有如下依赖：\r\n\r\n```js\r\n\"dependencies\": {\r\n    A: \"1.0.0\",\r\n    C: \"1.0.0\"\r\n}\r\n```\r\n\r\n而 A 和 C 两个模块有如下的依赖关系。\r\n\r\n```\r\nA@1.0.0 -> B@1.0.0\r\n\r\nC@1.0.1 -> B@2.0.0\r\n```\r\n\r\n### npm v2 时代\r\n\r\n在 npm v2 时代，执行 `npm install` 后 node_modules 会是这样的：\r\n\r\n```\r\nnode_modules\r\n├── A@1.0.0\r\n│   └── node_modules\r\n│       └── B@1.0.0\r\n└── C@1.0.0\r\n    └── node_modules\r\n        └── B@2.0.0\r\n```\r\n\r\n这个时候如果在安装一个模块，D@1.0.0，D 有如下依赖：\r\n\r\n```\r\nD@1.0.0 -> B@1.0.0\r\n```\r\n\r\n安装完成之后，node_modules 会是这样的：\r\n\r\n```\r\nnode_modules\r\n├── A@1.0.0\r\n│   └── node_modules\r\n│       └── B@1.0.0\r\n├── C@1.0.0\r\n│   └── node_modules\r\n│       └── B@2.0.0\r\n└── D@1.0.0\r\n    └── node_modules\r\n        └── B@1.0.0\r\n```\r\n\r\nB@1.0.0 存在了两份，这显然是浪费的，这也是被吐槽最多的点，一个项目中存在太多相同版本的模块的副本。\r\n\r\n想想 require 在寻找模块时候的机制，它会向上级目录去寻找，因此 npm 3 做了改变。\r\n\r\n### npm v3 时代\r\n\r\n安装 A@1.0.0 模块，现在的目录结构变为：\r\n\r\n```\r\nnode_modules\r\n├── A@1.0.0\r\n└── B@1.0.0\r\n```\r\n\r\n可以看到他们存在于同一级目录，这个时候 A 中的 js 脚本在 A 中找不到 node_modules 后会在父级目录中找到 B 模块。\r\n\r\n继续安装 C@1.0.0 模块，因为 C@1.0.0 依赖的是 B@2.0.0 模块，而此时在 node_modules 中已经存在了 B@1.0.0，因此安装后的目录结构是这样的：\r\n\r\n```\r\nnode_modules\r\n├── A@1.0.0\r\n├── B@1.0.0\r\n└── C@1.0.0\r\n    └── node_modules\r\n        └── B@2.0.0\r\n```\r\n\r\n现在继续安装一个模块 E@1.0.0，它有如下依赖：\r\n\r\n```\r\nE@1.0.0 -> B@2.0.0\r\n```\r\n\r\n其实 B@2.0.0 已经存在了，只是它位于 C@1.0.0 模块下，\r\n\r\n安装完成后目录结构变为了：\r\n\r\n```\r\nnode_modules\r\n├── A@1.0.0\r\n├── B@1.0.0\r\n├── C@1.0.0\r\n│   └── node_modules\r\n│       └── B@2.0.0\r\n└── E@1.0.0\r\n    └── node_modules\r\n        └── B@2.0.0\r\n```\r\n\r\n这个时候 B@2.0.0 又存在了两份。Ok，继续安装一个模块 F@1.0.0，它的依赖关系如下：\r\n\r\n```\r\nF@1.0.0 -> B@1.0.0\r\n```\r\n\r\n这个时候因为 B@1.0.0 已经存在于项目根目录下的 node_modules 中了，因此目录结构是这样的：\r\n\r\n```\r\n├── A@1.0.0\r\n├── B@1.0.0\r\n├── C@1.0.0\r\n│   └── node_modules\r\n│       └── B@2.0.0\r\n├── D@1.0.0\r\n│   └── node_modules\r\n│       └── B@2.0.0\r\n├── E@1.0.0\r\n│   └── node_modules\r\n│       └── B@2.0.0\r\n└── F@1.0.0\r\n```\r\n\r\n### npm v3 去重\r\n\r\n好了，这个时候突然 A@1.0.0 需要升级到 2.0.0 版本，依赖关系也变为了:\r\n\r\n```\r\nA@2.0.0 -> B@2.0.0\r\n```\r\n\r\n安装后目录结构变为了：\r\n\r\n```\r\nnode_modules\r\n├── A@2.0.0\r\n│   └── node_modules\r\n│       └── B@2.0.0\r\n├── B@1.0.0\r\n├── C@1.0.0\r\n│   └── node_modules\r\n│       └── B@2.0.0\r\n├── D@1.0.0\r\n│   └── node_modules\r\n│       └── B@2.0.0\r\n├── E@1.0.0\r\n│   └── node_modules\r\n│       └── B@2.0.0\r\n└── F@1.0.0\r\n```\r\n\r\n随后 F 模块也升级至 2.0.0 版本了，依赖关系也变为了:\r\n\r\n```\r\nF@2.0.0 -> B@2.0.0\r\n```\r\n\r\n执行安装，在这个过程中首先会移除掉，F@1.0.0 然后发现，B@1.0.0 已经没有模块依赖它了，因此也移除了 B@1.0.0，然后安装 F@2.0.0，并安装其依赖 B@2.0.0，发现项目根目录的 `node_modules` 中并没有 B 模块的任何版本，于是就安装在了根目录的 `node_modules` 中。\r\n\r\n得到目录结构为：\r\n\r\n```\r\nnode_modules\r\n├── A@2.0.0\r\n│   └── node_modules\r\n│       └── B@2.0.0\r\n├── B@2.0.0\r\n├── C@1.0.0\r\n│   └── node_modules\r\n│       └── B@2.0.0\r\n├── D@1.0.0\r\n│   └── node_modules\r\n│       └── B@2.0.0\r\n├── E@1.0.0\r\n│   └── node_modules\r\n│       └── B@2.0.0\r\n└── F@2.0.0\r\n```\r\n\r\n坑爹呢，B@2.0.0 存在了很多个副本了。但也不要紧张，通常 npm 会利用链接来将多个副本指向同一个模块。这样的目录结构虽然觉得有些浪费，但是对代码运行没有丝毫影响。也许你想让他好看一点，没有问题，执行命令：\r\n\r\n```\r\nnpm dedupe\r\n```\r\n\r\n该命令会遍历模块依赖树，根据模块之间的依赖关系，移动模块的位置，去除重复，让整个 node_modules 的目录结构更加扁平一些。\r\n\r\n```\r\nnode_modules\r\n├── A@2.0.0\r\n├── B@2.0.0\r\n├── C@1.0.0\r\n├── D@1.0.0\r\n├── E@1.0.0\r\n└── F@2.0.0\r\n```\r\n\r\n### 目录结构的不确定性\r\n\r\n模块的安装次序决定了 `node_modules` 中的目录结构，这也是为什么明明 dependencies 中依赖的模块但得到的目录结构不同，假如有如下两个模块需要安装：\r\n\r\n```\r\nA@1.0.0 -> B@1.0.0\r\nC@1.0.0 -> B@2.0.0\r\n```\r\n\r\n安装 A 和 C 的次序不同得到的 `node_modules` 也就不同，因为 npm 会优先将模块放置在根目录下的 `node_modules` 中，所以先安装 A 和 C 中的哪一个决定了在 根目录下的 `node_modules` 中存在的是 B 的 2.0.0 版本还是 1.0.0 版本。\r\n\r\n只有在手动使用 `npm i <package> --save` 的时候才会出现这种情况，使用 `npm i`，npm 会去读取 `package.json` 中的 dependencies，而 dependencies 是安装字母顺序排列的。\r\n"}}
{"actor":{"display_login":"wy-ei"}}