# dev 与 devDep 的区别

> 当 require('package-hello') 时，假设 package-hello 是一个 npm 库，我们是如何找到该 package 的？ 1. 寻找当前目录的 node_modules/package-hello 目录 1. 如果未找到，寻找上一级的 ../node_modules/package-hello 目录，以此递归

- 发布于: 2019-06-28
- 原文: https://shanyue.tech/posts/frontend-engineering/node_modules/
- 标签: frontend-engineering
- 专栏: 前端工程化

---

## dev 与 devDep 的区别

## node_modules 的拓扑结构

## 基础

当 `require('package-hello')` 时，假设 `package-hello` 是一个 npm 库，我们是如何找到该 `package` 的？

1. 寻找当前目录的 `node_modules/package-hello` 目录
1. 如果未找到，寻找上一级的 `../node_modules/package-hello` 目录，以此递归查找

## 很久以前: 嵌套结构

在 `npmv2` 时，`node_modules` 对于各个 package 的拓扑为嵌套结构。

假设:

1. 项目依赖 `package-a` 与 `package-b` 两个 package
1. `package-a` 与 `package-b` 均依赖 `lodash@4.17.4`

依赖关系以 Markdown 列表表示:

```markdown
- package-a
  - `lodash@4.17.4`
- package-b
  - `lodash@4.17.4`
```

此时 `node_modules` 目录结构如下:

```mermaid
graph
  app(node_modules) ---> A(package-a)
  app          ---> B(package-b)
  A            ---> C("lodash@4.17.4")
  B            ---> D("lodash@4.17.4")
```

此时最大的问题

1. 嵌套过深
2. 占用空间过大

## 现在阶段: 平铺结构

> 目前在 npm/yarn 中仍然为平铺结构，但 pnpm 使用了更省空间的方法，以后将会提到

在 `npmv3` 之后 `node_modules` 为平铺结构，拓扑结构如下:

```mermaid
graph
  app(node_modules) ---> A(package-a)
  app          ---> B(package-b)
  app          ---> C("lodash@4.17.4")
```

### _一个问题: 以下依赖最终 node_modules 结果如何？_

> 可参考[该示例](https://github.com/shfshanyue/node-examples/tree/master/engineering/package/topology)

依赖关系以 Markdown 列表表示

```markdown
- package-a
  - `lodash@^4.17.4`
- package-b
  - `lodash@^4.16.1`
```

答: 与上拓扑结构一致，因为二者为 `^` 版本号，他们均会下载匹配该版本号范围的最新版本，比如 `@4.17.4`，因此二者依赖一致。

> 此时如果有 lock，会有一点小问题，待稍后讨论

node_modules 目录结构如下图：

```mermaid
graph
  app(node_modules) ---> A(package-a)
  app          ---> B(package-b)
  app          ---> C("lodash@4.17.4")
```

### _再一个问题: 以下依赖最终 node_modules 结果如何？_

> 可参考[该示例](https://github.com/shfshanyue/node-examples/tree/master/engineering/package/topology-dup)

```markdown
- package-a
  - `lodash@4.17.4`
- package-b
  - `lodash@4.16.1`
```

答：package-b 先从自身 node_modules 下寻找 `lodash`，找到 `lodash@4.16.1`

node_modules 目录结构如下图：

```mermaid
graph
  app(node_modules) ---> A(package-a)
  app          ---> B(package-b)
  app          ---> C("lodash@4.17.4")
  B            ---> D("lodash@4.16.1")
```

### _再一个问题: 以下依赖最终 node_modules 结果如何_

```markdown
- package-a
  - `lodash@4.0.0`
- package-b
  - `lodash@4.0.0`
- package-c
  - `lodash@3.0.0`
- package-d
  - `lodash@3.0.0`
```

答：package-d 只能从自身的 node_modules 下寻找 `lodash@3.0.0`，而无法从 package-c 下寻找，此时 **lodash@3.0.0 不可避免地会被安装两次**

node_modules 目录结构如下图:

```mermaid
graph
  app(node_modules) ---> A(package-a)
  app          ---> B(package-b)
  app          ---> C(package-c)
  app          ---> D(package-d)
  app          ---> X("lodash@4.0.0")
  C            ---> Y("lodash@3.0.0")
  D            ---> Z("lodash@3.0.0")
```

## 重复的版本依赖有什么问题？

可参考 [npm doppelgangers](https://rushjs.io/pages/advanced/npm_doppelgangers/)

1. Install Size，安装体积变大，浪费磁盘空间
1. Build Size，构建打包体积变大，浪费带宽，网站打开延迟，破坏用户体验 (PS: 支持 Tree Shaking 会好点)
1. 破坏单例模式，破坏缓存，如 postcss 的许多插件将 postcss 扔进 dependencies，重复的版本将导致解析 AST 多次

## lock 与重复的版本依赖问题

### _一个问题: 以下依赖最终 node_modules 结果如何？_

> 可参考[该示例](https://github.com/shfshanyue/node-examples/tree/master/engineering/package/topology)

依赖关系以 Markdown 列表表示

```markdown
- package-a
  - `lodash@^4.16.0`
- package-b
  - `lodash@^4.17.0`
```

答: 因为二者为 `^` 版本号，他们均会下载匹配该版本号范围的最新版本，比如 `@4.17.0`，因此二者依赖一致。

```mermaid
graph
  app(node_modules) ---> A(package-a)
  app          ---> B(package-b)
  app          ---> C("lodash@4.17.0")
```

### _再一个问题: 如果有 lockfile 且安装时间不一致，最终 node_modules 结果如何_

依赖关系以 Markdown 列表表示:

```markdown
- package-a
  - `lodash@^4.16.0`
- package-b
  - `lodash@^4.17.0`
```

安装流程如下:

1. 首次下载 `package-a`，其依赖 `lodash@^4.16.0`，此时最新版本为 `lodash@4.16.0`
1. 三个月后，下载 `package-b`，其依赖 `lodash@4.17.0`，此时最新版本为 `lodash@4.17.0`

问: 最终 `node_modules` 及 lockfile 结果如何？

```mermaid
graph
  app(node_modules) ---> A(package-a)
  app          ---> B(package-b)
  app          ---> C("lodash@4.17.0")
```
