一文讲透 pnpm 管理 monorepo

594 阅读4分钟

关于 npm 管理参考:一文讲透 npm 管理 monorepo

什么是 Monorepo

Monorepo(单一仓库)是一种代码管理策略,它指的是在一个单一的版本控制仓库中维护多个项目或包。这些项目可能彼此独立,或者相互依赖,比如共享代码库、库组件、服务应用程序、前端应用等。

管理方案

项目的管理方案采用 pnpm 管理依赖项,其他操作优先考虑使用 npm -- 取其精华,舍弃其糟粕。

初始化仓库

  1. 创建项目目录 monorepo-demo 并初始化 package.json

    # Linux / MacOS / Windows
    mkdir monorepo-demo
    cd monorepo-demo
    
    npm init
    

    在此步中填写正确的包名(可加入命名空间),例如 @demo/root,可忽略第五步中的操作。

  2. 创建基于 pnpm 管理工作区的配置文件:

    # Linux / MacOS
    touch pnpm-workspace.yaml
    
    # Windows
    type nul >pnpm-workspace.yaml
    

    加入下面内容,代表子项目存放在 @demo 目录中:

    packages: 
      - '@demo/*'
    
  3. 初始化子项目 a 与 b:

    # Linux / MacOS
    mkdir -p @demo/{a,b}
    cd @demo/a && npm init
    cd ../b && npm init
    
    # Windows
    mkdir @demo\a @demo\b
    cd @demo\a & npm init
    cd ..\b & npm init
    

    在此步中填写正确的包名称(可加入命名空间),例如 @demo/a,可忽略第五步中的操作。

    当前目录结构:

    .
    ├── @demo
    │   ├── a
    │   │   └── package.json
    │   └── b
    │       └── package.json
    ├── package.json
    └── pnpm-workspace.yaml
    
  4. 由于根项目不需要发布,所以设置为私有化(等同于在根项目的 package.json 中加入 private="true"):

    npm pkg set private="true"
    
  5. (可选)设置根项目与子项目名称:

    npm pkg set name="@demo/root"
    npm pkg set name="@demo/a" --prefix @demo/a
    npm pkg set name="@demo/b" --prefix @demo/b
    

依赖安装

安装全局(公共)依赖

lodash 依赖:

# --workspace-root 可简写为 -w
pnpm add --workspace-root lodash

基于子项目安装依赖

axios 依赖,两种方式:

  • 基于工作区的安装方式:

    pnpm add --filter @demo/a axios
    
  • 进入子项目目录安装:

    # Linux / MacOS
    cd @demo/a && pnpm add axios
    
    # Windows
    cd @demo\a & pnpm add axios
    

基于工作区互相依赖

  • 子项目 b 引用子项目 a:

    pnpm add @demo/a --filter @demo/b
    
  • 子项目 a 作为全局(公共)依赖:

    pnpm add @demo/a --workspace-root
    

编写测试代码

@demo/a

  1. 在子项目 a 中创建文件(或手动在 @demo/a 下创建两个文件:index.jsanswerer.js):
# Linux / MacOS
touch @demo/a/{index.js,answerer.js}

# Windows
type nul > @demo\a\index.js
type nul > @demo\a\answerer.js
  1. index.js 代码:

    console.log("Hello, @demo/a");
    
  2. answerer.js 代码:

    const axios = require("axios");
    
    module.exports.getAnswer = () => {
      axios({
        method: "get",
        url: "https://yesno.wtf/api",
      }).then(({ data }) => console.log(JSON.stringify(data)));
    };
    

@demo/b

  1. 在子项目 b 中创建文件(或手动在 @demo/b 下创建一个文件:index.js):

    # Linux / MacOS
    touch @demo/b/index.js
    
    # Windows
    type nul > @demo\b\index.js
    
  2. index.js 代码:

    const a = require("@demo/a/answerer");
    
    a.getAnswer();
    

当前目录结构

  • 项目全局安装 lodash
  • 基于子项目 a 安装 axios
  • 子项目 a 作为子项目 b 的依赖;
.
├── @demo
│   ├── a
│   │   ├── answerer.js
│   │   ├── index.js
│   │   ├── node_modules
│   │   │   └── axios ⇒ ../../../node_modules/.pnpm/axios@1.6.2/node_modules/axios
│   │   └── package.json
│   └── b
│       ├── index.js
│       ├── node_modules
│       │   └── @demo
│       │       └── a ⇒ ../../../a
│       └── package.json
├── node_modules
│   └── lodash ⇒ .pnpm/lodash@4.17.21/node_modules/lodash
├── package.json
├── pnpm-lock.yaml
└── pnpm-workspace.yaml

配置运行脚本

  1. 为子项目添加运行脚本(等同于在子项目的 package.json 中加入 start="node index.js" 的运行脚本):

    npm pkg set scripts.start="node index.js" --prefix @demo/a
    npm pkg set scripts.start="node index.js" --prefix @demo/b
    
  2. 在根项目添加运行脚本,关联子项目运行脚本(等同于在根项目的 package.json 中加入两个 scripts 的运行脚本):

    npm pkg set scripts.start:a="npm run start --prefix @demo/a"
    npm pkg set scripts.start:b="npm run start --prefix @demo/b"
    

运行

npm 运行:

npm run start:a					# 运行子项目 a,显示 Hello, @demo/a
npm run start:b					# 运行子项目 b,显示通过 a 请求的接口数据
npm run start:all				# 同时运行两个子项目
npm run --prefix @demo/a start	# 直接运行子项目 a 的脚本命令
npm run --prefix @demo/b start	# 直接运行子项目 a 的脚本命令

pnpm 运行:

pnpm start:a				# 通过根项目运行子项目 a 的脚本命令
pnpm start:b				# 通过根项目运行子项目 b 的脚本命令
pnpm --filter @demo/a start	# 直接运行子项目 a 的脚本命令
pnpm --filter @demo/b start	# 直接运行子项目 b 的脚本命令

区别:默认情况下,pnpm 不会任意执行用户定义脚本的 pre 钩子和 post 钩子(例如 prestart)。 这种从 npm 继承过来的习惯,会导致脚本执行是隐式的,而不是显式的,从而混淆了执行流程。

本文为原创内容,版权所有 © 2024 姚生。欢迎转载,但请注明出处,并附上原文链接。未经授权不得用于商业用途。如有疑问,请联系作者。