如何使用 Git 将 JavaScript 和 TypeScript 项目配置为 Monorepo

随着开发过程的逐渐深入,项目中的文件越来越多,在单一项目中管理这些文件就变得越来越困难。您可能会发现前端和后端项目使用了某些共享模块,同时,您也需要管理这些模块的不同版本。
Monorepo 是一种项目结构,它能够使您在一个地方完成以上提到的所有复杂操作。
在尝试使用 Lerna 配置 Monorepo 的过程中我失败了。使用 Yalc 和 Yarn Workspace 在将项目移出 Monorepo 的时候也非常麻烦。
最终,我发现一种通过 git submodules 命令来达到相同效果的方法。 Git 在解决代码冲突方面表现优秀,Git 的分支管理功能同样可以用来控制版本。在 Git 或者 Gitlab 上,您可以免费拥有无限多的私人仓库。除此之外,在使用 Webpack 的 TypeScript 或者 JavaScript 项目中,您也可以配置别名来到达模块引入路径清晰可读的效果。
在本文中,我将展示如何通过设置依赖和子模块来构建一个 monorepo 项目,以及如何设置模块别名,之后我将谈论在这一设置过程中我遇到的一些该方法的缺点。
浏览 git-monorepo-project 查看项目最终设置效果~
1. 配置依赖
每一个依赖都是一个 git 模块。
每个依赖都有自己单独的 git 仓库,这个仓库可以是一个带有 package.json 或者是bundled/transpile 这样的 JavaScript 文件的完整项目,或者也可以是一个仅仅包含简单 JavaScript 或 Typescript 文件的项目。
创建一个依赖仓库
您可以创建一个共有仓库或者是私有仓库,并确保贡献者能够访问,然后对该仓库推送代码。
依赖仓库版本管理
我们通常需要不同的依赖版本,这一过程也叫做版本化。版本化允许我们更改特定的版本而不影响那些使用其他版本依赖的项目。
我们可以使用分支来完成版本化配置。例如,使用 main 分支作为最新版本代码,stable@v0.0.1 分支用来表示 0.0.1 分支,以此类推。
2. 构建项目
在上图中,模块 1 是前端项目的一个子模块。
使用 git 设置 monorepo 项目的主要思路就是将步骤 1 中创建的依赖添加为子模块。
在项目结构中,每个子模块都是一个本地目录。因此,我们可以将这些子模块和本地目录一样在代码中引入或者做其他操作。作为一个 git 仓库,任何已提交的改变在推送之后同样会被复制一份提交到关联的项目中。
项目结构
管理项目的一个好办法就是将所有的依赖都放置于 src/packages 目录下,如下是实例目录树:
project-root/
├── .gitsubmodules
├── package.json
├── tsconfig.json
├── webpack.config.js
└── src/
├── index.ts
├── packages/
│ ├── module1 (submodule)/
│ │ ├── package.json
│ │ └── src/
│ │ └── index.ts
│ ├── module2 (submodule)/
│ │ └── index.ts
│ └── ...
└── ...
添加依赖
在完成步骤 1 中的创建依赖后,您可以使用 git submodule add 命令将其添加为一个子模块,然后将它存储在 src/packages 目录下,例如:
$ git submodule add https://github.com/username/module-name.git src/packages/module-name
如果要添加特定版本的依赖仓库,在添加子模块的时候,使用 b 标识,如下:
$ git submodule add -b stable@v0.0.1 https://github.com/username/module-name.git src/packages/module-name
现在,您可以将新添加的依赖作为本地目录引入了,例如:import Module1 from “../packages/module1”;
从另一个客户端操作
在配置 monorepo 之后,我们就可以很容易的在另一台电脑上安装项目及其依赖项了。在你有很多台工作站或者有队友的时候这是很有用的。
从另一台机器上安装项目需要经历以下步骤:
- 在使用 Clone 命令的时候添加 -recursive 标识。这一命令会下载当前项目以及所有的子模块。例如 $ git clone -recursive github.com/username/ma…
- 如果必要的话,以 “npm install” 或者 “yarn” 命令安装 node 模块。
完成以上两步后,项目就可以准备启动了!
3. 配置模块别名
按照上述步骤配置 monorepo 带来了一个问题:引入路径较长且不易读。例如,当从 src/pages/dashboard/profile/ProfileMenu.tsx 文件中引入 “packages/modules1” 时,引入路径是 “../../../packages/module1”。
很幸运的是,我们可以通过设置模块别名来达到缩短引入路径的目的:注意:如果你在使用 webpack 来转译 TypeScript 文件,你将需要同时为 JavaScript 和 TypeScript 设置项目别名。
为 JavaScript 项目设置模块别名
您可以在使用 webpack 的 JavaScript 项目中修改 webpack.config.js 文件中的 resolve.alias 配置项来配置模块别名。对于使用 CRA 创建的 React 应用,可以使用 react-app-rewired 命令来覆盖 webpack 配置。
示例 webpack 配置如下:
// webpack.config.js
module.exports = {
…,
resolve: {
alias: {
// import Module1 from “module1”
"module1": "path/to/src/packages/module1",
// this config allow importing any modules
// under src/packages directory
// i.e import Module1 from “packages/module1”
"packages": "path/to/src/packages",
...
}
}
}
更多示例请参考 webpack.config.js
为 TypeScript 项目配置模块别名
您可以通过 tsconfig.json 文件中的 compilerOptions.paths 配置项来为 TypeScript 项目配置别名,示例如下:
{
"compilerOptions": {
…,
"baseUrl": "./src",
"paths": {
// import Module1 from “module1”
"module1": "packages/module1",
"module1/*": "packages/module1/*",
// this config allow importing any modules
// under src/packages directory
// i.e import Module1 from “packages/module1”
"packages": "packages",
"packages/*": "packages/*",
...
}
}
}
请确保 “baseUrl” 配置项一定存在,该配置帮助编译器解析依赖路径,更多信息请参阅 tsconfig.extends.json。