从 NPM 迁移到 PNPM

PNPM 一直被誉为 NPM 的更高性能和更可靠的替代品,旨在减少缓慢的构建时间并消除依赖项不匹配的问题。我主要研究 PNPM 作为一种加快 CI 管道完成时间的方法,以及在开发周期中使用更好的包管理器。

我在一个仍处于 alpha 开发阶段的项目上测试了这一点,因此可以承受任何因错误或其他问题而导致的潜在停机。在部署应用程序时,用 PNPM 替换包管理器不太可能导致任何重大更改,但如果应用程序已经上线,则绝对应该首先在测试环境中进行测试。

唯一的潜在问题是依赖不匹配,但我已在本博文中更详细地描述了这一点。

Get started

  1. PNPM 的安装指南可在此处找到。对于 JS 开发人员来说,最简单的方法可能是运行:npm install -g pnpm。

  2. 在要转换为 PNPM 的项目中,找到 node_modules 目录并将其删除。

  3. 将以下代码添加到项目的 package.json。这将阻止人们使用任何其他包管理器安装包。

    1
    "scripts": { "preinstall": "npx only-allow pnpm", ... }
  4. 在目录根目录中,创建一个名为的文件pnpm-workspace.yaml并添加以下内容 (Optinal)
    如果项目要使用pnpm workspace,则需要添加如下配置

    1
    2
    3
    4
    5
    6
    packages:
    # include packages in subfolders (change as required)
    - 'apps/**'
    - 'packages/**'
    # if required, exclude directories
    - '!**/test/**'
  5. 在终端中运行pnpm import。这将根据当前的 yarn.lock 或 package-lock.json 创建一个 pnpm-lock.yaml 文件。

  6. 删除 yarn.lock 或 package-lock.json 文件。

  7. 通过运行pnpm i或使用 pnpm install 安装PNPM依赖项

  8. 如果你的 package.json 中有使用前缀的脚本npm run,则需要将其替换为pnpm 例如: pnpm test,而不是npm run test

重要的提示

使用 NPM 或 Yarn 安装依赖项时,会创建一个“flat”node modules 目录。这意味着源代码可以访问未作为依赖项添加到项目的依赖项。PNPM 的工作方式不同,它使用符号链接仅将项目的直接依赖项添加到模块目录的根目录中。

例如,如果你有A一个导入包的包B

import something from ‘B’

但没有在项目的依赖项中明确指定依赖B,那么执行将失败。

这种新结构不仅提高了构建性能,还降低了项目中出现依赖性错误的可能性。

如果你确实遇到了需要扁平节点模块结构(比如 NPM 或 Yarn 创建的那种结构)的情况,PNPM 提供了一个解决方案:

1
pnpm install --shamefully-hoist

尽管应尽可能避免这种情况,因为它违背了 PNPM 实现的设计模式。您可能需要使用此功能的一个示例是,您安装的依赖项未在package.json中明确指定。

使用pnpm进行构建

第一次运行时,pnpm install您将在终端中看到一个进度图,如下图所示:

first pnpm install

请注意,首次安装时“已重用”计数保持为 0。这是因为我们尚未创建 PNPM 可以引用的缓存。

一旦安装了所有依赖项,如果您pnpm install再次运行或添加新包pnpm add some-new-package -w,您将看到“重复使用”计数器现在正在上升。

pnpm cached

这种缓存可显著加快安装过程,因为它可避免重新下载已获取的软件包。软件包也是同时下载,而不是逐个下载。

“在 pnpm 中,如果包已经安装在其他项目中,则总是会重复使用,从而节省大量磁盘空间,这使得它比 npm 更快、更高效。”

PNPM 和 CI 管道

我第一次研究使用 PNPM 作为替代包管理器的主要目的是加快我的 CI 管道的时间,即使对于不是特别复杂且没有端到端测试要运行的项目,该管道也经常需要 10 或 15 分钟。

考虑到这一点,这里有一个示例.gitlab-ci.yml文件,其中包含一个简单的部署脚本到 Gitlab page,展示了我如何使用 PNPM。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

image: node:16.14.0
before_script:
- curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7
- npm config set store-path /root/.pnpm-store/v3
- npm config set registry <nexus url>
pages:
stage: deploy
interruptible: true
cache:
key: $CI_COMMIT_REF_SLUG
paths:
- /root/.pnpm-store/v3
- public/
- ./node_modules
policy: pull
script:
- pnpm install
- pnpm build-storybook
artifacts:
paths:
- public
only:
- master

您可以看到,在此before_script阶段,我们发出 CURL 请求来下载 PNPM。然后,我们将存储路径设置为新 PNPM 缓存存储的位置。

pnpm store path此路径可能因您的项目而异。我通过添加到我之前的 CI 脚本、运行管道,然后复制/粘贴它给我的路径,找到了正确的路径。

在此之后,我们确保注册表正在使用 Nexus 来安装包。

下一个重要部分是“缓存”部分。这很可能与您现有的设置类似,只是我们需要添加到“路径”:

前面提到的 PNPM 存储路径,例如/root/.pnpm-store/v3

./node_modules

通常我们会.npm在路径数组中使用它,但现在可以将其删除。

我们利用缓存键$CI_COMMIT_REF_SLUG,它允许我们在同一分支(例如 master)中的作业之间共享缓存。初始运行后,当我们运行管道时,如果 PNPM 成功命中缓存,我们应该会看到“reused”计数器上升。

最后,在本script节中,我们运行新pnpm install命令,然后运行项目相关的命令,例如 build-storybook。

其他 PNPM 功能

PNPM 的另一个有用功能是能够管理 Node 版本。我们很多人目前都使用nvm它,它的工作方式几乎相同。

一些例子:

  • 安装 Node 的 LTS 版本:pnpm env use –global lts
  • 安装node 16:pnpm env use –global 16
  • 安装最新版本的 Node:pnpm env use –global latest
  • 删除特定版本的 Node:pnpm env remove –global 14.0.0

与我们当前使用的流程相匹配的一个有用示例是.npmrc在项目根目录中有一个定义 Node 版本的文件:

1
use-node-version=16.14.0

当我们运行 时pnpm start,它将从配置文件中获取此 Node 版本并将其用于我们的项目。能够使用我们的默认包管理器管理 Node 版本非常方便。

最后说明

您可能需要将其添加.pnpm-store/**到您的.gitignore文件中。

PNPM的文档非常有用且非常详细。

作者

鹏叔

发布于

2023-08-13

更新于

2024-08-06

许可协议

评论