Skip to content

包管理工具

第一章:NPM

官方文档:npm 文档

中文文档:npm 中文文档 | Nodejs.cn 旗下

一、概述

1. 基本概念

模块(module)

通常以单个文件形式存在的功能片段,入口文件通常称之为入口模块或主模块。

库(library,简称 lib)

以一个或多个模块组成的完整 (功能块 / 工具) ,为开发中某一方面的问题提供完整的解决方案。

包(package)

包含元数据的库,这些元数据包括:名称、描述、git 主页、许可证协议、作者、依赖等等。

2. 介绍

NPM 全称 Node Package Manager(Node 的包管理器)是一个应用程序。

3. 作用

通过 NPM 可以对 Node 的工具包进行搜索、下载、安装、删除、上传。借助别人写好的包,可以让开发更加方便。

常见的使用场景有以下 3 种:

  • 允许用户从 NPM 服务器下载别人编写的第三方包到本地使用。
  • 允许用户从 NPM 服务器下载并安装别人编写的命令行程序到本地使用。
  • 允许用户将自己编写的包上传到 NPM 服务器供别人使用。

4. 组成

npm 由三部分组成:

  • registry:入口
    • 可以把它想象成一个庞大的数据库
    • 第三方库的开发者,将自己的库按照 npm 的规范,打包上传到数据库中
    • 使用者通过统一的地址下载第三方包
  • 官网:https://www.npmjs.com
    • 查询包
    • 注册、登录、管理个人信息
  • CLI:command-line interface 命令行接口
    • 安装好 npm 后,通过 CLI 来使用 npm 的各种功能

5. 安装

安装 Node.js 时会自动安装 npm,无需额外安装。

二、NPM 常用操作命令

1. 初始化

项目中没有 package.json,我们需要进行初始化,创建 package.json 文件。

sh
npm init

# 以下命令可以快速初始化
npm init --yes
npm init -y

2. 包的管理

1)搜索包
bash
npm search 包名
npm s 包名

该命令使用频率不高,一般在搜索包的时候,会到 https://www.npmjs.com 搜索。

2)安装包
[1] 本地安装
bash
npm install 包名
npm i 包名

# 安装并在 package.json 中保存包的信息 (dependencies 属性, 表示生成依赖)
npm install 包名 --save
npm install 包名 -S

# 安装并在 package.json 中保存包的信息 (devDependencies 属性, 表示开发依赖)
npm install babel --save-dev
npm install babel -D

# 解决对等依赖问题 peer
npm i --legacy-peer-deps

注意:

  • 安装一个包的时候,npm 会自动管理依赖,它会下载该包的依赖包到 node_modules 目录中。
  • 如果本地安装的包带有 CLI,npm 会将它的 CLI 脚本文件放置到 node_modules/.bin 下,使用命令 npx 命令名 即可调用。
  • 随着开发的进展,node_modules 目录会变得异常庞大,目录下的内容不适合直接传输到生产环境,因此通常使用 .gitignore 文件忽略该目录中的内容。
  • 本地安装适用于绝大部分的包,它会在当前目录及其子目录中发挥作用。通常在项目的根目录中使用本地安装。
  • Node.js v6 以及以上版本的 npm,安装包时会自动保存在 dependencies 中,可以不用写 --save。
  • 当我们拿到某个项目后,一般都需要执行 npm i 来安所有依赖。

包安装完成之后文件夹下会增加一个文件夹和一个文件:

  • node_modules 文件夹:存放下载的包。
  • package-lock.json:包的锁文件,用来锁定包的版本。
[2] 生产依赖与开发依赖

生产环境与开发环境

开发环境是程序员 专门用来写代码 的环境,一般是指程序员的电脑,开发环境的项目一般 只能程序员自己访问

生产环境是项目 代码正式运行 的环境,一般是指正式的服务器电脑,生产环境的项目一般 每个客户都可以访问

总结:开发依赖是只在开发阶段使用的依赖包,而生产依赖是开发阶段和最终上线运行阶段都用到的依赖包。

我们可以在安装时设置选项来区分依赖的类型,目前分为两类:

类型命令补充
生产依赖npm i -S 包名
npm i --save 包名
-S 等效于 --save,-S 是默认选项
包信息保存在 package.json 中 dependencies 属性
开发依赖npm i -D 包名
npm i --save-dev 包名
-D 等效于 --save-dev
包信息保存在 package.json 中 devDependencies 属性
[3] 全局安装

我们可以执行安装选项 -g 进行全局安装。

shell
npm install --global 包名
npm i -g 包名

npm i -g nodemon 全局安装完成之后就可以在命令行的任何位置运行 nodemon 命令。该命令的作用是自动重启 node 应用程序。

说明:

① 全局安装的包并非所有工程可用,它仅提供全局的 CLI 工具。

② 可以通过 npm root -gnpm config get prefix 可以查看全局安装的包所在位置。

③ 默认安装在 C:/Users/你的用户名/AppData/Roaming/npm 位置。

[4] 安装指定版本的包

项目中可能会遇到版本不匹配的情况,有时就需要安装指定版本的包,可以使用下面的命令。

bash
# 格式
npm i <包名@版本>

# 示例
npm i jquery@1.11.2

上面在的命令执行后,会在 package.json 文件中增加一个依赖,且版本号是 ^1.11.2,怎么在安装该包的时候去掉 ^

shell
npm install --save-exact 包名
npm install -E 包名
3)查询

查询包安装路径

shell
npm root [-g]

查看包信息

shell
npm view 包名 [子信息]
## view aliases:v info show

查询安装的包,会列出 node_modules 中的目录结构。

shell
npm list [-g] [--depth=依赖深度]
## list aliases: ls  la  ll
4)更新包
bash
# 更新包
npm update [-g] [包名]
# update aliases: up, upgrade

npm outdated        # 查看当前本地安装的包哪些需要更新
npm outdated -g     # 查看当前全局安装的包哪些需要更新

注意:更新本地安装的包,会受到 pakeage.json 中版本设置的约束;更新全局安装的包会直接更新到最新版。

5)删除依赖

项目中可能需要删除某些不需要的包,可以使用下面的命令。

shell
npm uninstall [-g] 包名

# uninstall aliases: remove, rm, r, un, unlink

3. 安装项目依赖

如果项目中已经存在 package.json,可以根据 package.json 中的依赖声明,安装工具包。

bash
# 本地安装所有依赖 dependencies + devDependencies
npm install
npm i

# 仅安装生产环境的依赖 dependencies
npm install --production    # 只安装生产环境依赖
npm i --production

4. 配置命令别名

通过配置命令别名可以更简单的执行命令。步骤如下:

配置 package.json 中的 scripts 属性。

json
{
  ......
  "scripts": {
    "server": "node server.js",
    "start": "node index.js",
  },
  ......
}

配置完成之后,可以使用别名执行命令。

shell
npm run server
npm run start

不仅如此,npm 还对某些常用的脚本名称进行了简化,下面的脚本名称是不需要使用 run 的:

  • start
  • stop
  • test

一些细节:

  • 脚本中可以省略 npx
  • start 脚本有默认值:node server.js

补充说明:
npm start 是项目中常用的一个命令,一般用来启动项目。
npm run 有自动向上级目录查找的特性,跟 require 函数也一样。
对于陌生的项目,可以通过查看 scripts 属性来参考项目的一些操作。

5. 元信息查询

1)npm 配置

npm config 命令可用于更新、查看和编辑用户和全局 npmrc 文件的内容。

通过下面的命令可以查询目前生效的各种配置。

shell
# 不加 -l 或 --json, 只会列出修改的配置项, 使用默认值的配置项不会展示
# -l: 以列表形式展示所有配置项; --json: 以json格式展示所有配置项
npm config ls [-l] [--json]

另外,可以通过下面的命令操作配置。

① 获取某个配置项

shell
npm config get 配置项

② 设置某个配置项

shell
npm config set 配置项=值

③ 移除某个配置项

shell
npm config delete 配置项
2)缓存管理
bash
npm config get cache                 # 查看缓存路径
npm config set cache "D:\dev\cache\npm-cache"  # 设置新的缓存目录
npm cache clean --force              # force 表示强制清除缓存
npm cache verify                     # 查看缓存大小和内容
3)查看 npm 的版本
sh
npm -v

6. 管理发布包

1)创建与发布

可以将自己开发的工具包发布到 npm 服务上,方便自己和其他开发者使用,操作步骤如下:

① 创建文件夹,并创建文件 index.js, 在文件中声明函数,使用 module.exports 暴露。

② npm 初始化工具包,package.json 填写包的信息(包的名字是唯一的)。

③ 注册账号:https://www.npmjs.com/signup

④ 激活账号(一定要激活账号)。

⑤ 修改为官方的官方镜像(命令行中运行 nrm use npm)。

⑥ 命令行下 npm login 填写相关用户信息。

⑦ 命令行下 npm publish 提交包 👌

2)更新包

后续可以对自己发布的包进行更新,操作步骤如下:

① 更新包中的代码。

② 测试代码是否可用。

③ 修改 package.json 中的版本号。

④ 发布更新 npm publish

3)删除包

执行如下命令删除包。

shell
npm unpublish --force

删除包需要满足一定的条件,参见:https://docs.npmjs.com/policies/unpublish

4)其他

使用命令 npm whoami 查看当前登录的账号。

使用命令 npm logout 注销。

三、npm 相关配置

  • prefix:在全局模式下,指的是安装 node 可执行文件的文件夹。否则,指的是包含 package.json 文件或 node_modules 文件夹的最近上级文件夹。

四、配置文件

1. package.json

这是一个项目的主要配置文件,它包含了许多重要的信息和配置选项。最重要的作用是记录当前工程的依赖。

  • name:项目的名称。

  • version:项目的版本号。

    格式:版本规范:主版本号.次版本号.补丁版本号

    • 主版本号:仅当程序发生了重大变化时才会增长,如新增了重要功能、新增了大量的 API、技术架构发生了重大变化。
    • 次版本号:仅当程序发生了一些小变化时才会增长,如新增了一些小功能、新增了一些辅助型的 API。
    • 补丁版本号:仅当解决了一些 bug 或进行了一些局部优化时更新,如修复了某个函数的 bug、提升了某个函数的运行效率。
  • description:项目的描述。

  • homepage:官网地址。

  • author:包的作者,必须是有效的 npm 账户名,书写规范是 account <mail>,例如:zhangsan <zhangsan@gmail.com>,不正确的账号和邮箱可能导致发布包时失败。

  • repository:包的仓储地址,通常指 git 或 svn 的地址,它是一个对象。

    • type:仓储类型,git 或 svn。
    • url:地址。
  • keywords:搜索关键字,发布包后,可以通过该数组中的关键字搜索到包。

  • main:包的入口文件,使用包的人默认从该入口文件导入包的内容。

  • scripts:用于运行的脚本命令,例如 npm start / npm run startnpm test 等。

  • private:如果设置为 true,npm 会阻止发布这个包。

  • dependencies:项目运行所需的依赖包及其版本号。

  • devDependencies:项目开发所需的依赖包及其版本号。

  • peerDependencies:项目的同级依赖,通常用于库或插件的开发。

  • optionalDependencies:项目的可选依赖。这些依赖在安装时如果失败,npm 不会报错,而是会继续安装其他的包。

  • license:该项目使用的开源协议。

2. package-lock.json

1)语义版本
符号描述示例示例描述
>大于某个版本>1.2.1大于 1.2.1 版本
>=大于等于某个版本>=1.2.1大于等于 1.2.1 版本
<小于某个版本<1.2.1小于 1.2.1 版本
<=小于等于某个版本<=1.2.1小于等于 1.2.1 版本
-介于两个版本之间1.2.1 - 1.4.5介于 1.2.1 和 1.4.5 之间
x不固定的版本号1.3.x只要保证主版本号是 1,次版本号是 3 即可
~补丁版本号可增~1.3.4保证主版本号是 1,次版本号是 3,补丁版本号大于等于 4
^次版本和补丁版本可增^1.3.4保证主版本号是 1,次版本号可以大于等于 3,补丁版本号可以大于等于 4
*最新版本*始终安装最新版本
||组合多个版本范围例如:`< 1.0.0
2)避免还原的差异

版本依赖控制始终是一个两难的问题。

如果允许版本增加,可以让依赖包的 bug 得以修复(补丁版本号),可能也会带来一些意外的惊喜(次版本号),但同样可能带来不确定的风险(新的 bug)。如果不允许版本增加,可以获得最好的稳定性,但失去了依赖包自我优化的能力。

而有的时候情况更加复杂,如果依赖包升级后,依赖也发生了变化,会有更多不确定的情况出现。

基于此,npm 在安装包的时候,会自动生成一个 package-lock.json 文件,该文件记录了安装包时的确切依赖关系。

当移植工程时,如果移植了 package-lock.json 文件,恢复安装时,会按照 package-lock.json 文件中的确切依赖进行安装,最大限度的避免了差异。


package-lock.json 文件是在你运行 npm install 命令时自动生成的一个文件(如果这个文件不存在的话)。这个文件记录了安装的每一个包的确切版本号,包括所有的依赖包。这意味着,不管何时你或者其他人在同一个项目中运行 npm install,都会得到完全相同版本的依赖。

这个文件的主要目的是为了确保项目的依赖的一致性,使得所有的开发者和 CI/CD 系统都能使用完全相同版本的依赖,从而避免了"在我机器上可以运行"这样的问题。

3)配置项
  • name:项目的名称。
  • version:项目的版本号。
  • lockfileVersion:lock 文件的版本号,npm 会根据这个版本号来确定如何读取和写入这个文件。
  • packages:项目的所有依赖,包括直接依赖和间接依赖。每一个依赖都包含了版本号、来源、完整性校验等信息。
  • requires:记录并锁定某个包所依赖的其他包(即子依赖)及其版本。

通常不需要手动编辑 package-lock.json 文件,npm 会自动管理这个文件。当添加、更新或者删除依赖时,npm 会相应地更新这个文件。

3. .npmrc

npm 的配置设置来自命令行、环境变量和 .npmrc 文件。具体 .npmrc 文件存放位置:

  • 每个项目配置文件
  • 每个用户配置文件(~/.npmrc
  • 全局配置文件($PREFIX/etc/npmrc
  • npm 内置配置文件 (/path/to/npm/npmrc)

在实际中,上面这些配置文件都会加载,只是优先级不一样。

.npmrc 文件中的配置项优先级由低到高依次是:npm 内置配置文件 < 全局配置 < 用户配置 < 项目配置。

其中一些常见的配置项包括:

  • registry:设置 npm 的注册表 URL,例如 registry=https://registry.npmjs.org/
  • proxy 和 https-proxy:设置 npm 的代理服务器,例如 proxy=http://127.0.0.1:7890/https-proxy=https://xxx.xxx:7890/
  • prefix:设置全局安装的路径,例如 prefix=/usr/local
  • save-exact:设置是否在安装包时保存精确的版本号,例如 save-exact=true
  • always-auth:设置是否总是需要认证,例如 always-auth=true

小技巧:此处的配置也可以参考 pnpm:认证与仓库设置 (.npmrc)

4. npm-shrinkwrap.json

npm-shrinkwrap.jsonpackage-lock.json:这两个文件都是用来锁定项目依赖的版本的。它们会列出项目的所有依赖,包括直接依赖和间接依赖,以及每个依赖的精确版本号。这样可以确保在不同的环境中安装相同的依赖。

如果包根目录中同时存在 package-lock.jsonnpm-shrinkwrap.json,则会优先使用 npm-shrinkwrap.json

区别:

  • npm-shrinkwrap.json 可以在发布软件包时包含在内。
  • npm-shrinkwrap.json 是由 npm shrinkwrap 创建的文件。

五、npm 下 node_modules 结构

1. npm i 发生了什么

在本地安装时,npm 会首先尝试找到一个合适的 prefix 文件夹。这样,即使你恰好 cd 进入到了其他文件夹,npm install foo@1.2.3 也会安装到合理的根目录下。

从当前工作目录($PWD)开始,npm 会向上遍历文件夹树,查找包含 package.json 文件或 node_modules 文件夹的目录。如果找到这样的目录,它将被视为运行 npm 命令的“当前目录”。

如果没有找到包根目录,则使用当前文件夹。

当你运行 npm install foo@1.2.3 时,包会被加载到缓存中,然后解压到 ./node_modules/foo。接着,foo 的任何依赖也会以类似的方式解压到 ./node_modules/foo/node_modules/...

任何二进制文件都被符号链接到 ./node_modules/.bin/,以便在需要时可以被 npm 脚本找到。

2. 目录结构原则

1)有哪些
  • 循环依赖:会向上遍历目录寻找 node_modules 文件夹。因此,在每个阶段,如果某个包已经安装在上级的 node_modules 文件夹中,那么它就不会在当前目录下再次安装。

  • 冲突:上面的方式仅在相同版本会安装在多个嵌套的 node_modules 文件夹中时使用。如果两个 “a” 包是不同版本,仍然可以存在 a/node_modules/b/node_modules/a

    面对这种情况,在 node_modules 目录中,不会使用扁平的目录结构,而会形成嵌套的目录,如下图:

    ├── node_modules
    │   ├── a 
    │   │   ├── node_modules
    │   │   │   ├── c
    │   │   │   |   |—— c包的文件
    │   │   │── a包的文件     
    │   ├── b 
    │   │   ├── node_modules
    │   │   │   ├── c
    │   │   │   |   |—— c包的文件
    │   │   │── b包的文件
  • 文件夹简约:在尽可能高的层级安装依赖,即在本地化的“target”文件夹下方(提升)。自版本 3 起,npm 默认会提升依赖。

2)例子

通过上面的三个原则,说下目录结构是什么样子的?

foo
+-- node_modules
    +-- blerg (1.2.5) <---[A]
    +-- bar (1.2.3) <---[B]
    |   +-- node_modules
    |       +-- baz (2.0.2) <---[C]
    +-- asdf (2.3.4)
    +-- baz (1.2.3) <---[D]
    +-- quux (3.2.0) <---[E]
点我查看答案
foo
+-- node_modules
    +-- blerg (1.2.5) <---[A]
    +-- bar (1.2.3) <---[B]
    |   +-- node_modules
    |       +-- baz (2.0.2) <---[C]
    +-- asdf (2.3.4)
    +-- baz (1.2.3) <---[D]
    +-- quux (3.2.0) <---[E]

第二章:yarn

一、简介

1. 介绍

yarn 是由 Facebook 在 2016 年推出的新的 Javascript 包管理工具。官方网址:https://yarnpkg.com

2. 特点

官方宣称的一些特点。

  • 速度超快:yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。同时利用并行下载以最大化资源利用率,因此安装速度更快。
  • 超级安全:在执行代码之前,yarn 会通过算法校验每个安装包的完整性。
  • 超级可靠:使用详细、简洁的锁文件格式(npm 的锁文件为 package-lock.json;yarn 的锁文件为 yarn.lock)和明确的安装算法,yarn 能够保证在不同系统上无差异的工作。

3. 安装

npm 安装 yarn

shell
npm i -g yarn

msi 安装包安装

https://classic.yarnpkg.com/en/docs/install#windows-stable

提前需要安装 Node.js。

4. yarn 配置淘宝镜像

方法一:可以通过如下命令配置淘宝镜像。

bash
yarn config set registry https://registry.npmmirror.com

可以通过 yarn config list 查看 yarn 的配置项。yarn config get registry 查看当前 yarn 源。

方法二:跟 npm 与 cnpm 的关系一样,可以为 yarn 设置国内的淘宝镜像,提升安装的速度。

bash
npm install cyarn -g --registry "https://registry.npmmirror.com"

配置后,只需将 yarn 改为 cyarn 使用即可。

二、常用命令

NPMYarn
npm --versionyarn --version
npm init [--yes/-y]yarn init [--yes/-y]
npm iyarn
install 是 yarn 命令的默认行为,所以无论你是运行 yarn 还是 yarn install,都会安装项目的所有依赖项。
npm install [--production]yarn install [--production/--prod]
npm run <别名>yarn [run] <别名>
npm install --save <包名>yarn add <包名>
npm install <包名>@[version]yarn add <包名>@[version]
npm install [--save-dev/-D] <包名>yarn add <包名> [--dev/-D]
npm install [--save-exact/-E] <包名>yarn add <包名>
npm i -g <包名>yarn global add <包名>
npm uninstall [--save] <包名>yarn remove <包名>
npm uninstall [--save-dev] <包名>yarn remove <包名>
npm uninstall --save-optional <包名>yarn remove <包名>
npm uninstall -g [package]yarn global remove <包名>
npm outdated [-g]yarn [global] outdated
npm update [-g] <包名>yarn [global] upgrade <包名>
npm root [-g]yarn [global] bin
npm view <包名> [子字段]yarn info <包名> [子字段]
npm list [-g] [--depth=依赖深度]yarn [global] list [--depth=依赖深度]
npm rebuildyarn install --force
npm cache cleanyarn cache clean
rm-rf node_modules && npm installyarn upgrade

思考:全局安装的包在任意位置找不到命令,怎么办?

配置 path 环境。yarn 全局安装包的位置在 C:/Users/你的用户名/AppData/Local/Yarn/bin,可以通过 yarn global bin 来查看。

三、特别礼物

在终端命令上,yarn 不仅仅是对 npm 的命令做了一个改名,还增加了一些原本没有的命令,这些命令在某些时候使用起来非常方便。

yarn check

使用 yarn check 命令,可以验证 package.json 文件的依赖记录和 lock 文件是否一致。这对于防止篡改非常有用。

yarn audit

使用 yarn audit 命令,可以检查本地安装的包有哪些已知漏洞,以表格的形式列出,漏洞级别分为以下几种:

  • INFO:信息级别
  • LOW:低级别
  • MODERATE:中级别
  • HIGH:高级别
  • CRITICAL:关键级别

yarn why

使用 yarn why <包名> 命令,可以在控制台打印出为什么安装了这个包,哪些包会用到它。

yarn create

非常有趣的命令。

今后,我们会学习一些脚手架,所谓脚手架,就是使用一个命令来搭建一个工程结构。

过去,我们都是使用如下的做法:

  1. 全局安装脚手架工具
  2. 使用全局命令搭建脚手架

由于大部分脚手架工具都是以 create-xxx 的方式命名的,比如 react 的官方脚手架名称为 create-react-app

因此,可以使用 yarn create 命令来一步完成安装和搭建。例如:

shell
yarn create react-app my-app

# 等同于下面的两条命令
yarn global add create-react-app
create-react-app my-app

第三章:pnpm

一、简介

1. 什么是 pnpm?

pnpm 是一个 Node.js 包管理器,类似于 npm 和 yarn。它的主要特点是高效的包存储方式。当多个项目依赖同一个包版本时,pnpm 不会像 npm 或 yarn 那样为每个项目复制一份包,而是将包存储在一个共享的地方,并通过硬链接或符号链接的方式引用它。这种方式可以节省大量的磁盘空间,同时也可以加快安装速度。

一句话概括:pnpm 代表 performant(高性能的)npm。

pnpm 还有其他一些特点,例如:

  • 它严格遵守 node_modules 的结构:如果一个包没有在项目的 package.json 文件中声明为依赖,那么它就不会出现在 node_modules 目录中。
  • 它有一个强大的命令行界面,可以很方便地进行包的安装、卸载、更新和查询。
  • 它支持 npm 的所有命令和特性,包括 npm scripts、npm shrinkwrap、npm audit 等。

pnpm 的这些特性使得它在一些场景中比 npm 和 yarn 更有优势。

中文文档:pnpm 中文网

2. 原理

1)百宝锦囊
[1] 硬链接和软链接

硬链接(hard link)是电脑文件系统中的多个文件平等地共享同一个文件存储单元。删除一个文件名字后,还可以用其它名字继续访问该文件。

符号链接 / 软链接(soft link、Symbolic link)是一类特殊的文件。其包含有一条以绝对路径或者相对路径的形式指向其它文件或者目录的引用。

[2] node 环境对硬链接和符号链接的处理

硬链接:硬链接是一个实实在在的文件,Node.js 不对其做任何特殊处理,也无法区别对待,实际上,Node.js 根本无从知晓该文件是不是一个硬链接。

符号链接:由于符号链接指向的是另一个文件或目录,当 Node.js 执行符号链接下的 JS 文件时,会使用原始路径。

[3] 非扁平的 node_modules

参考这篇文章:Why should we use pnpm? by @ZoltanKochan

NPM 版本 3 之前:

node_modules
└─ foo
   ├─ index.js
   ├─ package.json
   └─ node_modules
      └─ bar
         ├─ index.js
         └─ package.json

问题:① 包经常创建太深的依赖关系树;② 当在不同的依赖项中需要包时,它们被复制粘贴了几次。

为了解决这些问题,npm 重新考虑了 node_modules 结构并提出了扁平化。 有了 npm@3

node_modules
├─ foo
|  ├─ index.js
|  └─ package.json
└─ bar
   ├─ index.js
   └─ package.json


pnpm 原理,一句话就是:pnpm 创建非扁平的 node_modules 目录。

首先,pnpm 会将所有的包硬链接到 .pnpm 文件夹下的对应子文件夹中。例如,如果你安装了 bar@1.0.0,它依赖于 foo@1.0.0,那么 pnpm 会将这两个包硬链接到 .pnpm 文件夹下的 foo@1.0.0/node_modules/bar 和 bar@1.0.0/node_modules/foo。

然后,pnpm 会创建符号链接来构建依赖关系。例如,foo 会被符号链接到 bar@1.0.0/node_modules 文件夹,表示 bar 依赖于 foo。

最后,对于直接依赖的包,例如 bar,pnpm 会将其符号链接到根 node_modules 文件夹。

这种方式的好处是,每个包只能访问到它依赖的包,而不能访问到其他不相关的包。这样可以避免一些由于错误访问了不应该访问的包而导致的问题。==> 很好的解决了分身问题和幽灵依赖问题。

2)实战开始

准备

先创建一个项目,使用 pnpm 管理包。之后添加依赖。

powershell
Get-ExecutionPolicy

set-ExecutionPolicy remoteSigned

pnpm --version

pnpm init

pnpm add -D nodemon

开始分析依赖结构

第一条关系

powershell
Get-Item "D:\VSCode\111\node_modules\nodemon" | Select-Object FullName, Target, LinkType | format-list


FullName : D:\VSCode\111\node_modules\nodemon
Target   : {D:\VSCode\111\node_modules\.pnpm\nodemon@3.1.13\node_modules\nodemon}
LinkType : Junction

发现关系:D:\VSCode\111\node_modules\nodemon --> D:\VSCode\111\node_modules\.pnpm\nodemon@3.1.13\node_modules\nodemon\

powershell
Get-Item "D:\VSCode\111\node_modules\.pnpm\nodemon@3.1.13\node_modules\nodemon\package.json" | Select-Object FullName, Target, LinkType | format-list


FullName : D:\VSCode\111\node_modules\.pnpm\nodemon@3.1.13\node_modules\nodemon\pack
           age.json
Target   : {D:\pnpm-store\store\v10\files\bc\2e69e0b8f16ba740b611b5fcc0cec0fca024a83
           bb737fbbd3eff5392aacf82e930a8a75f17abe2a48a0e0bcf37ff52626504438d5dd3f73f
           89c6f2463bedf4}
LinkType : HardLink

powershell
cmd /c "fsutil hardlink list D:\VSCode\111\node_modules\.pnpm\nodemon@3.1.13\node_modules\nodemon\package.json" | format-list


\pnpm-store\store\v10\files\bc\2e69e0b8f16ba740b611b5fcc0cec0fca024a83bb737fbbd3eff5392aacf82e930a8a75f17abe2a48a0e0bcf37ff52626504438d5dd3f73f89c6f2463bedf4
\VSCode\111\node_modules\.pnpm\nodemon@3.1.13\node_modules\nodemon\package.json

发现 D:\VSCode\111\node_modules\.pnpm\nodemon@3.1.13\node_modules\nodemon 下的文件其实硬链接到 pnpm 存储库中。

第二条关系

powershell
Get-Item "D:\VSCode\111\node_modules\.pnpm\nodemon@3.1.13\node_modules\chokidar" | Select-Object FullName, Target, LinkType | format-list


FullName : D:\VSCode\111\node_modules\.pnpm\nodemon@3.1.13\node_modules\chokidar
Target   : {D:\VSCode\111\node_modules\.pnpm\chokidar@3.6.0\node_modules\chokidar}
LinkType : Junction

发现关系:D:\VSCode\111\node_modules\.pnpm\nodemon@3.1.13\node_modules\chokidar --> D:\VSCode\111\node_modules\.pnpm\chokidar@3.6.0\node_modules\chokidar\

powershell
Get-Item "D:\VSCode\111\node_modules\.pnpm\chokidar@3.6.0\node_modules\chokidar\package.json" | Select-Object FullName, Target, LinkType | format-list


FullName : D:\VSCode\111\node_modules\.pnpm\chokidar@3.6.0\node_modules\chokidar\pac
           kage.json
Target   : {D:\pnpm-store\store\v10\files\c4\ed6d522c3412ca4c100064e9fef549b1c7a86b4
           127cd2fb98468b4d505f6c79bc0b9bb539ca375f354fc75ebbe200d5f813845a36f54f598
           6e3e63ea97ea3a, D:\VSCode\vue3-admin-project\node_modules\.pnpm\chokidar@
           3.6.0\node_modules\chokidar\package.json}
LinkType : HardLink

发现 D:\VSCode\111\node_modules\.pnpm\chokidar@3.6.0\node_modules\chokidar 下的文件其实硬链接到 pnpm 存储库中。

3. 安装

1️⃣ 通过 npm 安装

bash
npm install -g pnpm

2️⃣ 设置 $XDG_DATA_HOME 环境变量。意思是 pnpm 所产生的文件存放在哪里(包括存储库)。

3️⃣ 其他环境变量:$XDG_CONFIG_HOME$XDG_CACHE_HOME$XDG_STATE_HOME

官网还提供了其他方式来安装 pnpm:https://pnpm.io/zh/installation

二、使用

1. 初始化

1)pnpm init

作用:创建一个 package.json 文件。

2)pnpm install

别名:i

作用:用于安装项目的所有依赖项。

选项:

  • --force:强制重新安装依赖(重新获取存储中修改的软件包、重新创建由不兼容版本的 pnpm 创建的锁文件和(或)模块目录、 安装所有 optionalDependencies,即使它们不满足当前环境,如 cpu、os、arch)。

  • --lockfile-only:在 CI 环境中,如果存在锁文件但需要更新,那么安装会失败。可以添加这个选项来仅更新 pnpm-lock.yaml

  • --fix-lockfile:自动修复损坏的锁定文件条目。

  • --shamefully-hoist:创建一个扁平的 node_modules 结构,类似于 npm 或 yarn。

2. 管理依赖常用命令

1)pnpm add <pkg>
命令含义
pnpm add pkg保存到 dependencies
pnpm add -D pkg保存到 devDependencies
pnpm add -O pkg保存到 optionalDependencies
pnpm add -g pkg安装全局依赖
pnpm add pkg@版本号安装指定版本
2)pnpm update

别名:up、upgrade

选项:

  • -g:更新全局安装的依赖包。
命令含义
pnpm update更新所有依赖项,遵守 package.json 中指定的范围
pnpm up --latest将所有依赖项更新到最新版本
pnpm up foo@2将 foo 更新到 v2 上的最新版本
pnpm up "@babel/*"更新 @babel 范围内的所有依赖项
3)pnpm remove

别名:rm、uninstall、un

选项:

  • --global, -g

  • --save-dev, -D

  • --save-optional, -O

  • --save-prod, -P

4)pnpm prune

作用:移除不需要的软件包。

选项:

  • --prod:删除在 devDependencies 中的包。

2. 查看依赖

命令解释
pnpm audit检查已安装程序包的已知安全问题。
pnpm outdated检查过期的包。
pnpm list此命令将以树结构的形式输出已安装的所有软件包的版本以及及其依赖项。
pnpm why开发中不知道这个包是谁安装的,可以使用这个命令。

3. 运行脚本

1)pnpm run

所有脚本都会被别名为 pnpm 命令,因此可以省略 run。

2)pnpm create

create-* 启动模板创建一个项目。

三、管理 pnpm 文件

1. 命令

1)pnpm store

命令:

  • path:返回激活的存储目录的路径。

    powershell
    pnpm store path
    D:\pnpm-store\store\v10
  • prune:从存储中删除未引用的包。

  • add:功能上等同于 pnpm add,不同之处在于只把包加入存储中。

2)pnpm root

作用:打印有效的存放模块的目录。

选项:

  • --global, -g:打印全局软件包的安装目录。

    默认安装在 C:\Users\用户名\AppData\Local\pnpm\global\

3)pnpm config

别名:c

作用:管理配置文件。

bash
# 设置下载包所使用的源
pnpm config set registry https://registry.npmmirror.com

可以使用下面的命令来设置全局存储的位置:

bash
# --------------------------------------------------------------------------
# 指定全局安装包的安装位置
pnpm config set store-dir "D:/pnpm-store/store"
# 指定 pnpm 存储所有包的共享存储位置
pnpm config set global-dir "D:/pnpm-store/global"
# 设置全局安装包的可执行文件 (binaries) 的存储位置
pnpm set global-bin-dir "D:/pnpm-store/global/bin"
# 包元数据缓存的位置
pnpm config set cache-dir "D:/pnpm-store/pnpm-cache"

# 其他 (不建议)
# pnpm 创建的当前仅由更新检查器使用的 pnpm-state.json 文件的目录。
pnpm config set state-dir "D:/pnpm-store/pnpm-state"
# 设置虚拟存储路径
pnpm config set virtual-store-dir "D:/pnpm-store/virtual-store"
# 设置日志文件目录
pnpm config set logs-dir "D:/pnpm-store/logs"

可以通过运行以下命令来获取当前活跃的 pnpm 存储目录:

bash
pnpm store path  # 返回激活的存储目录的路径
# 或者
pnpm config get store-dir

# 查看所有配置
pnpm config list

2. 配置文件

pnpm 从命令行、环境变量、pnpm-workspace.yaml.npmrc 文件获取其配置。

1)配置文件路径

配置文件为 INI (全局) 和 YAML (本地) 格式。

本地配置文件位于项目的根目录中,名为 pnpm-workspace.yaml。

全局配置文件位于以下位置之一:

  • 如果设置了 $XDG_CONFIG_HOME 环境变量,则为 $XDG_CONFIG_HOME/pnpm/rc
  • 在 Windows 上:~/AppData/Local/pnpm/config/rc
  • 在 macOS 上:~/Library/preferences/pnpm/rc
  • 在 Linux上:~/.config/pnpm/rc
bash
# 获取全局配置文件的路径
pnpm config get globalconfig
2)storeDir

所有包被保存在磁盘上的位置。默认值:

  • 如果设置了 $PNPM_HOME 环境变量,则为 $PNPM_HOME/pnpm/rc
  • 如果设置了 $XDG_DATA_HOME 环境变量,则为 $XDG_DATA_HOME/pnpm/store
  • 在 Windows 上: ~/AppData/Local/pnpm/store
  • 在 macOS 上:~/Library/pnpm/global
  • 在 Linux 上:~/.local/share/pnpm/store

建议以后前端项目都创建在与 storeDir 相同的盘符上。原因参见:存储路径已指定

3)globalDir

指定储存全局依赖的目录。默认值:

  • 如果设置了 $XDG_DATA_HOME 环境变量,则为 $XDG_DATA_HOME/pnpm/store
  • 在 Windows 上:~/AppData/Local/pnpm/store
  • 在 macOS 上:~/Library/pnpm/global
  • 在 Linux 上:~/.local/share/pnpm/global

官方文档地址:globaldir

4)globalBinDir

允许设置全局安装包的 bin 文件的目标目录。默认值:

  • 如果设置了 $XDG_DATA_HOME 环境变量,则为 $XDG_DATA_HOME/pnpm
  • 在 Windows 上:~/AppData/Local/pnpm
  • 在 macOS 上:~/Library/pnpm
  • 在 Linux 上:~/.local/share/pnpm
5)cacheDir

缓存的位置(软件包元数据和 dlx)。默认值:

  • 如果设置了 $XDG_CACHE_HOME 环境变量,则为 $XDG_CACHE_HOME/pnpm
  • 在 Windows 上:~/AppData/Local/pnpm-cache
  • 在 macOS 上:~/Library/Caches/pnpm
  • 在 Linux 上:~/.cache/pnpm
6)stateDir

pnpm 创建的当前仅由更新检查器使用的 pnpm-state.json 文件的目录。默认值:

  • 如果设置了 $XDG_STATE_HOME 环境变量,则为 $XDG_STATE_HOME/pnpm
  • 在 Windows 上:~/AppData/Local/pnpm-state
  • 在 macOS 上:~/.pnpm-state
  • 在 Linux 上:~/.local/share/pnpm

第四章:其他

一、cnpm

1. 是什么

cnpm 是一个淘宝构建的 npmjs.com 的完整镜像,也称为『淘宝镜像』。网址:https://npmmirror.com

cnpm 服务部署在国内阿里云服务器上,可以提高包的下载速度。

官方也提供了一个全局工具包 cnpm,操作命令与 npm 大体相同。

2. 安装

方式一:全局安装 cnpm 命令,安装完成后使用 cnpm 命令代替 npm 命令。

bash
npm install -g cnpm --registry=https://registry.npmmirror.com

方式二(Linux):通过添加 npm 参数 alias 一个新命令,安装完成后使用 cnpm 命令代替 npm 命令。

bash
alias cnpm="npm --registry=https://registry.npmmirror.com \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=https://npmmirror.com/mirrors/node \
--userconfig=$HOME/.cnpmrc"

方式三:把官方镜像地址修改为淘宝镜像地址,修改后继续使用 npm 命令。

bash
# 设置为淘宝镜像
npm config set registry https://registry.npmmirror.com
# 验证配置
npm config get registry

# 如果想改回官方镜像   
npm config set registry https://registry.npmjs.com

3. 操作命令

功能命令
初始化cnpm init
安装包cnpm install 包名
cnpm i 包名
cnpm i -S 包名
cnpm i -D 包名
cnpm i -g 包名
安装项目依赖cnpm i
删除cnpm r 包名

二、nrm 🛠

使用 nrm: NPM registry manager 配置管理 npm 的镜像地址。

1)安装 nrm

shell
npm i -g nrm

2)修改镜像

shell
nrm use taobao
nrm use npm

3)检查是否配置成功

shell
npm config list

检查 registry 地址是否为 https://registry.npmmirror.com,如果是则表明成功。

虽然 cnpm 可以提高速度,但是 npm 也可以通过淘宝镜像进行加速,所以 npm 的使用率还是高于 cnpm。

preview
图片加载中
预览

Released under the MIT License.