npm
小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
在JavaScript中我们可以通过模块化的方式将代码划分成一个个小的结构
在以后的开发中我们就可以通过模块化的方式来封装自己的代码,并且封装成一个工具
此时我们可能希望分享这个工具或库,但是因为使用了模块化的方式进行开发
所以我们即需要一个网址去发布和下载我们的库,
同时需要一个工具可以自动帮助我们去管理和维护相关的依赖
我们可以通过这个工具来安装,升级,删除我们的工具,库及其相关依赖
这个工具就是npm,而这个网址就是www.npmjs.com/
npm 的 registry
www.npmjs.com/这个网址实际上是npm包的检索和文档查看的网址
真正的npm包是存放在registry.npmjs.org/这个registry地址上的,
我们下载和上传我们的库实际也是上传到这个地址对应的服务器中
项目配置文件
我们每一个项目都会有一个对应的配置文件,无论是前端项目还是后端项目:
- 这个配置文件会记录着你项目的名称、版本号、项目描述等
- 也会记录着你项目所依赖的其他库的信息和依赖库的版本号
在Node环境下面(无论是前端还是后端) 这个配置文件就是package.json
所以我们在开发项目前或者至少在安装相应的依赖前,
需要在项目的根目录下生成package.json
# 生成方式1 --- 自己手动输入对应的配置信息
npm init
# 生成方式2 --- 全部使用默认的配置信息
npm init --yes|-y
常见的配置信息
| 属性名 | 描述 | 默认值 | 说明 | ||||
|---|---|---|---|---|---|---|---|
| name | 项目的名称 | 项目文件夹名 | 全部小写,多字母建议用中横线-分隔,不建议使用点.或下划线_分隔(虽然.和_可以使用) | ||||
| version | 版本号 | 1.0.0 | 遵循semver规范 ---> major.minor.patch | ||||
| description | 描述信息,很多时候是作为项目的基本描述 | “” | |||||
| author | 作者相关信息 | "" | |||||
| license | 开源协议 | ISC | |||||
| private | 当前的项目是否是私有的 | false | 当值为true时,npm是不能发布它的 这是防止私有项目或模块发布出去的方式 | ||||
| main | 程序主入口 | index.js | 这个属性只有在当前项目作为一个模块被其它项目引用时候有用 使用该模块的项目可以根据这个入口文件进行代码的构建 在本地开发的时候,项目的实际主入口文件是由构建工具的配置文件决定的,而不是在 package.json中确定 | ||||
| script | 一些脚本命令,以键值对的形式存在 | test: "xxxx" | 配置后我们可以通过 npm run 命令的key来执行这个命令 对于常用的 start、 test、stop、restart可以省略掉run直接通过 npm start等方式运行例如key为dev的时候,运行该命令需要使用 npm run dev但是如果key是start的时候,运行该命令使用 npm start即可,run可以省略 | ||||
| dependencies | 无论开发环境还是生产环境都需要依赖的包 通常是我们项目实际开发用到的一些库模块 | 安装方式`npm i | install <包名> [--save | --save-prod | --save-production | 省略]<br />我们可以通过npm install --production` 来只安装生产环境下的依赖,而不安装开发环境下的依赖 | |
| devDependencies | 些包在生产环境是不需要的 仅仅只是在开发环境下需要的库 | 安装方式 `npm i | install <包名> --save-dev | -D` | |||
| engines | 指定Node和NPM的版本号 安装的过程中,会先检查对应的引擎版本,如果不符合就会报错 | ||||||
| browserslist | 项目需要适配的浏览器版本信息 可以在多个需要该信息的工具之间共享信息 可以单独抽离成一个配置文件,且具有默认值,所以一般不在 package.json中进行配置 |
版本管理
npm的包通常需要遵从semver版本规范:
semver版本规范是major.minor.patch
| 说明 | 描述 |
|---|---|
| major(主版本号) | 增加了不兼容之前的版本的API |
| minor(次版本号) | 增加了新的API,但是兼容之前的版本 |
| patch(修订号) | 没有新功能,只是进行了问题修正,也就是仅仅只是修复了bug |
^和~之间的区别
| 符号 | 说明 | 例子 |
|---|---|---|
^ | 锁主版本号 | ^x.y.z: 表示x是保持不变的,y和z永远安装最新的版本 |
~ | 锁主版本号和次版本号 | ~x.y.z: 表示x和y保持不变的,z永远安装最新的版本 |
npm install
安装npm包分两种情况:
- 全局安装 (global install):
npm install <包名> -g - 项目(局部)安装 (local install):
npm install <包名>
全局安装
局安装是直接将某个包安装到全局
# 安装指令
npm i <包名> -g
一般情况下,我们是将一个工具库安装到全局,例如webapck,less,typescript等
这些工具会生成对应的可执行指令,并配置好对应的环境变量
这就意味着,我们可以在任意目录下执行这些工具提供的可执行文件
如全局安装完webpack后,我们可以在任意目录层级下使用webpack指令
全局安装的都是一些工具库,一些方法库(如
axios,koa等)是没有必要安装到全局的而且就算安装到了全局,项目也无法找到这些库,
因为在项目中查找包的时候,顶层文件夹是去
/node_modules下进行查找而全局包是安装在
/usr/local/lib/node_modules这个文件夹中的根本不是一个文件夹,所以一些方法库(如
axios,koa等)是没有必要安装到全局的
项目安装
项目安装会在当前目录下生产一个 node_modules 文件夹,
其查找路径为module.paths中所对应的查找路径
console.log(module.paths)
/*
=>
[
'/Users/klaus/Desktop/node-demo/node_modules',
'/Users/klaus/Desktop/node_modules',
'/Users/klaus/node_modules',
'/Users/node_modules',
'/node_modules'
]
*/
局部安装分为开发时依赖和生产时依赖
# 安装开发和生产依赖
# npm install <包名>
# 或 npm i <包名>
# 安装开发依赖
# npm install <包名> --save-dev
# 或 npm i <包名> --save-dev
# 或 npm install <包名> -D
# 或 npm i <包名> -D
自动根据package.json中的依赖(包括开发时依赖和生产时依赖),
在项目根目录下生产node_modules文件夹,并自动去安装对应的依赖
对应的依赖所需要的版本会自动根据package.json中对应包所设置的版本约束信息去下载和引入可以使用的最新版本的包
npm install
# 或 npm i
原理
npm install会检测是有package-lock.json文件:
-
没有lock文件
-
分析依赖关系,这是因为我们可能包会依赖其他的包,并且多个包之间会产生相同依赖的情况
-
从registry仓库中下载压缩包(如果我们设置了镜像,那么会从镜像服务器下载压缩包)
-
获取到压缩包后会对压缩包进行缓存(从npm5开始有的)
-
将压缩包解压到项目的node_modules文件夹中
-
生成对应的
package-lock.json文件
-
-
有lock文件
-
检测lock中包的版本是否和
package.json中一致(会按照semver版本规范检测)- 不一致,那么会重新构建依赖关系,直接会走顶层的流程
-
一致的情况下,会去优先查找缓存
- 没有找到,会从registry仓库下载,直接走顶层流程
-
查找到,会获取缓存中的压缩文件,并且将压缩文件解压到node_modules文件夹中;
-
package-lock.json
| 属性名 | 描述 |
|---|---|
| name | 项目的名称 |
| version | 项目的版本 |
| lockfileVersion | lock文件的版本 |
requires (例如: requires: true) | 模块所依赖的对应模块是存储在requires这个配置对象中的 |
| dependencies | 项目的依赖 |
{
"name": "demo",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true, // 之后版本依赖关系是存储在requires属性中的
// 之前的npm包管理是树结构,这样就会导致npm树中有多个节点其实是重复的,但是被多次安装了
// 所以npm V5.0 开始,将npm中包依赖关系扁平化了,这样就不会导致节点重叠的问题
"dependencies": {
"axios": {
"version": "0.21.4",
"resolved": "https://registry.nlark.com/axios/download/axios-0.21.4.tgz?cache=0&sync_timestamp=1630942582656&other_urls=https%3A%2F%2Fregistry.nlark.com%2Faxios%2Fdownload%2Faxios-0.21.4.tgz",
"integrity": "sha1-xnuQ3AVo5cHPKwuFjEO6KOLtpXU=",
"requires": {
"follow-redirects": "^1.14.0"
}
},
"follow-redirects": {
"version": "1.14.4",
"resolved": "https://registry.nlark.com/follow-redirects/download/follow-redirects-1.14.4.tgz?cache=0&sync_timestamp=1631622129411&other_urls=https%3A%2F%2Fregistry.nlark.com%2Ffollow-redirects%2Fdownload%2Ffollow-redirects-1.14.4.tgz",
"integrity": "sha1-g4/fSKi73XnlLuUfsclOPtmLk3k="
}
}
}
某一个特定包所对应的package-lock.json中的信息
"axios": {
"version": "0.21.4", // 实际安装的版本号
"resolved": "https://registry.nlark.com/axios/download/axios-0.21.4.tgz?cache=0&sync_timestamp=1630942582656&other_urls=https%3A%2F%2Fregistry.nlark.com%2Faxios%2Fdownload%2Faxios-0.21.4.tgz", // 对应的库的压缩包所在的地址
"integrity": "sha1-xnuQ3AVo5cHPKwuFjEO6KOLtpXU=", // 一个秘钥,可以用来在缓存文件夹中寻找到对应的压缩库
"requires": {
"follow-redirects": "^1.14.0" // 所需要的依赖库
}
}
其它命令
其它的一些npm命令可以点击这里进行查看
# 卸载某个依赖包
npm uninstall <包名>
npm uninstall <包名> --save-dev
npm uninstall <包名> -D
# 强制构建 --- 无视package.lock.json和缓存
npm rebuild
# 清除缓存
npm cache clean
# 查看npm缓存所在的文件夹
npm config get cache
yarn
另一个node包管理工具yarn:
yarn是由Facebook、Google、Exponent 和 Tilde 联合推出了一个新的 JS 包管理工具
yarn 最早是为了弥补 npm 的一些缺陷而出现的
cnpm
由于一些特殊的原因,某些情况下我们没办法很好的从 registry.npmjs.org下载下来一些需要的包
解决方法1:设置npm的registry地址
# 获取镜像地址
npm config get registry
# 设置镜像地址
pm config set registry https://registry.npm.taobao.org
但有的时候,我们不太希望随意修改npm原本从官方下来包的渠道, 这个时候,我们可以使用cnpm
# 下载cnpm 并直接修改registry
npm install -g cnpm --registry=https://registry.npm.taobao.org
# 获取cnpm registry地址
cnpm config get registry
# cnpm 设置registry地址
cnpm config ser registry
npx
npx是npm V5.2之后自带的一个命令
npx 想要解决的主要问题,就是调用项目内部安装的模块
一般来说,我们在项目级别安装了某个可执行的npm包 (也就意味着这个包在node_modules/.bin文件夹下会有对应脚本的软连接 ), 例如webpack,http=server, mocha等
如果我们直接在命令行中对这些脚本进行运行,默认情况下是去全局环境下寻找对应的指令
# 此时运行的webpack是全局的webpack,并不是局部的webpack
$ webpack
但是又的时候我妈需要调用项目内部安装的模块
在没有npx之前,我们的做法有两种
- 将指令配置到
package.json的scripts字段中
"scripts": {
"start": "webpack serve" // => 此时运行的webpack就会优先查找项目级别的webpack来进行执行
}
- 直接执行对应的脚本文件
# 找到本地的webpack对应的运行脚本进行执行
$ node-modules/.bin/webpack --version
npx 就是想解决这个问题,让项目内部安装的模块用起来更方便, 同时可以避免全局安装的模块
# 当我们使用npx去查找某一个包的时候
# 1. 现在项目的node_modules中进行查找,如果查找到就使用,没有找到执行第二步
# 2. 去环境变量$path中进行查找,如果查找到就使用,没有找到,没有找到执行第三步
# 3. 没有安装,将对应的包安装到一个临时目录中后进行相应的使用,在对应的包使用完毕以后,会自动移除对于的临时目录
$ npx webpack --version # 此时运行的webpack会优先运行项目级别的webpack