基于pnpm搭建monorepo项目

·  阅读 4616
基于pnpm搭建monorepo项目

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

受到一些同学的影响(@东东吖),觉得有必要记录一下自己做的一些事情,踩坑也好经验也罢,也许有些人看来就是流水账,但是总会有人看了觉得稍微有点用处,或者各位看官不吝赐教指点一下,我都觉得有所裨益。
我是@Laffery,一个刚上路的前端新手。如果你觉得这篇文章对你有帮助,还请不吝点个赞再滑走。

背景

说回正题,最近在开发自己的项目的时候,想额外开发一个库,因而项目的规模越来越大,原有的项目结构已经不再适用。

比如原有项目结构是这样

# app
├── lib
│   ├── pkg1
│   │   └── package.json
│   ├── pkg2
│   │   └── package.json
│   └── pkg3
│       └── package.json
└── package.json
复制代码

目前我遇到的主要痛点是这样的

  • 为什么我的项目里面要有这些package?

    • 这些包是我在开发过程中逐渐沉淀下来的通用(对自己而言)包,项目主体app对这些包有依赖

    • 我想把它们放在这个项目里一起开发,因为有很多可以复用的代码

  • 想象一下pkg1,pkg2,pkg3和我们主体项目app的依赖存在重合的情形,相信受过node_modules折磨的小伙伴都知道这意味着

    • 下载慢
    • 项目体积过大
  • 每个pkg都有各种各样的npm script,当然主要无外乎就是dev,build,test,lint之类的。如果你有这样一个workflow--每次PR后在CI里面把所有包都进行lint或test测试之后才允许合并,那么你一定会为需要依次到各个包目录下执行脚本而烦恼。

什么是monorepo?

简单来说就是把多个项目放在同一个代码仓库里,区别于multirepo(每个项目对应一个代码仓库)。它的核心优势就是便于代码复用版本管理

这里不多做介绍,如果有不清楚的小伙伴,可以自行搜索。

pnpm

大家都知道,npm是下载nodejs自带的包管理工具,它当然十分优秀,但是有时候下载龟速,为人诟病。

而我近来一直在使用的yarn,很好的解决了一些下载慢的问题,但是这和这篇文章的主题没有关系了。(其实yarn也能做monorepo,但是为什么我用pnpm呢?因为我在上一家公司见到的就是pnpm)。

另外还有很多包管理器之上的解决方案,比如 lerna, rush, nx 等等,与 pnpm 等结合用也会有不错的效果。

谈谈这篇文章的主角,pnpm(>=7)。

节约磁盘空间并提升安装速度

pnpm有一个概念,叫做workspace,就是将一个仓库分成不同的工作空间,一般是一个package对应一个workspace。

pnpm在执行pnpm install(根目录下)时,做的事情就是将所有workspace的所有的依赖,下载到node_modules/.pnpm目录下,然后再根据各workspace的依赖,在其目录下通过软链接的方式将这些依赖添加进来。

盗用一下官方的图

image.png

相信看到这里大家能意识到,所有依赖只需要下载一次,那么下载不仅快,而且占用的磁盘体积也小。

对原有项目进行monorepo改造

我们重新调整下项目的结构

# app
├── packages
│   ├── pkg1
│   │   ├── package.json
│   │   └── pnpm-lock.yaml
│   ├── pkg2
│   │   ├── package.json
│   │   └── pnpm-lock.yaml
│   ├── pkg3
│   │   ├── package.json
│   │   └── pnpm-lock.yaml
│   └── app
│       ├── package.json
│       └── pnpm-lock.yaml
├── package.json
├── pnpm-lock.yaml
└── pnpm-workspace.yaml
复制代码

是不是感觉清爽很多[旺柴]。

我们在pnpm-workspace.yaml中对workspace进行配置,好让pnpm知道都有哪些workspace。

# ./pnpm-workspace.yaml
packages:
  # root directory
  - "."
  # all packages in subdirs of packages/
  - "packages/**"
  # exclude packages that are inside test/ directories
  - "!**/test/**" # '!' means exclude
复制代码

现在动手执行pnpm install,和前面说的一样,所有设定好的workspace的依赖同时被下载下来了。

执行各workspace的脚本

现在我们回到开头提到的一个场景。

如果你有这样一个workflow--每次PR后在CI里面把所有包都进行lint或test测试之后才允许合并,那么你一定会为需要依次到各个包目录下执行脚本而烦恼。

在根目录下执行app下的脚本

通常你要执行app包下指令,可能会这样做

cd packages/app
npm run xxx
复制代码

或者你是个省事小能手,在./package.json里面这样写

// ./package.json
{
    // ...
    "scripts": {
        "app:xxx": "cd packages/app && npm run xxx"
    }
    // ...
}
复制代码

这样你就只需要在根目录下执行npm run app:dev就可以了。

这种做法未尝不可,但是假如有一天你心血来潮,把这个包的目录名称改成了webapp,就得忍痛把这些指令全部改成cd packages/webapp && npm run xxx

pnpm提供一个--filter-F)指令,你可以设定选择器,让pnpm在特定包下执行指令

我们假设上面提到的包叫这些名字(package.json里的name)

  • pkg1: @laffery/pkg1
  • pkg2: @laffery/pkg2
  • pkg3: @laffery/pkg3
  • app: @laffery/app

现在你要执行app下的dev指令,只需要

pnpm run --filter @laffery/app dev
复制代码

我们一般很少改包的名字,所以这样改目录名字就不会有问题。

当然了,我们是省事小能手,可以把所有常用的指令都配置在根目录下。

// ./package.json
{
    // ...
    "scripts": {
        "app:dev": "pnpm run --filter @laffery/app dev",
        "app:build": "pnpm run --filter @laffery/app build",
        "app:deploy": "pnpm run --filter @laffery/app deploy"
    }
    // ...
}
复制代码

现在我们所有的命令都可以在根目录下执行了。

批量执行命令

这个--filter既然是过滤器,又不是只选择其中一个,当然是支持筛选多个workspace啦。

现在你想对所有的包进行lint或test,可以这样做

// ./package.json
{
    // ...
    "scripts": {
        "lint": "pnpm run --filter=\"@laffery/*\" lint",
        "test": "pnpm run --filter=\"@laffery/*\" test"
    }
    // ...
}
复制代码

然后在你的.github/workflows/ci.yaml下轻松地写下

# .github/workflows/ci.yaml
jobs:
  check:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - name: Install
        run: npm i -g pnpm && pnpm i
      - name: ESlint
        run: pnpm lint
      - name: Tests
        run: pnpm t
复制代码

大功告成!

当然,还有一些其它的功能,比如统一publish npm包,大家都可以上官网看看。

复用同仓库下的代码

假设app依赖于pkg1@1.5.0pkg2@1.5.0,而后两者均依赖于pkg3@1.5.0。常规的做法是直接使用npm上的版本。但是如果想直接用当前正在开发中的pkg3@1.5.1,而又还没有发布到npm上, 就很难办了。

总不能import xxx from "../../pacakge/pkc3/xxx"吧。

这个时候workspace就派上了用场,可以这样给pkg1写依赖

// packages/pkg1/package.json
{
    "dependencies": {
        "@laffery/pkg3": "workspace:1.5.1",
    }
}
复制代码

感谢评论区@骑自行车指出,在设置依赖版本的时候推荐用workspace: *,就可以保持依赖的版本是工作空间里最新版本,不需要每次手动更新依赖版本。

详细的介绍可以看官网的文档

VSCode Workspace

另外补充一个,如果你和我一样是个VSCode铁粉,可以考虑在根目录下新建一个xxx.code-workspace文件

// xxx.code-workspace
{
  "folders": [
    {
      "path": "packages/pkg1",
      "name": "Package 1"
    },
    // ...
    {
      "path": "packages/app",
      "name": "Web Application"
    },
  ]
}
复制代码

编辑器会弹出打开工作区按钮,点开会有奇效~,了解更多请参考

总结

本文介绍了基于pnpm搭建monorepo项目的一些经验,你可以

  • 节约磁盘空间并提升安装速度
  • 方便统一管理,执行测试、构建、发布等一系列指令
  • 复用同仓库下其它package的代码

本文同步发表在我的个人博客上,感兴趣可以click here

我是@Laffery,一个刚刚上路的前端新人。如果你喜欢我的文章,或者觉得有用,欢迎点赞or关注or转发。

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改