概述
- npm是什么
- 配置
- npm install
- 版本号规则
- 安装过程
- package.lock.json
- script生命周期
- 常见问题
npm是什么
是用nodejs写的包管理生态系统
- 网站 www.npmjs.com/
- 注册表(npm数据库,60W+package)
- cli工具(命令行)
npm配置
环境变量
windows 安装完node通常会自动设置环境变量PATH
NODE_PATH = XXX\Node\nodejs
PATH = %NODE_PATH%\;%NODE_PATH%\node_modules;%NODE_PATH%\node_global;
如果不配置呢?
error:node不是内部或外部命令,也不是可运行的程序 或批处理文件。
npm配置项
查看npm配置
npm config ls 或 npm config list
上面同样列举了npmrc(npm用户配置文件)的地址
这里面核心的是三项
- 当前的用户信息(username、email...)
- registry - 镜像地址,npm包的下载源(通常会修改为淘宝镜像或者私服镜像)
- prefix - 全局安装目录
修改全局安装目录
npm config set prefix "F:\office_software\nodejs\xxx_global"
修改镜像地址(推荐使用nrm进行多镜像管理)
npm config set registry "https://registry.npm.taobao.org"
另外一个配置就是npm缓存的目录
$ npm config get cache
C:\Users\Administrator\AppData\Roaming\npm-cache
npm-cache存放下载包的缓存(对应sha1计算后的内容也在里面)
修改npm缓存目录
npm config set cache "F:\office_software\nodejs\xxx_cache"
npm install
安装操作
npm install 或 npm i
这两个命令是一样的,一个全写一个简写
npm install 或 npm i
npm会根据package.json配置文件中的依赖配置下载安装到项目下的node_modules目录
-g 或 -global
npm i -g XXX
全局安装,安装后的包位于系统预设目录下
这样就安装在了C:\Users\Administrator\AppData\Roaming\npm\node_modules目录下
-S 或 --save
安装的包将写入package.json里面的dependencies,dependencies:生产环境需要依赖的库
比如:less和scss编译用的包,只有开发时才用得到,实际运行时并不用到,所以就不用放到生产环境依赖包中
-D 或 --save-dev
安装的包将写入packege.json里面的devDependencies,devdependencies:只有开发环境下需要依赖的库
安装本地包
- 安装本地模块
假如npm_pkg包已经写好(里面只要有个package.json就可以),如
npm install ../npm_pkg
通常用来测试本地npm包
- 将包作为一个命令行工具(cli)
npm install ../npm_demo_cli -g
npm install还有很多其他使用方式,不常用,就不一一介绍了。 具体请参照《官方文档》
npm版本号规则
版本号格式
主版本号[MAJOR].次版本号[MINOR].修订号[PATCH]
版本号递增规则如下:
- 主版本号:当做了不兼容的 API 修改,
- 次版本号:当做了向下兼容的功能性新增,
- 修订号:当做了向下兼容的问题修正。先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。
当主版本号升级后,次版本号和修订号需要重置为0,次版本号进行升级后,修订版本需要重置为0。
关于测试版本后缀命名:
- alpha:初期内测版,会有很多bug, 如:1.0.0-alpha.1。
- beta:中期内测,但会持续加入新的功能。
- rc:Release Candidate 系统平台上就是发行候选版本。RC版不会再加入新的功能了,主要着重于除错,处在beta版之后,正式版之前
semver:semantic version 语义化版本
- 固定版本:例如 1.0.1、1.2.1-beta.0 这样表示包的特定版本的字符串
- 范围版本:是对满足特定规则的版本的一种表示,例如 1.0.3-2.3.4、1.x、^0.2、>1.4
语义化说明
用到的语义化字符有: ~、>、<、=、>=、<=、-、||、x、X、*
'^2.1.1' // 2.x最新版本(主版本号锁定)
'~2.1.1' // 2.1.x最新版本好(主和次版本号锁定)
'>2.1' // 高于2.1版本的最新版本
'1.0.0 - 1.2.0' // 两个版本之间最新的版本(必须要有空格)
'*' // 最新的版本号
'3.x' // 对应x部分最新的版本号
npm install默认安装 ^x.x.x类型的版本,用于兼容大版本下最新的版本
npm安装过程
-
执行工程自身preinstall
-
确定首层依赖模块
- 即:package.json中的 dependencies 和 devDependencies
-
获取模块
- 在下载一个模块之前,首先要确定其版本,这是因为 package.json 中往往是 semver版本
- 此时如果版本描述文件(npm-shrinkwrap.json 或 package-lock.json)中有该模块信息直接拿即可
如果npm-shrinkwrap.json和package-lock.json同时存在,则以npm-shrinkwrap.json为主,忽略另一个
- 如果没有则从仓库获取(向registry查询)。如 packaeg.json 中某个包的版本是 ^1.1.0,npm 就会去仓库中获取符合 1.x.x 形式的最新版本。
-
获取模块内容
- 上一步会获取到模块的压缩包地址(resolved 字段),npm 会用此地址检查本地缓存,缓存中有就直接拿,如果没有则从仓库下载
-
查找该模块依赖
- 如果有依赖则回到第1步,如果没有则停止。
-
模块扁平化
-
遍历所有节点,逐个将模块放在根节点下面,也就是 node-modules 的第一层。当发现有重复模块时,则将其丢弃
-
这里需要对重复模块进行一个定义,它指的是模块名相同且 semver 兼容。每个 semver 都对应一段版本允许范围,如果两个模块的版本允许范围存在交集,那么就可以得到一个兼容版本,而不必版本号完全一致,这可以使更多冗余模块在 dedupe 过程中被去掉。
比如
node-modules 下 foo 模块依赖 lodash@^1.0.0,bar 模块依赖 lodash@^1.1.0,则 ^1.1.0 为兼容版本。
而当 foo 依赖 lodash@^2.0.0,bar 依赖 lodash@^1.1.0,则依据 semver 的规则,二者不存在兼容版本。会将一个版本放在 node_modules 中,另一个仍保留在依赖树里。
-
-
安装模块
- 这一步将会更新工程中的 node_modules,并执行模块中的生命周期函数(按照 preinstall、install、postinstall 的顺序)。
-
执行工程自身生命周期
- 当前 npm 工程如果定义了钩子此时会被执行
-
最后一步是生成或更新版本描述文件(package.json和package-lock.json),npm install 过程完成。
安装的npm有子依赖包
如果这个npm还有其他依赖包,会一并安装(只会安装dependencies部分)
devDependencies中的依赖并不会安装
安装的npm包中依赖了同名不同==小版本==的包
A@1
|-B@1.1
|-C@1
D@1
|-B@1.2
E@1
|-B@1.3
这个时候,因为解析的时候,B@1已经在根目录了 那么B@2就会成为D@1中私有模块,即
node_modules
|-A@1
|-B@1.3
|-C@1
|-D@1
|-E@1
安装的npm包中依赖了同名不同==大版本==的包
A@1
|-B@1
|-C@1
D@1
|-B@2
E@1
|-B@2
这个时候,因为解析的时候,B@1已经在根目录了 那么B@2就会成为D@1中私有模块,即
node_modules
|-A@1
|-B@1
|-C@1
|-D@1
|-node_modules
|-B@2
|-E@1
|-node_modules
|-B@2
后面版本的npm包,即使是之前在私有目录中安装过,也同样会重复安装
那同样版本的npm包会多次安装么
package.lock.json
npm 5版本引入了package.lock.json
即在npm install之后会在根目录下生成一个package.lock.json文件
举个例子
{
...
"babel-polyfill": {
"version": "6.26.0",
"resolved": "https://rcnpm.zhuanspirit.com/babel-polyfill/download/babel-polyfill-6.26.0.tgz",
"integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=",
"requires": {
"babel-runtime": "6.26.0",
"core-js": "2.6.5",
"regenerator-runtime": "0.10.5"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.10.5",
"resolved": "https://rcnpm.zhuanspirit.com/regenerator-runtime/download/regenerator-runtime-0.10.5.tgz",
"integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg="
}
}
}
}
结构很简单:
- babel-polyfill: 包名字
- version:安装的版本号
- resolved:npm压缩包下载地址
- integrity:sha1值(一种哈希算法)按内容计算后的值
- requires:所依赖的包信息
- dependencies:requires的模块中所依赖的包,且和根目录中版本不一致的包信息
那它是做什么用的?
锁定安装时包的版本号,并且可以上传到git
保证每个人安装的依赖是一样的
为什么要生成这个文件?
因为npm install -D xxx 之后,默认版本是向后兼容的
比如:
"dependencies": {
"core-js": "^3.2.0",
}
^3.2.0 即表示3.x版本中最新的版本
也就是说package.json中锁定的其实是大版本号,而小版本每次安装都是取最新的。
假如说后面又发布了3.x更高版本,那么后面执行npm install同学安装的就是更高的版本了,比如3.5.0
当然,上面说的版本号规则都是所谓的“规则”,并没有什么强制校验的措施,遵不遵守全凭自觉。所以很有可能出现:
即使是小版本也会出现和之前版本不兼容的情况。
所以,锁定当前的npm包的版本号就很有必要,package.lock.json 也就应运而生了。
那么package.lock.json作用就是:锁定npm包小版本号
问题:那npm install时,如果package.json和package-lock.json都存在,以哪个为主?
其实只有一条规则:==如果package-lock中的版本,在package.json范围内,就以package-lock为主,否则以package.json为主==
比如:以core-js为例子
1)场景一
package.json 中是 "^3.2.0", package-lock.json 中是 "3.1.0"
兼容范围是3.2.0+版本,而3.1.0是在这范围之外(比这小),所以安装时以package.json的^3.2.0为主,就会安装3.x最新的版本(目前是3.5.0)
2)场景二
package.json 中是 "^3.1.0", package-lock.json 中是 "3.2.0"
兼容范围是3.1.0+版本,而3.2.0正符合这个范围,所以安装就按照package-lock.json中指定的3.2.0版本
npm script
生命周期
node为script指令提供了生命周期钩子
- preXXX - XXX命令之前之前调用
- postXXx - XXX命令之后调用
举个例子,比如package.json中:
"scripts": {
"aaa": "echo \"Log: aaa specified\""
}
当执行下面脚本时
npm run aaa
实际上等价于际执行
if (preaaa) npm run preaaa
npm run aaa
if (postaaa) npm run postaaa
所以,进一步实验
"scripts": {
"aaa": "echo \"Log: aaa specified\"",
"preaaa": "echo \"Log: preaaa specified\"",
"postaaa": "echo \"Log: postaaa specified\" & exit 1"
}
结果为:
E:\work\npm_test>npm run aaa
> npm_test@1.0.0 preaaa E:\work\npm_test
> echo "Log: preaaa specified"
"Log: preaaa specified"
> npm_test@1.0.0 aaa E:\work\npm_test
> echo "Log: aaa specified"
"Log: aaa specified"
> npm_test@1.0.0 postaaa E:\work\npm_test
> echo "Log: postaaa specified"
"Log: postaaa specified"
./node_modules/.bin/
npm run执行时,会把./node_modules/.bin/ 目录添加到执行环境的 PATH 变量中
这样,即便只在当前项目里安装了某个包,npm run一样可以调用
传参
执行 script 脚本如果需要传入参数,需要在命令后加 -- 标明, 如:
npm run dev -- -r main
将自定义参数r=main传入dev命令中
process.env
在执行的script 脚本中,可以直接使用process.env对象,它会返回所有和执行环境相关的信息
- npm_lifecycle_event - 正在运行的脚本名称
- npm_package_ - 当前项目 package.json 中指定字段的配置值:如 npm_package_name 获取包名
- npm_package__ - package.json 中嵌套属性:如:npm_package_dependencies_core_js
注:获取属性时,如果是带有中划线的属性,需要改成下划线,如core-js需要改成core_js
npm常见疑问
npm cache clean --force
删除npm_cache下所有缓存文件
删除后,package-lock.json文件中就找不到对应的sha1内容了,就会强制重新从服务器拉取npm包
npm cache verify
重新计算npm_cache下所有缓存文件是否与sha1值匹配,如果不匹配可能删除
npm install发生错误(大招)
删除package.lock.json文件
npm cache clean --force强制删除缓存中的npm包