灏天阁

pnpm:是时候对npm说再见了

· Yin灏

🌟pnpm的背景和由来 🌟

PNPM是一种包管理工具,始于 2016 年。该工具与其他包管理工具相比具有多种独特的特征,例如支持符号链接、快速安装速度,以及支持多语言开发。那么,pnpm的由来是什么呢?

在之前的一些包管理工具(如npm或者yarn)内,为了防止包的版本过多,一般都是将包下载到项目的 node_modules 目录中。但是,这种方法可引发一些问题,例如每个包都要占用磁盘空间等。因此,常常需要定期将旧版本的包删除。

然而,这仅仅是缓解问题的方法,问题的根源在于大量依赖包的冗余复制,这会导致整个项目包的大小快速增加。而 PNPM已经成功地解决了这个问题。

具体而言,pnpm主张的是将每个包仅在磁盘上保存一份,既在项目根目录下使用一个 node_modules文件夹,每个包则使用符号链接的方式链接到这个目录下。这样做,可以显著减少磁盘空间占用并缩短安装时间。

因为其设计理念和优势,pnpm受到越来越多的关注,成为webpack等许多重要工具的默认包管理器。

🧐 分析pnpm与其他包管理工具的区别与优势 🧐

包管理工具的区别

在选择符合自己工具的包管理工具时,我们应该认真比较它们之间的区别。作为主流的三种包管理工具,pnpmnpmyarn使用了不同的包下载和管理方式。

  • npm: 最初发布于 2010 年,是一种由Node.js提供的默认包管理器。 npm存储依赖的包版本在 node_modules 文件中,这意味着相同的模块可能会在不同项目中多次存在,这会增加磁盘空间的占用,并且在安装依赖的时候会比较慢。

  • yarn: 2016 年发布的 yarn,是由Facebook、Google 和 Tilde 联合开发的一个包管理器。与npm不同,yarn 使用lockfile机制,确定了每个包的具体版本。yarn 使用 yarn.lock 文件与无法很好地同时支持npmyarn以及node_modules目录占用过多的问题。

  • pnpm: 2016 年发布的pnpm,则使用了一些更独特的方式来提高包下载和管理效率。pnpm在安装和更新依赖的时候,采用 symbolic links 来代替npmyarn下载的多个实例,其中硬链接和符号链接的混合组成,从而降低了依赖占用的磁盘空间

pnpm的优势

除了与npmyarn相比的区别之外,pnpm的一些独特设计也给它带来了显著的优势。

  • 快速安装: 由于pnpm使用了链接的方式来管理包,因此无需重新复制每个包文件,使得一次安装所需的时间大大减少。

  • 节省磁盘空间: 由于同一个包只被保存在磁盘上一次,因此pnpm适用于多个项目使用同一依赖的情况。通过使用本地缓存,多个项目可以共享依赖包,极大地减少了磁盘空间的使用。

  • 支持多语言开发: pnpm不仅支持Node.js,还包括许多其他的编程语言,例如TypeScript、Reason 等。这使得使用pnpm的我们可以更容易地在一个项目中使用多种语言进行开发。

  • 易于使用: pnpm基本命令与npm相同,所以只需要在使用上进行简单的修改即可。PNPM具有完善的文档和有效的社区支持。

以上就是对pnpm和其他包管理工具的区别与优劣势的分析,我们看到,pnpm的特殊设计使之成为了一个高效的包管理工具,值得我们们尝试。

🔗 硬链接和符号链接 🔗

在介绍pnpm中硬链接和符号链接的应用之前,我们需要先了解硬链接和符号链接这两种链接方式。

硬链接:是Linux中一种文件系统链接的方式。当执行ln -P 创建一个硬链接时,硬链接和原文件共享inodeblock,该硬链接可以视作源文件的另外一个别名。硬链接共享文件的访问和更改权限,因为硬链接和源文件的数据指针和inode是相同的。也就是说,不论你打开源文件还是硬链接,看到的都是同一份数据。

符号链接:也叫软链接,是在文件系统中创建的一种特殊文件,它可以连接到其他文件或目录。在符号链接创建时,它会新建一个inode,该inode记录了被符号链接所指向的目标inode。可以理解为一个指针,当你打开符号链接时,会从符号链接所指向的目标inode处读入数据并处理。

相对而言,硬链接的速度比符号链接更快,因为硬链接只创建一个文件副本,多个文件链接指向同一个文件,而符号链接则是创建一个独立的文件,并指向原文件。

📂pnpm中的硬链接和符号链接 📂

当我们使用pnpm安装依赖时,它会在全局缓存目录中创建硬链接和符号链接。目录下的每个包将被存储在对应的缓存目录中,执行pnpm install的时候,pnpm会在每个项目中生成一个 node_modules 文件夹,然后使用符号链接将不同的依赖项放在这个文件夹内,完成依赖管理。

下面是一个pnpm硬链接和符号链接的例子:

node_modules
├── acorn -> ../.pnpm/acorn@8.6.0/node_modules/acorn
├── axios -> ../.pnpm/axios@0.21.0/node_modules/axios
├── commander -> ../.pnpm/commander@8.0.0/node_modules/commander
├── express -> ../.pnpm/express@4.17.1/node_modules/express
...

这里的 ../.pnpm/ 是全局依赖缓存的地址,每个依赖项都是用符号链接创建的。当pnpm在实际的项目中使用依赖项时,它会将符号链接重定向回全局安装的包,从而减少磁盘的使用。由于硬链接共享一个inode,因此可以使得执行命令更加快速。

通过结合硬链接和符号索引,pnpm有效地将文件目录的大小限制在最小范围内,同时确保了快速本地安装速度。

💡pnpm的使用方法和基本命令 💡

安装

我们可以使用npm来安装pnpm。在命令行中运行以下命令即可:

npm install -g pnpm

这将全局安装pnpm

配置

默认情况下,pnpm的数据存储在~/.pnpm-store文件夹中,我们可以通过环境变量PNPM_STORE_DIR来更改存储位置,例如:

PNPM_STORE_DIR=/data/pnpm pnpm install

这将pnpm的缓存存储在/data/pnpm文件夹中。

使用

相比npmpnpm的基本命令几乎相同,可以使用常规的installupdatelistuninstall等命令。以下介绍几个常用命令:

  • pnpm install: 安装依赖包。
  • pnpm update: 更新依赖包到最新版本。
  • pnpm list: 查看当前项目的依赖关系。
  • pnpm uninstall <package-name>:卸载指定的依赖包。
  • pnpm run <script>:执行已经定义的不同的脚本命令。

值得注意的是,pnpm默认会将执行命令的项目目录中的node_modules文件夹替换为符号链接。这意味着符号链接上的内容是实时更新的,这对多个项目共享资源的情况特别有用。

常见问题和解决方案

1. pnpm安装的速度比npmyarn更快,为什么?

pnpm通过使用链接的方式来管理包,在安装和更新依赖的时候,它使用符号链接代替复制文件,因此无需反复复制文件或下载文件,可以使安装速度大大提高。

2. 如何清除pnpm的缓存?

可以在命令行中使用以下命令来清除pnpm的缓存:

pnpm cache clean

或者,可以在指定的目录下使用以下命令:

pnpm cache clean --store ./cache-folder

3. 如何强制重新加载pnpm缓存?

可以使用以下命令强制重新加载pnpm缓存:

pnpm install --force

4. 如何升级pnpm

可以使用以下命令升级pnpm

npm install -g pnpm@latest

非常好,下面是我们所需的第四部分内容。

🚀pnpm的最佳实践 🚀

使用pnpmworkspaces功能

使用pnpmworkspaces功能可以将多个项目捆绑在一起,这样可以更方便地管理这些项目的依赖。workspaces将多个项目组成一个整体,因此我们可以更轻松地管理所有项目的依赖。我们可以使用类似以下的方法来配置workspaces

{
  "name": "root",
  "private": true,
  "workspaces": ["packages/*/package.json"]
}

这意味着我们在packages目录下的所有子目录中都可以使用workspaces功能。

使用pnpm workspaceshrinkwrap

pnpm install命令在workspace中的执行方式与单独运行在某个包的**npm install** 或 yarn install 命令有所不同。例如,运行pnpm install命令将自动安装所有子项目的依赖项。

有时,我们可能需要确保每个项目使用相同的依赖项版本,以此来保证项目之间的可靠性。在这种情况下,使用shrinkwrap文件非常有用,它可以确保每个项目使用相同的依赖项版本。我们可以使用以下命令来创建shrinkwrap文件:

pnpm install --shrinkwrap-only

使用pnpm.workspace文件

当我们需要特定的命令在所有的workspace中执行时,可以使用.workspace文件。例如,所有workspace都需要使用相同的eslint配置,我们可以在root目录下的.workspace文件中放置以下内容:

{
  "env": {
    "NODE_ENV": "production"
  },
  "scripts": {
    "lint": "eslint src/**"
  }
}

这样做的好处是,这个配置将可用于整个workspace,而不仅仅是一个单独的项目。

通过pnpm script执行依赖

pnpm可以在全局缓存中执行脚本。使用 pnpm run 命令时除了运行在根项目中定义的脚本, 它也会查找全局缓存中的脚本。我们可以使用以下命令来执行容器中的任何可执行文件:

pnpm run my-script

其中,my-script必须是在容器中安装的可执行文件。这种做法在进行复杂的构建过程时非常有用,这样就不需要重复安装相同的依赖项。

使用pnpm的多语言支持

pnpm并不局限于Node.js,它同样适用于其他语言。在使用不同的编程语言共同开发一个项目时,可以使用pnpm作为包管理器,这样可以更方便地管理各个项目的依赖。

在管理多个不同语言的项目时,我们可以设置PNPM_LANG环境变量来指定每个项目的默认语言,例如:

PNPM_LANG=typescript pnpm install

此外,pnpm还支持安装不同语言的依赖项。例如,我们甚至可以在JavaScript项目中安装C/C++依赖项。

🏢 workspace的用法详解

在使用pnpm时,我们可以使用workspace功能来管理多个项目的依赖关系。在这一部分中,我们将详细介绍workspace的用法,包括如何配置、安装依赖、更新依赖、使用别名等等。

配置workspaces

要启用workspace功能,我们需要在根目录下创建一个package.json文件,并使用workspaces属性来指定哪些文件夹被视为workspace。例如:

{
  "name": "my-project",
  "version": "1.0.0",
  "private": true,
  "workspaces": ["packages/*"]
}

上面的配置将使my-project项目中的packages文件夹下的所有子目录成为workspace

安装依赖

在安装workspace中的依赖时,我们可以使用pnpm install命令,pnpm会自动为每个workspace安装它们的依赖项。例如:

pnpm install

这将为所有workspace安装依赖。我们也可以使用pnpm install命令为特定的workspace安装依赖。例如:

pnpm install --workspace my-package

这将为名为my-packageworkspace安装依赖。

更新依赖

在更新workspace的依赖时,我们可以使用pnpm update命令。这将为所有workspace中的依赖更新并安装最新版本。如果我们希望只更新特定的workspace依赖,可以使用--workspace标志:

pnpm update --workspace my-package

这将只更新名为my-packageworkspace依赖。

别名使用

当我们在workspace中使用某个依赖项,并希望像在普通的Node.js项目中一样使用它时,就需要使用别名功能。别名可以让我们将workspace中的一个模块映射到另一个模块。为此,需要在package.json文件中指定别名。

例如,我们可以在package.json文件中添加以下内容:

{
  "name": "my-project",
  "version": "1.0.0",
  "private": true,
  "workspaces": ["packages/*"],
  "dependencies": {
    "my-utils": "workspace:*"
  }
}

这将在 workspace 中安装my-utils包,并将其指定为别名"workspace:*",这意味着可以像在普通Node.js项目中一样引用它。

- Book Lists -