共计 7399 个字符,预计需要花费 19 分钟才能阅读完成。
每次克隆下他人的代码后,执行的第一步就是 npm install
装置依赖包,装置胜利后所有的包都会放在我的项目的 node_modules
文件夹下,也会主动生成 package-lock.json
文件。有没有好奇过 node_modules
下的文件都是啥?package-lock.json
文件的作用是啥?
本文次要解决以下几个问题:
package.json
中的dependencies
和devDependencies
的区别是啥,peerDependencies
、bundledDependencies
、optionalDependencies
又是啥?- 为什么有的命令写在
package.json
中的script
中就能够执行,然而通过命令行间接执行就不行? - 为什么须要
package-lock.json
文件? - 一个包在我的项目中有可能须要不同的版本,最初装置到根目录
node_modules
中的具体是哪个版本?
带着这几个问题,咱们先从 package.json
文件说起。
package.json
最靠谱的官网文档请点这里
官网文档中列出了好多属性,感兴趣的能够一个个看一遍。上面只列出其中几个比拟罕用且重要的属性。
name & version
如果想要公布一个 npm
包,name
和 version
属性是必须的。他们两个组合会造成一个惟一的标识来表名以后包。当前每更新一次包,version
就须要进行相应的更改。如果你不打算公布包,只想在本地应用,这两个字段不是必须的。
name
字段命名的规定如下:
- 长度不能超过 214 个字符(对于有 scoped 的包,该限度包含 scoped 字段)(什么是 Scoped packages?)
- 有作用域的包名字能够以. 或者_结尾,没有作用域限度的不可
- 不能含有大写字母
- 不能含有非 URL 平安的字符
version
字段
版本号须要合乎 semver
(语义化版本号) 规定,具体版本格局为:主版本号. 次版本号. 订正号
,如 1.1.0。
- 主版本号(major):做了不兼容的 API 批改
- 次版本号(minor):做了向下兼容的功能性新增
- 订正号(patch):做了向下兼容的问题修改
当有一些后行版本须要公布时,能够在 主版本号. 次版本号. 订正号
之后加上一个中划线和标识符如 alpha(外部版本)、beta(公测版本)、rc(候选版本)等来表明。
以 vue 的版本为例:
- 最新的稳固版本:3.0.5
- 最新的 rc 版本:3.0.0-rc.13
- 最新的 beta 版本:3.0.0-beta.24
- 最新的 alpha 版本:3.0.0-alpha.13
能够通过 npm install semver
来查看一个包的命名是否合乎 semver
规定。无关 semver 具体的阐明能够看这里
dependencies & devDependencies
dependencies
和 devDependencies
大家应该都不生疏, 通过 npm install xx --save
装置的包会写入 dependencies
中,通过 npm install xx --save-dev
装置的包会写入devDependencies
。
dependencies
中的包是生产环境的依赖,属于线上代码的一部分,比方 vue
、axios
、veui
等。devDependencies
中的包是开发环境的依赖,只是在本地开发的时候须要依赖这里的包,比方 vue-loader
、eslint
等。
咱们平时用的 npm install
命令既会装置 dependencies
中的包,也会装置 devDependencies
中的包。如果只想装置 dependencies
中包,能够应用 npm install --production
或者将 NODE_ENV
环境变量设置为production
,通常在生成环境咱们会这么用。
须要留神的是,一个模块会不会被打包取决于咱们在我的项目中是否引入了该模块,跟该模块放在 dependencies
中还是 devDependencies
并没有关系。
peerDependencies & bundledDependencies & optionalDependencies
这三个属性在平时咱们的我的项目开发中都用不到。不同于 dependencies
& devDependencies
面向的是包的使用者,peerDependencies
& optionalDependencies
& bundledDependencies
这三个属性是面向包的发布者。
peerDependencies
咱们在一些 node_modules
包的 package.json
中能够看到 peerDependencies
,它用来表明如果你想要应用此插件,此插件要求宿主环境所装置的包。比方我的项目中用到的veui1.0.0-alpha.24
版本中:
"peerDependencies": {"vue": "^2.5.16"}
这表明如果你想要应用 veui
的1.0.0-alpha.24
版本,所要求的 vue
版本须要满足 >=2.5.16
且<3.0.0
。
在 npm3.x
以上版本中,如果装置完结后宿主环境没有满足 peerDependencies
中的要求,会在控制台打印出正告信息。
bundledDependencies
当咱们想在本地保留一个 npm
残缺的包或者想生成一个压缩文件来获取 npm
包的时候,会用到 bundledDependencies
。本地应用npm pack
打包时会将 bundledDependencies
中依赖的包一起打包,当 npm install
时相应的包会同时被装置。须要留神的是,bundledDependencies
中的包不应该蕴含具体的版本信息,具体的版本信息须要在 dependencies
中指定。
例如一个 package.json
文件如下:
{
"name": "awesome-web-framework",
"version": "1.0.0",
"bundledDependencies": [
"renderized",
"super-streams"
]
}
当咱们执行 npm pack
后会生成 awesome-web-framework-1.0.0.tgz
文件。该文件中蕴含 renderized
和super-streams
这两个依赖,当执行 npm install awesome-web-framework-1.0.0.tgz
下载包时,这两个依赖会被装置。
当咱们应用 npm publish
来公布包的话,这个属性不会起作用。
optionalDependencies
从名字上就能够看出,这是可选依赖。如果有包写在 optionalDependencies
中,即便 npm
找不到或者装置失败了也不会影响装置过程。须要留神的是,optionalDependencies
中的配置会笼罩 dependencies
中的配置,所以不要将同一个包同时放在这两个外面。
如果应用了optionalDependencies
,肯定记得要在我的项目中做好异样解决,获取不到的状况下应该怎么办。
scripts
定义在 scripts
中的命令,咱们通过 npm run <command>
就能够执行。npm run <command>
是 npm run-script <command>
的简写。如果不加command
,则会列出当前目录下可执行的所有脚本。
test
、start
、restart
、stop
这几个命令执行时能够不加 run
,间接npm test
、npm start
、npm restart
、npm stop
调用即可。
env
是一个内置的命令,能够通过 npm run env
能够获取到脚本运行时的所有环境变量。自定义的 env
命令会笼罩内置的 env
命令。
之前开发中遇到一种状况,比方咱们想本地通过 http-server
启动一个服务器,如果当时没有全局装置过 http-server
包,只是装置在对应我的项目的 node_modules
中。在命令行中输出 http-server
会报 command not found
,然而如果咱们在scripts
中减少如下一条命令就能够执行胜利。
scripts: {
"server": "http-server","eslint": "eslint --ext .js"
}
为什么同样的命令写在 scripts
中就能够胜利,然而在命令行中执行就不行呢?这是因为 npm run
命令会将 node_modules/.bin/
退出到 shell
的环境变量 PATH
中,这样即便部分装置的包也能够间接执行而不必加 node_modules/.bin/
前缀。当执行完结后,再将其删除。
是不是还是没明确,上面咱们来具体分析一下。
首先要明确什么是环境变量。环境变量就是零碎在执行一个程序,然而没有明确表明该程序所在的残缺门路时,须要去哪里寻找该程序。
对于部分装置的包,拿 eslint
来说,npm
会在本地我的项目 ./node_modules/.bin
目录下创立一个指向 ./node_moudles/eslint/bin/eslint.js
名为 eslint
的软链接,即执行 ./node_modules/.bin/eslint
实际上是执行 ./node_moudles/eslint/bin/eslint.js
。而当咱们执行npm run eslint
的时候,node_modules/.bin/
会被退出到环境变量 PATH
中,实际上执行的是./node_modules/.bin/eslint
,这样就串起来了。
实践说完之后,咱们来理论验证一下。
首先看一下零碎的环境变量。间接执行 env
即可。
而后在以后我的项目目录下通过 npm run env
查看脚本运行时的环境变量。
通过比照能够发现,运行时的 PATH
多了两个环境变量。即 npm
指令的门路和我的项目 /node_modules/.bin
的门路。
以上就是 package.json
中罕用 & 重要的几个属性,接下来咱们来看一看package-lock.json
。
package-lock.json
对于 npm
,package.json
文件能够看成它的输出,node_modules
能够做为它的输入。在现实状况下,npm
应该是一个纯函数,无论何时执行雷同的 package.json
文件都应该产生完全相同的 node_modules
树。在一些状况下,这的确能够做到。然而在大多状况下,都实现不了。次要有以下几个起因:
- 使用者的
npm
版本有可能不同,不同的npm
版本有着不同的装置算法 - 自上次装置之后,有些合乎
semver-range
的包曾经有新的版本公布。这样再有他人装置的时候,会装置符合要求的最新版本。比方引入vue
包:vue:^2.6.1
。A 小伙伴下载的时候是2.6.1
,过一阵有另一个小伙伴 B 入职在安装包的时候,vue
曾经降级到2.6.2
,这样npm
就会下载2.6.2
的包装置在他的本地 - 针对第二点,一个解决办法是固定本人引入的包的版本,然而通常咱们不会这么做。即便这样做了,也只能保障本人引入的包版本固定,也无奈保障包的依赖的降级。比方
vue
其中的一个依赖lodash
,lodash:^4.17.4
,A 下载的是4.17.4
, B 下载的时候有可能曾经降级到了4.17.21
为了解决上述问题,npm5.x
开始减少了 package-lock.json
文件。每当 npm install
执行的时候,npm
都会产生或者更新 package-lock.json
文件。package-lock.json
文件的作用就是锁定以后的依赖装置构造,与 node_modules
中下所有包的树状构造一一对应。
有了这个 package-lock.json
文件,就能保障团队每个人装置的包版本都是雷同的,不会呈现有些包降级造成我这好使他人那不好使的兼容性问题。
上面是 less
的package-lock.json
文件构造:
"less": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/less/-/less-3.13.1.tgz",
"integrity": "sha512-SwA1aQXGUvp+P5XdZslUOhhLnClSLIjWvJhmd+Vgib5BFIr9lMNlQwmwUNOjXThF/A0x+MCYYPeWEfeWiLRnTw==",
"dev": true,
"requires": {
"copy-anything": "^2.0.1",
"errno": "^0.1.1",
"graceful-fs": "^4.1.2",
"image-size": "~0.5.0",
"make-dir": "^2.1.0",
"mime": "^1.4.1",
"native-request": "^1.0.5",
"source-map": "~0.6.0",
"tslib": "^1.10.0"
},dependencies: {
"copy-anything": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.3.tgz",
"integrity": "sha512-GK6QUtisv4fNS+XcI7shX0Gx9ORg7QqIznyfho79JTnX1XhLiyZHfftvGiziqzRiEi/Bjhgpi+D2o7HxJFPnDQ==",
"dev": true,
"requires": {"is-what": "^3.12.0"}
}
}
}
- version: 包的版本信息
- resoloved: 包的装置源
- integrity:一个
hash
值,用来校验包的完整性 - dev:布尔值,如果为
true
,表明此包如果不是顶层模块的一个开发依赖(写在devDependencie
s 中),就是一个传递依赖(如下面less
中的copy-anything
)。 - requires: 对应子依赖的依赖,与依赖包的
package.json
中dependencies
的依赖项雷同 - dependencies:构造与外层构造雷同,存在于包本人的
node_modules
中的依赖(不是所有的包都有,当子依赖的依赖版本与根目录的node_modules
中的依赖抵触时,才会有)
通过剖析下面的 package-lock.json
文件,兴许会有一个问题。为什么有的包能够被装置在根目录的 node_modules
中,有的包却只能装置在本人包上面的 node_modules
中?这就波及到 npm
的装置机制。
npm 从 3.x
开始,采纳了扁平化的形式来装置 node_modules
。在装置时,npm 会遍历整个依赖树,不论是我的项目的间接依赖还是子依赖的依赖,都会优先装置在根目录的node_modules
中。遇到雷同名称的包,如果发现根目录的 node_modules
中存在然而不合乎 semver-range
,会在子依赖的node_modules
中装置符合条件的包。
具体的装置算法如下:
- 从磁盘加载
node_modules
树 - 克隆
node_modules
树 - 获取
package.json
文件和分类结束的元数据信息并把元数据信息插入到克隆树中 - 遍历克隆树,检测是否有失落的依赖。如果有,把他们增加到克隆树中,依赖会尽可能的增加到最高层
- 比拟原始树和克隆树,列出将原始树转换为克隆树所要采取的具体步骤
- 执行,包含
install
,update
,remove
andmove
以 npm
官网的例子举例,假如 package{dep}
构造代表包和包的依赖,现有如下构造:A{B,C}
, B{C}
, C{D}
,依照上述算法执行结束后,生成的 node_modules
构造如下:
A
+-- B
+-- C
+-- D
对于 B
,C
被装置在顶层很好了解,因为是 A
的间接依赖。然而 B
又依赖 C
,装置C
的时候发现顶层曾经有 C
了,所以不会在 B
本人的 node_modules
中再次装置。C
又依赖 D
,装置D
的时候发现根目录并没有 D
,所以会把D
晋升到顶层。
换成 A{B,C}
, B{C,D@1}
, C{D@2}
这样的依赖关系后,产生的构造如下:
A
+-- B
+-- C
`-- D@2
+-- D@1
B
又依赖了 D@1
,装置时发现根目录的node_modules
没有,所以会把 D@1
装置在顶层。C
依赖了 D@2
,装置D@2
时,因为 npm
不容许同层存在两个名字雷同的包,这样就与跟目录 node_modules
的D@1
抵触,所以会把 D@2
装置在 C
本人的 node_modules
中。
模块的装置程序决定了当有雷同的依赖时,哪个版本的包会被装置在顶层。首先我的项目中被动引入的包必定会被装置在顶层,而后会依照包名称排序 (a-z) 进行顺次装置,跟包在 package.json
中写入的程序无关。因而,如果上述将 B{C,D@1}
换成 E{C,D@1}
,那么D@2
将会被装置在顶层。
有一种状况,当咱们我的项目中所援用的包版本较低,比方 A{B@1,C}
,而C
所须要的是 C{B@2}
版本,当初的构造应该如下:
A
+-- B@1
+-- C
`-- B@2
有一天咱们将我的项目中的 B
降级到B@2
,现实状况下的构造应该如下:
A
+-- B@2
+-- C
然而当初 package-lock.json
文件的构造却是这样的:
A
+-- B@2
+-- C
`-- B@2
B@2
不仅存在于根目录的 node_modules
下,C
下也同样存在。这时须要咱们手动执行 npm dedupe
进行去重操作,执行实现后会发现 C
上面的 B@2
会隐没。大家能够在本人的我的项目中试一试,优化一下 package-lock.json
文件的构造。
以下是在我的我的项目中执行 npm dedupe
的后果:
removed 41 packages, moved 15 packages and audited 1994 packages in 18.538s
在 npm5.x
之前,能够手动通过 npm shrinkwrap
生成 npm-shrinkwrap.json
文件,与 package-lock.json
文件的作用雷同。当我的项目中同时存在 npm-shrinkwrap.json
和package-lock.json
,将以 npm-shrinkwrap.json
为主。
本文只是一些实践根底,之后会介绍一些 npm
源码相干的常识。
参考文章
- npm 官网
- 前端工程化 – 分析 npm 的包管理机制
- 前端工程化(5):你所须要的 npm 常识储备都在这了
- semver