UzumakiItachi
首页
  • JavaSript
  • Vue

    • Vue2
    • Vue3
  • React

    • React_18
  • WebPack
  • 浏览器相关
  • 工程化相关
  • 工作中遇到的问题以及解决方案
  • Git
  • 面试
  • 学习
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
  • 个人产出
  • 实用工具
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

UzumakiItachi

起风了,唯有努力生存。
首页
  • JavaSript
  • Vue

    • Vue2
    • Vue3
  • React

    • React_18
  • WebPack
  • 浏览器相关
  • 工程化相关
  • 工作中遇到的问题以及解决方案
  • Git
  • 面试
  • 学习
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
  • 个人产出
  • 实用工具
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • WebPack

  • 浏览器相关

  • 工程化相关

    • 执行npm run dev的时候发生了什么
    • 仓库版本管理-standard-version
    • git版本管理以及生成changelog
    • 发布一个npm包
    • gitlab runner 免密码登录服务器
    • 实现一个小程序持续集成工具
    • 使用changeset管理monorepo项目
    • pnpm+trubo打造一个极致丝滑的monorepo工程
      • 前言
      • 什么是monorepo
      • pnpm
        • pnpm有着独特的包管理机制
        • 非平铺的node_modules
        • 对monorepo的支持
      • 开始
      • 痛点
        • 关于changelog的生成
        • 关于构建效率的问题
      • 总结
    • 编写一个生成git提交信息的vite插件
    • 关于构建SPA项目的一些优化
    • 实现一个简易的脚手架
    • vite搭建多页面项目
    • 实现一个可自定义模板内容的脚手架
  • 工作中遇到的问题以及解决方案

  • Git

  • Vite

  • 一些小工具

  • 算法

  • 服务器

  • HTTP

  • 技术
  • 工程化相关
hanhanbuku
2023-08-17
目录

pnpm+trubo打造一个极致丝滑的monorepo工程

# 前言

最近写了一个基于monorepo的管理系统,将装修组件库和一些工具包拆分了出来,方便在别的项目复用。在开发过程中体验到了monorepo的方便同时也遇到了一些痛点,本文就展开说一下如何搭建一个monorepo工程以及如何解决开发过程中遇到的痛点

# 什么是monorepo

引用网上的一段话:

Monorepo 是一种项目代码管理方式,指单个仓库中管理多个项目,有助于简化代码共享、版本控制、构建和部署等方面的复杂性,并提供更好的可重用性和协作性。Monorepo 提倡了开放、透明、共享的组织文化,这种方法已经被很多大型公司广泛使用,如 Google、Facebook 和 Microsoft 等。

总结一下monorepo就是把多个项目的代码放在同一个仓库中管理,这样对某些工具的抽离以及基建的复用是非常友好的,尤其是在面对一些相似度非常高的项目的时候就特别好用。目前也是有很多开源大项目都采取这种方式去管理代码

# pnpm

pnpm和npm、yarn一样,也是一个包管理工具,官网对他是这样介绍的

  • pnpm 比其他包管理器快2 倍
  • node_modules 中的文件为复制或链接自特定的内容寻址存储库
  • pnpm 内置支持单仓多包
  • pnpm 默认创建了一个非平铺的 node_modules,因此代码无法访问任意包

这里简单说一下pnpm的好处,以及他对于我们的monorepo工程有什么帮助:

# pnpm有着独特的包管理机制

pnpm会将安装过的包缓存在磁盘中,当你下次再次安装他会从磁盘中直接拉取包过来,效率更高

# 非平铺的node_modules

pnpm创建了一个非平铺的node_modules。大家都知道在npm和yarn中会存在一个幽灵依赖的问题,也就是我们下载的依赖可能还会依赖别的包,而由于npm的平铺机制,会导致我们可以引入这些依赖的依赖。这就很容易导致出问题, 这里不对三个包管理工具做太多的介绍了,感兴趣的可以自行了解一下。而有过pnpm使用经验的小伙伴都知道,pnpm会把依赖的依赖统一放在.pnpm这个文件夹下管理,而我们下载依赖则会放置在.pnpm同级,然后pnpm通过自身软连接的形式关联起这些包,这样就避免了我们可以直接引入幽灵依赖的问题。

# 对monorepo的支持

在介绍这个特点之前我们首先要明白,一个monorepo的项目需要解决哪些问题?首先是公共依赖的管理,对子包操作的便捷度(例如批量执行子包的脚本或者单独操作某些子包之类的)。 在pnpm之前大部分都是通过workspace+lerna。我并没有使用过,但是从网上查阅的资料看来,使用过程挺繁琐的并不简单。在monorepo中有一个非常常见的场景,就是包和包之间存在依赖关系,在npm和yarn里面我们需要使用workspace这个功能并且通过npm link来关连起有依赖关系的包(这么做的好处是为了开发阶段联调的方便)。而pnpm自带了workspace,并且提供了相关命令可以自由的筛选子包 这样无论是公共依赖的管理以及为某个子包单独安装独有的依赖都非常的方便简单

关于pnpm的介绍就说到这里。

# 开始

学习完前置的知识,下面就开始着手去搭建一个基于pnpm的monorepo工程

首先安装一下pnpm

npm install -g pnpm
1

新建一个文件夹然后进入文件夹初始化一下项目

pnpm init 
1

接着我们在根目录创建一个packages文件夹,用于存放我们的所有子项目,接着我们去packages下创建两个项目。

pnpm pnpm create vite app1
pnpm pnpm create vite app2
1
2

此时我们开启workspace,创建一个pnpm-workspace.yaml文件

packages:
  - 'packages/*'

1
2
3

这样表示packages下所有的文件夹都处于workspace内,他们就可以共享公共依赖了。

此时我们安装一下依赖

pnpm i
1

安装完成之后我们的项目目录将会是这个样子

可以看到,不光子项目下有node_modules,根目录也有一个。子项目下的是他自己独有的一些依赖,而根目录下的就是用来管理所有的公共依赖了。并且可以看到根目录下的node_modules里面有一个.pnpm文件夹,而子项目是没有的,并且打开子项目中的一个依赖包的依赖文件可以看到,里面除了一些bin命令外没有任何东西。因为这些依赖都被放到了.pnpm文件夹内了。

至此,一个非常简单的monorepo工程就算是搭建完毕了。下面来讲如果建立包和包之间的依赖关系。

此时我们再创建一个util的子包,作为公共的工具包。

然后我们将他安装到app1中

pnpm add util -r -F app1
1

-F是-filter的简写,就是找到app1这个子包。而被安装的包pnpm会优先在工作空间找,没找到才回去npm上下载。

可以看到,util这个包已经作为依赖被安装进了app1中,并且版本号是以workspace开头的。这个再构建过程中pnpm会自动帮我们替换成最新的版本。

通过这样的操作之后,就可以即时的开发两个包并且相互感知更新了。无需再使用npm link。

此时假设我们开发好了项目,需要统一执行构建,应该怎么做呢?别急,我们同样可以根据pnpm提供的-f命令操作

"build:all": "pnpm -r --filter=./packages/* run build",
1

在根目录的package.json中添加这样一行命令,pnpm就会去找到所有packages下的包,并且执行build命令。

此时可能有些同学会有疑惑,我的子包之间有依赖关系,这样统一执行他的构建顺序是怎样的呢?

pnpm在这一点上也同样做了处理,他在构建阶段会去进行一个构建的调度,自动的检测出子包之间是否存在依赖关系,存在依赖关系的话会优先构建被依赖的子包,然后再构建你的主包。效果这里就不贴图了,可以自己去试一试。

# 痛点

经过上面这些步骤之后,一个monorepo的工程就可以正常的开发了。下面来说一下开发过程中会遇到的痛点以及解决方案。主要还是集中在工程化管理这一块

# 关于changelog的生成

当我们在发版之前生成本次开发内容的changelog是一个很有必要的步骤。他就像git提交记录一样可以帮我们记录每次发版的内容。平时大家应该都是通过conventional-changelog-cli这个库去获取commit提交记录来生成changelog的。很显然这个方案在monorepo中显得不够灵活。 比conventional-changelog-cli更适合monorepo的是changeset这个库,它可以获取到你的所有子包并且为每个子包定制自己的changelog,但是使用起来会比较麻烦。不过目前我没有发现比他更合适的工具了。

详细的使用方法可以看一下这篇文章 (opens new window)。

# 关于构建效率的问题

对于构建这一块,在日常开发过程中,并不是每次开发任务都会去动到所有的子包。可能这次我只修改了a包,下次只需改了c包和d包。那没有被修改的包肯定不需要重复的去构建了。对于这种情况如果我们单一执行每个包的构建显然不太明智,而如果统一执行构建又会出现重复构建的问题。

而关于这些问题最开始我想到的是通过在ci中判断不同分支名来执行不同的构建命令。这样做虽然能解决问题但是毫无扩展性可言,加一个新包就需要去加条新的构建命令和他的构建分支。最理想的情况就是,有没有一种方法能检测当前哪些包更新了哪些包没有更新然后只去构建那些已经更新了的包呢?

答案当然是有的,那就是turbo。

关于turbo的介绍这里也简单说下

Turborepo 是一个为 monorepo 而生的极快的构建系统。目的是为了解决大型monorepo 项目构建速度缓慢的一大痛点。turbo的核心是永远不会重新构建已经构建过的内容。turbo 会把每次构建的产物与日志缓存起来,下次构建时只有文件发生变动的部分才会重新构建,没有变动的直接命中缓存并重现日志。turbo拥有更智能的任务调度程序,充分利用空闲 CPU,使得整体构建速度更快。另外,turbo 还具有远程缓存功能,可以与团队和 CI/CD 共享构建缓存。

提取一下重要信息,turbo 的核心是永远不会重新构建已经构建过的内容,由于turbo在构建过程中会产生一个缓存文件,并且他会检测你的代码有无更新,没有更新就直接复用缓存。这不就正好能解决上面讲的问题了吗。 关于turbo的介绍后面会再开一篇文章细说。这里先介绍一下在项目中的使用

首先安装turbo

pnpm i turbo
1

在根目录创建一个turbo.json的配置文件

{
  "$schema": "https://turborepo.org/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    }
  }
}
1
2
3
4
5
6
7
8
9

pipeline里面配置的就是turbo的命令,这里我们配置了一个build命令。让他去执行所有的build命令。并且将缓存产物输出在dist文件夹下。

turbo run build
1

执行此命令即可开始打包。当你多次打包后你就会发现turbo会自动的去命中缓存,而不会重复打包。大大的缩减的构建时间

# 总结

以上就是关于本文的全部内容,主要介绍了一下一个monorepo工程适用哪些工具。如果没有一套完善的工具体系去支撑,monorepo也会变得一团糟。 顺便贴一下我写的管理系统的地址,关于monorepo有不明白的东西可以去看看项目里是怎么做的NOTCOOL (opens new window)

编辑 (opens new window)
上次更新: 2023/08/17, 16:55:23
使用changeset管理monorepo项目
编写一个生成git提交信息的vite插件

← 使用changeset管理monorepo项目 编写一个生成git提交信息的vite插件→

最近更新
01
小程序实现全局状态管理
07-09
02
前端检测更新,自动刷新网页
06-09
03
swiper渲染大量数据的优化方案
06-06
更多文章>
Theme by Vdoing | Copyright © 2023-2025 UzumakiItachi | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式