如何搭建Monorepo
date
Mar 27, 2024
slug
如何搭建Monorepo
status
Published
tags
备忘录
summary
利用pnpm的workspace功能来搭建monorepo是目前比较流行的方法。
首先创建项目文件夹repo,进入后执行
pnpm init
,创建packages文件夹,再在根目录下创建pnpm-workspace.yaml(https://pnpm.io/pnpm-workspace_yaml)。type
Post
利用pnpm的workspace功能来搭建monorepo是目前比较流行的实践。
首先创建项目文件夹repo,进入后执行
pnpm init
,然后创建packages文件夹,再在根目录下创建pnpm-workspace.yaml(https://pnpm.io/pnpm-workspace_yaml)。我们的项目有很多时候是通过框架的cli工具创建出来的,但这些子项目并不直接与monorepo契合,还需要进行一些改造。比如现在我们有一个frontend子项目,它是由vite创建,有一个backend子项目,它是由nest-cli创建。我们首先要更改它们package.json里的name,分别改为@repo/frontend和@repo/backend。接下来要做的工作就是把它们公共的依赖提取到根目录的package.json,注意这些公共的依赖的版本要大致相同并且提取后的依赖的版本都要兼容。如果是一个前端子项目和后端子项目,一般可以提取的依赖有typescript和pretter、eslint等等。这样提取后,相同的依赖就只会安装一份。
如果有一个shared子项目,里面用来放一些共享的工具函数。如果你直接在Nest.js创建的backend引入shared源码,会发现会报一些错误。这个时候就可以选择将shared通过tsc编译后再进行引入,就不会有什么问题了。但这个时候还有一个问题,比如frontend是通过vite创建的,它引入的库期望是ESM,而backend期望的是CommonJS,这时候shared编译后的产物就必然和其一矛盾。有两种解决的方案,第一种是是用更加复杂的构建工具分别编译出ESM和CommonJS的产物,第二种是编译成CommonJS,再去vite那边进行配置,把CommonJS转换成ESM再引入(vite本来是天然会做这一步操作的,但有时候这种Monorepo内部的引入需要手动配置一下才会进行)。
接下来是eslint的问题,你可以像vue3.0那样在根目录写一个eslint配置文件覆盖所有package,但对于cli工具生成出来的项目来说却不太好处理,因为生成脚手架的时候已经有了eslint配置文件,而且配置的差异也比较大。一个比较方便的做法就是让子项目保留脚手架生成的eslint配置文件,让它们为各自的子项目负责。也可以参考一下这篇文章的做法:https://turbo.build/repo/docs/handbook/linting/eslint。
prettier的配置文件可以直接放在根目录,因为子项目间一般不会有什么差异。
另一个问题是我们提交时如何触发eslint和prettier。一般我们会通过husky和lint-staged,在提交的时候触发eslint和prettier,但这种monorepo的情况下该如何处理呢?其中一种做法就是,在各自的子项目编写lint-staged配置,在根目录什么配置也不写。这样在根目录执行lint-staged命令的时候,子项目就会分别找到各自的配置自动执行(https://github.com/lint-staged/lint-staged?tab=readme-ov-file#how-to-use-lint-staged-in-a-multi-package-monorepo)。但是要注意这些子项目中的配置并不会自动继承根目录的配置,这也是为什么没有在根目录写配置的原因。