工程化之包管理器pnpm,一次说明白前端在中的使用

127 阅读12分钟

pnpm 是一种替代 npm 的包管理工具,具有高效性和节省磁盘空间的特点。它在前端开发中的作用和优势主要体现在以下几个方面:pnpm 中文网站

pnpm 是什么

pnpm - 速度快、节省磁盘空间的软件包管理器

pnpm 在前端开发中的作用和优势

1. 高效的包管理

  • 快速安装:pnpm 使用内容寻址存储依赖包,允许重复使用相同版本的包,极大地提高了安装速度。
  • 链接的依赖:pnpm 在安装包时,会将包以符号链接的方式存储,减少了文件的重复拷贝。

2. 节省磁盘空间

  • 冻结依赖:pnpm 将依赖包安装到全局存储区,每个项目只存储符号链接而不是复制整个包,这样可以有效节省硬盘空间。
  • 共享依赖:多个项目可以共享相同版本的依赖,避免不必要的磁盘开销。

3. 强制一致性

  • 严格的依赖管理:pnpm 会检查每个包的依赖关系,以确保所有包都正确满足其依赖项。这有助于避免依赖冲突和不一致问题。
  • 使用 shamefully-hoist:pnpm 允许设置 shamefully-hoist 选项,将部分依赖提升至顶层。这在某些特定情况下可以解决依赖问题。

4. 更好的工作区支持

  • 工作区(Workspaces) :pnpm 对工作区有很好的支持,允许在单一的仓库中管理多个包,有助于管理大型项目或微服务架构。

5. 更强的兼容性

  • 兼容 npm Script:pnpm 支持 npm 的所有脚本,可以与现有项目无缝集成。使用 pnpm 安装后,所有 npm 脚本依然可以正常工作。

6. 简单易用

  • 命令行接口:pnpm 的命令行接口与 npm 类似,大多数命令都可以直接替换成 pnpm 使用,例如 pnpm installpnpm add 等,降低了切换的学习成本。

7. 开发体验

  • 锁定文件:pnpm 自动生成 pnpm-lock.yaml 文件以锁定依赖,确保团队成员或 CI/CD 系统在安装时使用相同版本的依赖。

小结

pnpm 在前端开发中的作用主要是提高包管理的效率,节省磁盘空间,确保依赖的一致性,提升开发体验,尤其适合处理大型项目或多包仓库。开发者可以根据项目需求选择使用 pnpm,以享受其带来的优化和便利。

pnpm安装

1. 安装 Node.js

确保你已经安装了 Node.js。你可以通过以下命令检查是否已经安装:

node -v

如果尚未安装 Node.js,可以从 Node.js 官网 下载并安装。

2. 使用 npm 安装 pnpm

在终端中运行以下命令以全局安装 pnpm:

npm install -g pnpm

3. 验证安装

安装完成后,可以通过以下命令验证 pnpm 是否成功安装:

pnpm -v

如果返回 pnpm 的版本号,则说明安装成功。

4. 使用 pnpm

一旦安装完成,你可以开始使用 pnpm 来管理你的项目依赖。例如:

  • 初始化新项目

    pnpm init
    
  • 安装依赖

    pnpm install <package-name>
    
  • 安装开发依赖

    pnpm add <package-name> --save-dev
    
  • 卸载依赖

    pnpm remove <package-name>
    
  • 更新依赖

    pnpm update
    

5. 工作区支持

如果你在一个多包项目(工作区)中使用 pnpm,可以通过以下命令来初始化工作区:

pnpm init -w

pnpm 常用命令

基本命令

  1. 初始化项目

    # 创建一个新的 `package.json` 文件
    pnpm init
    
  2. 安装依赖

    # 安装 `package.json` 中列出的所有依赖
    pnpm install
    
  3. 安装特定依赖

    pnpm add <package-name>
    

    安装指定的包,并将其添加到 dependencies 中。

  4. 安装开发依赖

    # 安装指定的包,并将其添加到 `devDependencies` 中
    pnpm add <package-name> --save-dev
    
  5. 卸载依赖

    # 卸载指定的依赖包
    pnpm remove <package-name>
    
  6. 更新依赖

    # 更新项目中所有依赖到最新版本
    pnpm update
    
  7. 更新特定依赖

    # 更新指定的依赖包
    pnpm update <package-name>
    

锁定和查看

  1. 查看已安装的依赖

    # 列出项目中已安装的所有依赖
    pnpm list
    
  2. 查看全局安装的包

    # 列出全局安装的依赖,不显示子依赖
    pnpm list -g --depth 0
    
  3. 生成锁定文件

     # 只生成或更新 `pnpm-lock.yaml` 文件,不实际安装依赖 (一般都是 pnpm i/add 后自动生成即可)
     pnpm install --lockfile-only
    

工作区支持

  1. 初始化一个工作区

    # 创建一个工作区的 `package.json` 文件
    pnpm init -w
    
  2. 在工作区中添加包

    # 在工作区中添加指定的包
    pnpm add <package-name> --workspace
    

脚本命令

  1. 运行脚本

    # 运行在 `package.json` 中定义的脚本
    pnpm run <script-name>
    

其他常用选项

  • 清除缓存

    # 清理不再使用的缓存包
    pnpm store prune
    
  • 查看帮助信息

    # 显示所有 pnpm 命令的帮助信息
    pnpm help
    

配置文件

pnpm-lock.yaml 和 package.json文件的区别(类似npm 文章这里不过多赘述可看# 程化之包管理器npm,一次说明白前端在中的使用 在使用 pnpm 作为包管理工具时,可以利用 .npmrc 文件来配置 npm 和 pnpm 的行为。以下是一些常用的 .npmrc 配置属性,以及它们的作用:

1. registry

  • 描述:指定 npm 包的注册表地址。

  • 示例

    registry=https://registry.npmjs.org/
    

    如果使用其他源,比如淘宝源,可以写成:

    registry=https://registry.npm.taobao.org/
    

2. always-auth

  • 描述:在每次请求中是否始终发送身份验证信息。

  • 示例

    always-auth=true
    

3. scope

  • 描述:指定 npm 包的作用域,通常用于组织或私有包。

  • 示例

    scope=@my-org
    

4. authToken

  • 描述:用于身份验证的访问令牌,可以用来访问私有库。

  • 示例

    //registry.npmjs.org/:_authToken=YOUR_AUTH_TOKEN
    

5. cache

  • 描述:定义 npm 的缓存目录,用于存放下载的包。

  • 示例

    cache=/path/to/npm/cache
    

6. save-exact

  • 描述:安装依赖时是否将其版本保存为精确版本,而不是使用波浪号或插入号。

  • 示例

    save-exact=true
    

7. strict-peer-dependencies

  • 描述:严格检查对等依赖。如果设置为 true,则在未满足某个对等依赖的情况下安装会失败。

  • 示例

    strict-peer-dependencies=true
    

8. shamefully-hoist

  • 描述:如果设置为 true,pnpm 会将所有依赖提升到顶层 node_modules 目录,可能帮助解决某些依赖问题。

  • 示例

    shamefully-hoist=true
    

9. link-workspace-packages

  • 描述:在工作区中使用时,是否将工作区中的包以符号链接的方式连接到 node_modules

  • 示例

    link-workspace-packages=true
    

10. scripts-prepend-node-path

  • 描述:控制 node 可执行文件的路径,以确保可以在运行 npm 脚本时找到 node

  • 示例

    scripts-prepend-node-path=auto
    

特别的配置文件 pnpm-workspace.yaml (monorepo工作区,正常项目无需,UI组件库 第三方插件等框架中会比较多使用到)

pnpm-workspace.yaml 是一个用于配置 pnpm 工作区的文件,主要用于定义和管理包含多个包的单一代码库(monorepo)的结构。以下是 pnpm-workspace.yaml 文件的主要作用和功能:

1. 定义工作区

  • 作用:该文件用于明确声明当前项目是一个工作区,并定义工作区中包含的所有子包。

  • 示例

    packages:
      - 'packages/*'  # 包含 packages 目录下的所有子包
      - 'libs/*'      # 包含 libs 目录下的所有子包
    

2. 配置包的路径

  • 作用:通过指定路径模式,可以方便地管理多包项目中哪些文件夹是包。

  • 示例

    packages:
      - 'packages/*'
      - 'services/*'
    

3. 共享依赖

  • 作用:在工作区中,所有共享依赖仅会被安装一次,而不是在每个子包中重复安装,从而减少磁盘空间的使用,提高安装速度。
  • 示例:在根目录的 package.json 中定义共享依赖,所有子包都可以使用它们。

4. 方便的开发和测试

  • 作用:统一管理多个包,使得在工作区中开发、测试和发布变得更加高效和简便。可以通过在根目录运行命令来安装、更新或测试所有包。
  • 示例:通过 pnpm install 指令,工作区中的所有包都会被统一处理。

5. 版本和锁定文件管理

  • 作用:工作区中的所有包使用同一个 pnpm-lock.yaml 锁定文件,确保所有包的依赖版本一致性,有助于避免出现不兼容性的问题。

6. 支持 linting 和其他脚本

  • 作用:可以在工作区根目录中集中管理 linting、构建、测试等的运行命令,方便进行批量操作

pnpm解决了什么npm没解决的问题

1. 磁盘空间的浪费

  • 问题:npm 在每个项目中都会单独安装依赖,即使是相同版本的依赖也会重复存储,导致磁盘空间的浪费。
  • 解决方案:pnpm 使用内容寻址存储和符号链接,允许多个项目共享相同版本的依赖。这样,只有一份依赖会被存储在硬盘中,从而显著节省磁盘空间。

2. 安装速度

  • 问题:npm 在处理多个已安装依赖时,安装速度可能较慢,特别是在大项目中。
  • 解决方案:pnpm 在安装依赖时通过使用缓存和符号链接来提高效率,减少安装时间,特别是在安装多个包时。

3. 严格的依赖管理

  • 问题:npm 对于对等依赖的处理较宽松,可能导致不一致性和依赖冲突,尤其是在大型项目中。
  • 解决方案:pnpm 强制执行对等依赖的解析,确保所有依赖都满足其声明的要求,从而减少潜在的版本冲突和不兼容问题。

4. 工作区支持

  • 问题:虽然 npm 在现代版本中增加了工作区的支持,但在处理大型 mono-repo 项目时可能仍然存在一些限制。
  • 解决方案:pnpm 的工作区模式非常强大,能够轻松管理多个包,并且允许共享依赖,使得开发和集成变得更加高效。

5. 安装过程中的一致性

  • 问题:npm 可能在不同的环境(如 CI/CD 系统和开发者本地)中产生不同的结果,特别是在安装依赖的途中。
  • 解决方案:pnpm 生成的 pnpm-lock.yaml 文件能够精确锁定所有依赖及其版本,确保在不同环境中的安装结果一致。

6. 性能

  • 问题:npm 的性能在面对大量依赖时可能会有所下降,特别是在对依赖进行合并和更新时。
  • 解决方案:pnpm 的模式通过避免依赖重复安装,并利用有效的缓存策略,显著提升了性能。

总结

pnpm 解决了幻影依赖(phantom dependencies)问题,这也是它相较于 npm 的一个显著优点。

什么是幻影依赖(又叫幽灵依赖)?

幻影依赖是指某个包在使用时,它的代码依赖于另一个包,但该包并没有明确列在 dependencies 或 devDependencies 中。当这个依赖(通常是直接依赖的间接依赖)未被安装在项目的 node_modules 目录中时,可能会导致运行时错误,特别是在某些情况下,代码可能会假设这个依赖是存在的。

举个例子

假设你有以下两个包:

  1. package-a

    {
      "name": "package-a",
      "version": "1.0.0",
      "dependencies": {
        "package-b": "^1.0.0"
      }
    }
    
  2. package-b

    {
      "name": "package-b",
      "version": "1.0.0"
    }
    

幻影依赖的示例

现在,假设有一个第三个包 package-c,使用了 package-a

{
  "name": "package-c",
  "version": "1.0.0",
  "dependencies": {
    "package-a": "^1.0.0"
  }
}

在这个例子中,package-c 依赖于 package-a,而 package-a 又依赖于 package-b。正常情况下,如果你运行 npm installpackage-b 会被安装在 package-a 的 node_modules 中。

假设 package-b 作为一个对 package-c 至关重要的依赖,它在 package-a 的实现中被使用,但在 package-c 的 package.json 中没有明确列出 package-b

运行时错误

如果 package-c 的代码中直接调用了 package-b 的功能,代码会抛出一个错误,因为在 package-c 中并没有安装 package-b,尽管它的某个依赖(package-a)实际上依赖了 package-b。运行时可能会出现像这样的错误:

Error: Cannot find module 'package-b'

解决幻影依赖

为了解决这个问题,应该在 package-c 的 package.json 中明确列出对 package-b 的依赖:

{
  "name": "package-c",
  "version": "1.0.0",
  "dependencies": {
    "package-a": "^1.0.0",
    "package-b": "^1.0.0"  // 明确添加对 package-b 的依赖
  }
}

现在,任何使用 package-c 的人都能确保他们的环境里有 package-b,从而避免幻影依赖引起的运行时错误。

pnpm 如何解决幻影依赖的问题?

  1. 严格的依赖管理

    • pnpm 强制要求所有的依赖都必须在 package.json 中进行显式声明。这意味着如果某个包依赖于其他包,那么这些包也需要在相应的 dependencies 或 devDependencies 中列明,避免出现没有明确定义的依赖。
  2. 建立符号链接

    • 与 npm 使用传统的 node_modules 结构不同,pnpm 使用符号链接和内容地址存储。即使是对等依赖,pnpm 也会记录并确保依赖关系的完整性,从而减少或消除幻影依赖的风险。
  3. 对等依赖的严格解析

    • pnpm 在安装过程中将检查对等依赖(peer dependencies),如果未满足,npm 会发出警告。这样可以确保在使用某个依赖时,其所需的对等依赖在正确的位置和版本中安装。

结论

通过这些机制,pnpm 使得项目的依赖关系更加清晰和可靠,显著减少了幻影依赖的问题。这种严格的依赖管理策略有助于提升代码的稳定性和可维护性。因而依据上面各种分析,pnpm优势瞬间突出,也是目前pnpm这么多人喜欢使用的原因。。。顺势而为吧,目前前端的技术发展迅猛,还是要多多接触新概念新知识,扎实基础。。。