npm、yarn、pnpm包管理工具
# node、npm 等相关概念
- node:一个基于 Chrome V8 引擎的 JavaScript 运行时;提供了 JavaScript 的运行环境;
- nvm:node.js 版本管理工具;(不同项目可能需要不同版本的 node;可以使用 nvm 来管理 node.js 版本)
- npm:node.js 包管理工具;用来管理 node.js 中的第三方插件;(新版本的 node 在安装的时候,会自动安装对应版本的 npm)
- npx:一个 npm 包执行器,可以使用 npx 来执行各种命令;
- nrm:npm 源的管理工具,可以用来方便的切换 npm 源;
- cnpm:使用的是淘宝的源。用法跟 npm 完全一致;(cnpm 经常会有问题,所以在很多地方不推荐使用)
- yarn:经过重新设计的崭新的 npm 客户端;运行速度显著提升,整个安装时间比 npm 少。一般推荐使用 yarn 代替 npm
# nvm 管理 node.js 版本
在实际的前端开发过程中,可能会经常遇见 node.js 的版本问题,不同的项目需要使用不同的 node.js 版本。直接安装的话,只能安装和使用 node.js 的一个版本。可以使用 nvm 来安装和管理不同版本的 node.js。有意思的是,nvm-windows 是基于 Go 语言来写的。
# nvm 常用命令
nvm ls :列出所有已安装的 node 版本
nvm ls-remote :列出所有远程服务器的版本(官方node version list)
nvm list :列出所有已安装的 node 版本
nvm list available :显示所有可下载的版本
nvm install stable :安装最新版 node
nvm install [node版本号] :安装指定版本 node
nvm uninstall [node版本号] :删除已安装的指定版本
nvm use [node版本号] :切换到指定版本 node
nvm current :当前 node 版本
nvm alias [别名] [node版本号] :给不同的版本号添加别名
nvm unalias [别名] :删除已定义的别名
nvm alias default [node版本号] :设置默认版本
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# npx
npx 是 npm v5.2.0 引入的一条命令( npx ),是一个 npm 包执行器,用来提供一些辅助的功能。
# npx 原理
npx 想要解决的主要问题,就是调用项目内部安装的模块,在没有 npx 之前,调用命令只能在项目脚本和 package.json 的 scripts 字段里面,或者在命令行下调用。
npx 的原理很简单,就是运行的时候,会到 node_modules/.bin 路径和环境变量$PATH里面,检查命令是否存在。由于 npx 会检查环境变量$PATH,所以系统命令也可以调用。
npx 执行流程如下:
- 到 node_modules/.bin 路径检查对应的命令是否存在,找到之后执行;
- 没有找到,就去环境变量 $PATH 里面,检查对应命令是否存在,找到之后执行;
- 还是没有找到,自动下载一个临时的依赖包最新版本在一个临时目录,然后再运行命令,运行完之后删除,不污染全局环境。
# npx 寻址调用
如果一个 npm 包配置了 bin ,当这个包被安装的时候,在项目的 node_modules/.bin 下就有会相应的指令来方便执行。
如果需要调用 next 相关的命令,比如查询 next 版本:
node node_module/.bin/next -v
{
"script": {
"next-v": "node node_module/.bin/next -v"
}
}
2
3
4
5
npm run next-v
npx next -v
// Make sure to add code blocks to your code group
在调用一些安装包的内置命令时,比如在使用 Jest 进行测试单个文件的时候,对于写 npm script 和 手动调用 node_modules 下的命令就不太友好,使用 npx 会方便很多。npx 在寻址调用的时候,会到项目的 node_modules/.bin 路径和系统的 path 中进行查找,检查命令是否存在,如果不存在则会临时下载或运行程序包或进行使用。
# npx 应用场景
# 1. 开启静态服务器
例如本地不存在 http-server 模块,则会自动临时下载安装,然后在当前目录进行启动一个 web 服务,这相当于一次性的命令。
npx http-server # 默认返回根目录下index.htmlnpx http-server -p 3000 # 也可以直接指定端口
# 2. 一次性执行命令
此外,你是否有遇见过想要尝试一些 CLI 工具创建项目,但是却要进行全局安装然后再进行创建,如果这个 CLI 工具只使用一次呢?比如利用 create-react-app 来创建 React 项目,使用 npx 来创建就可以避免这个问题,而且不用担心使用时的升级问题,因为 npx 确保使用最新的生成器或者软件包。
比如以下命令,npx 将 create-react-app 下载到一个临时目录,使用以后再删除,不用全局安装 create-react-app ,运行后不会污染全局环境
npx create-react-app my-react-repo
这样 npx 会自动从注册表中安装 create-react-app 软件包,并调用它,调用完成后又不会保存在全局文件中,因此不会污染全局安装或需要多个步骤。这个特性非常适合生成器之类的软件包,比如 create-react-app , vue-cli , yeoman 等。
# 使用npx创建vue项目
npx -p @vue/cli vue create hello-word
2
# 3. 执行本地命令
比如项目下安装了 mocha ,可以直接用 npx 执行。
npx mocha --version
# 4. 切换 node 版本
要运行的命令不兼容当前的 node 版本时,可使用 npx 切换版;npx 的 -p 选项指定要安装的包,并将其添加到正在运行的 $PATH 中。
npx node@12 -v
# npm 与 yarn 的区别
# 早期的npm
其实在最早期的npm版本(npm v2),npm的设计可以说是非常的简单,在安装依赖的时候会将依赖放到 node_modules文件中; 同时,如果某个直接依赖A依赖于其他的依赖包B,那么依赖B会作为间接依赖,安装到依赖A的文件夹node_modules中,然后可能多个包之间也会有出现同样的依赖递归的,如果项目一旦过大,那么必然会形成一棵巨大的依赖树,依赖包会出现重复,形成嵌套地狱。
那么我们如何去理解"嵌套地狱"呢?
- 首先,项目的依赖树的层级过于深,如果有问题不利于排查和调试;
- 在依赖的分支中,可能会出现同样版本的相互依赖的问题
那么这样的重复问题会带来什么后果呢?
- 首先,会使得安装的结果占据了大量的空间资源,造成了资源的浪费;
- 同时,因为安装的依赖重复,会造成在安装依赖时,安装时间过长;
- 甚至是,因为目录层级过深,导致文件路径过长,会在windows系统下删除node_modules文件,出现删除不掉的情况。
npm 是 node 官方的包管理工具。
早期 npm 设计理念是根据语义化版本:主版本号、次版本号、修订号;解决的是依赖的管理问题。可能会造成依赖黑洞,很多依赖被重复安装,还会造成依赖层级过深,无法共享实例等问题。
后来 npm 实现了依赖树打平,相同的依赖会提升到最上层。原因是 require()来引入外部模块时候,如果是 node 核心模块,会直接返回 node 核心模块;如果不是会判断当前node_modules
文件夹是否有此模块,如果有就返回,没有就递归往上层的目录查找,直到根目录。
上面方案没有完全解决重复安装依赖的问题,如果多个依赖包依赖同一个依赖包的不同版本,那应该提升哪一个依赖版本呢?这样会造成依赖树不稳定。
# yarn
yarn 以扁平的结构来管理依赖,生成 yarn.lock 来保证依赖树的确定性,结合 package.json 一起决定整个依赖树版本。(yarn.lock 的子依赖的版本不是固定的版本)
- 离线模式,直接使用全局缓存中的依赖,而不使用网络请求。
- yarn 是多队列同时下载,npm 是单队列下载。
- 安全校验。
# pnpm
完美解决了依赖重复安装的问题,解决思路是通过软链和硬链。
参考链接:
从 npm 到 yarn 再到 pnpm —— 为什么要使用 pnpm? (opens new window)
字节的一个小问题 npm 和 yarn 不一样吗? (opens new window)