npm install后发生了什么

npm install 之后发生了什么?

记一次排错经历——npm缓存浅析

npm 模块安装机制简介

介绍下 npm 模块安装机制,为什么输入 npm install 就可以自动安装对应的模块?

npm install是干嘛的

npm install命令用来安装模块到node_modules目录。

安装之前,npm install会先检查node_modules目录之中是否已经存在指定模块。如果存在,就不再重新安装了,即使远程仓库已经有了一个新版本,也不会安装。

如果希望一个模块不管是否安装过,npm 都要强制重新安装,可以使用-f--force参数。

1
$ npm install <packageName> --force
1
$ npm install --verbose

运行命令,会把安装过程的log输出。

WX20190808-134108@2x

registry

npm 模块仓库提供了一个查询服务,叫做 registry,npmjs.org 的查询服务网址是https://registry.npmjs.org/

后面可以加上npm包名,比如:https://registry.npmjs.org/npm-study4,打开地址是一个JSON 对象,里面是该模块所有版本的信息。dist.tarball属性是该版本压缩包的网址,打开这个网址就可以下载npm压缩包,在本地解压,就可以获得源码,npm installnpm update命令,都是通过这种方式安装模块。

WX20190709-102920@2x

npm update

1
$ npm update <packageName>

它会先到远程仓库查询最新版本,然后查询本地版本。如果本地版本不存在,或者远程版本较新,就会安装。

缓存

1
2
3
4
5
6
#获取目录的具体位置
$ npm config get cache
#/Users/qinhanwen/.npm

#清除缓存目录文件
$ npm cache clean -f

要注意在 npm@5 之后

  • 缓存数据放在 .npm/_cacache 文件夹中
  • 清除命令清除的是.npm/_cacache 目录下文件

那么看一下.npm/_cacache目录下

WX20190711-192302@2x

content-v2 里面基本都是一些二进制文件,把二进制文件的扩展名改为 .tgz 再解压之后,会发现就是在我们熟知的npm包。

WX20190711-212439@2x

index-v5 里面是一些描述性的文件,也是 content-v2 里文件的索引,有点像HTTP的响应头,而且还有缓存相关的值

WX20190711-213336@2x

所以npm install之后发生了什么

先了解一下shell的解释行(Shebang)是什么

Shebang(也称为Hashbang)是一个由井号和叹号构成的字符串行(#!),其出现在文本文件的第一行的前两个字符。

如下列出了一些典型的shebang解释器指令:

1
2
3
4
5
#!/bin/sh—使用sh,即Bourne shell或其它兼容shell执行脚本
#!/bin/csh—使用csh,即C shell执行
#!/usr/bin/perl -w—使用带警告的Perl执行
#!/usr/bin/python -O—使用具有代码优化的Python执行
#!/usr/bin/php—使用PHP的命令行解释器执行

通常出现在linux的shell脚本第一行,作为解释行,告诉解释器shell的执行方式。

npm入口文件后面添加--inspect-brk就可以断点调试

1
#!/usr/bin/env node  --inspect-brk

入口文件在:

WX20190808-134156@2x

前置条件:npm版本6.4.1不存在package-lock.json,以及没有缓存文件存在的情况。

1.入口文件进入

2.加载install.js

WX20190805-224325@2x

WX20190712-094454@2x

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function Installer (where, dryrun, args, opts) {
validate('SBA|SBAO', arguments)
if (!opts) opts = {}
this.where = where
this.dryrun = dryrun
this.args = args
// fakechildren are children created from the lockfile and lack relationship data
// the only exist when the tree does not match the lockfile
// this is fine when doing full tree installs/updates but not ok when modifying only
// a few deps via `npm install` or `npm uninstall`.
this.currentTree = null
this.idealTree = null
this.differences = []
this.todo = []
this.progress = {}
this.noPackageJsonOk = !!args.length
this.topLevelLifecycles = !args.length

this.autoPrune = npm.config.get('package-lock')

const dev = npm.config.get('dev')
const only = npm.config.get('only')
const onlyProd = /^prod(uction)?$/.test(only)
const onlyDev = /^dev(elopment)?$/.test(only)
const prod = npm.config.get('production')
this.dev = opts.dev != null ? opts.dev : dev || (!onlyProd && !prod) || onlyDev
this.prod = opts.prod != null ? opts.prod : !onlyDev

this.packageLockOnly = opts.packageLockOnly != null
? opts.packageLockOnly : npm.config.get('package-lock-only')
this.rollback = opts.rollback != null ? opts.rollback : npm.config.get('rollback')
this.link = opts.link != null ? opts.link : npm.config.get('link')
this.saveOnlyLock = opts.saveOnlyLock
this.global = opts.global != null ? opts.global : this.where === path.resolve(npm.globalDir, '..')
this.audit = npm.config.get('audit') && !this.global
this.started = Date.now()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
Installer.prototype.run = function (_cb) {
//进行一些校验
...
//将安装的步骤放入队列里面
var installSteps = []
var postInstallSteps = []
if (!this.dryrun) {
installSteps.push(
[this.newTracker(log, 'runTopLevelLifecycles', 2)],
[this, this.runPreinstallTopLevelLifecycles])
}
installSteps.push(
[this.newTracker(log, 'loadCurrentTree', 4)],
[this, this.loadCurrentTree],
[this, this.finishTracker, 'loadCurrentTree'],

[this.newTracker(log, 'loadIdealTree', 12)],
[this, this.loadIdealTree],
[this, this.finishTracker, 'loadIdealTree'],

[this, this.debugTree, 'currentTree', 'currentTree'],
[this, this.debugTree, 'idealTree', 'idealTree'],

[this.newTracker(log, 'generateActionsToTake')],
[this, this.generateActionsToTake],
[this, this.finishTracker, 'generateActionsToTake'],

[this, this.debugActions, 'diffTrees', 'differences'],
[this, this.debugActions, 'decomposeActions', 'todo'],
[this, this.startAudit]
)

if (this.packageLockOnly) {
postInstallSteps.push(
[this, this.saveToDependencies])
} else if (!this.dryrun) {
installSteps.push(
[this.newTracker(log, 'executeActions', 8)],
[this, this.executeActions],
[this, this.finishTracker, 'executeActions'])
var node_modules = path.resolve(this.where, 'node_modules')
var staging = path.resolve(node_modules, '.staging')
postInstallSteps.push(
[this.newTracker(log, 'rollbackFailedOptional', 1)],
[this, this.rollbackFailedOptional, staging, this.todo],
[this, this.finishTracker, 'rollbackFailedOptional'],
[this, this.commit, staging, this.todo],

[this, this.runPostinstallTopLevelLifecycles],
[this, this.finishTracker, 'runTopLevelLifecycles']
)
if (getSaveType()) {
postInstallSteps.push(
[this, (next) => { computeMetadata(this.idealTree); next() }],
[this, this.pruneIdealTree],
[this, this.debugLogicalTree, 'saveTree', 'idealTree'],
[this, this.saveToDependencies])
}
}
postInstallSteps.push(
[this, this.printWarnings],
[this, this.printInstalled])

var self = this
//到这里才真正开始执行
chain(installSteps, function (installEr) {
if (installEr) self.failing = true
chain(postInstallSteps, function (postInstallEr) {
if (installEr && postInstallEr) {
var msg = errorMessage(postInstallEr)
msg.summary.forEach(function (logline) {
log.warn.apply(log, logline)
})
msg.detail.forEach(function (logline) {
log.verbose.apply(log, logline)
})
}
cb(installEr || postInstallEr, self.getInstalledModules(), self.idealTree)
})
})
return result
}

3.经历install的生命周期

在各种生命周期中做了的一些事情:

读根目录下package.json文件,确定依赖树

WX20190712-105741@2x

获取元信息

WX20190712-140123@2x

获取包信息,就是要安装的包的数据

WX20190712-163503@2x

内容:

WX20190712-164143@2x

下载的.tgz后缀的压缩包,比如http://registry.npmjs.org/npm-study1/-/npm-study1-2.0.0.tgz,保存到.npm/_cacache文件夹,解压到node_modules目录下,创建package-lock.json文件。

补充:

当运行npm install的时候,会检查node_modules目录下,并不是node_modules目录下有依赖包,就不安装了(情况比较多)。

如果node_modules目录下没有这个文件,缓存里有的话,据我对打印出log的观察,应该是从缓存里解压到node_modules目录下。

那么哪些操作会更新package.json或更新package-lock.json,同时更新node_modules下的依赖包?

node_modules下不存在某依赖包:

npm init初始化后,npm install xxx(高版本的npm)会自动更新package.json文件,并且在最后创建package-lock.json文件,同时更新node_modules下的依赖包。

node_modules下存在某依赖包:

1)^版本号情况下, package.json中某个依赖包的小版本要大于package-lock.json中的同个依赖包小版本,

npm install的时候,会修改package-lock.json文件,同时更新node_modules下的依赖包(安装某依赖包为当前大版本下的最新版本)。 小于的话,更新node_modules下的某依赖包,版本号与package-lock.json版本号一致。

2)版本号具体到某个版本的话,只要和node_modules下的依赖包版本不同,就会安装。

2)npm update packageName,更新package.json文件,同时更新node_modules下的某依赖包,版本号为当前依赖包大版本号下最新的版本(比如2.1.0就升到2.9.0,不会超过3.0.0)。

4)或者npm install xxx@x.x.x,更新package.json文件,更新package-lock.json文件,更新node_modules

5)npm uninstall packageName <args>,更新package.json文件,更新package-lock.json文件,更新node_modules