您的位置:

Monorepo开发

一、什么是Monorepo

Monorepo是指在一个代码仓库中管理多个项目,每个项目可以拆分成不同的模块,统一的依赖管理,共享代码和配置等。相比于多个独立的仓库,在monorepo中可以更容易地实现代码复用和协同开发。

在实际应用中,常见的使用Monorepo的场景有:

  • 多个相关的项目有共享代码的需求,例如同一公司或同一部门的多个项目。
  • 前端应用拆分成多个模块或组件,共享依赖。
  • 微服务架构中,多个服务共享公共的代码和配置。

而在monorepo中,一个典型的项目结构通常如下:

root/
├── package.json
├── packages/
│   ├── app/
│   ├── lib/
│   └── common/
└── scripts/

其中,根目录下的package.json包含所有项目的相关信息和依赖,packages/目录下存放不同的项目或模块,scripts/目录下存放一些脚本和配置文件。

二、Monorepo的优缺点

1. 优点

(1)统一的依赖管理

在Monorepo中,所有的项目和模块共享同一个package.json,可以大大简化依赖管理。例如,对于共同依赖的库,只需要在package.json中进行一次安装即可,而不需要每个项目单独安装。

(2)共享代码

在Monorepo中,多个项目之间可以共享代码和模块,避免重复编写和维护代码,提高了代码的复用性和可维护性。此外,共享的代码也可以更容易地进行版本控制和更新。

(3)容易进行协同开发

由于所有的代码都在一个仓库中,可以更容易地进行代码协同开发,例如可以使用git子模块、git subtree等技术来让开发者只克隆需要关注的部分,方便团队协同开发。

2. 缺点

(1)构建速度慢

由于Monorepo中会有多个项目和模块的代码,每次构建都需要对每个模块进行编译和构建,如果项目非常庞大,构建速度会明显变慢。

(2)发布部署复杂

在Monorepo中,不同项目和模块之间存在依赖关系,如果需要对某个模块进行修改或升级,需要考虑依赖关系对其他模块的影响,因此发布和部署可能会更加复杂。

三、使用Monorepo管理项目

在本节中,我们将通过一个简单的示例来演示如何使用Monorepo管理项目。

1. 初始化项目

首先,我们需要在本地创建一个新的文件夹,例如monorepo-test,并执行以下命令初始化项目:

mkdir monorepo-test
cd monorepo-test
npm init

在创建package.json时,可以将Monorepo相关的配置设置成项目的初始化信息:

{
  "name": "monorepo-test",
  "private": true,
  "workspaces": [
    "packages/*"
  ]
}

private: true表示这是一个私有库,workspaces: ["packages/*"]表示我们的工作区设置在packages/目录下。

2. 创建项目

现在我们来创建一个新的项目,可以使用lerna命令来快捷地创建:

npx lerna create app --dependencies=react

你会看到一个新的packages/app目录创建出来。

现在,我们需要在app目录下添加一个React组件,例如:

// packages/app/src/Hello.js
import React from 'react';

export default function Hello({ name }) {
  return 

Hello, {name}!

; }

然后,我们需要在packages/app目录下创建package.json,并在其中添加依赖和构建指令:

npm init -y
npm install --save react
// packages/app/package.json
{
  "name": "app",
  "version": "0.0.1",
  "scripts": {
    "build": "npm run build:lib",
    "build:lib": "rollup -c",
    "start": "react-scripts start"
  },
  "devDependencies": {
    "@babel/core": "^7.12.3",
    "@babel/plugin-proposal-class-properties": "^7.12.1",
    "@babel/preset-env": "^7.12.1",
    "@rollup/plugin-commonjs": "^16.0.0",
    "@rollup/plugin-node-resolve": "^10.0.0",
    "rollup": "^2.32.1",
    "rollup-plugin-babel": "^4.4.0",
    "rollup-plugin-css-only": "^3.1.0",
    "rollup-plugin-terser": "^7.0.2"
  },
  "dependencies": {
    "react": "^17.0.1",
    "react-dom": "^17.0.1"
  }
}

在这里,我们使用了rollup进行打包和构建,同时添加了一些常见的依赖和插件。

3. 创建依赖模块

现在,我们需要创建一个独立的依赖模块,用于在不同的项目中共享代码和逻辑。

我们可以使用lerna来快速创建一个空的模块:

npx lerna create common

然后,在packages/common目录下,创建一个空的JavaScript文件index.js,它将作为我们的共享代码的入口点。

// packages/common/index.js

由于common模块是一个纯JavaScript模块,因此我们只需要在common/package.json中添加必要的依赖和构建脚本即可:

// packages/common/package.json
{
  "name": "common",
  "version": "0.0.1",
  "main": "index.js",
  "scripts": {
    "build": "npm run build:lib",
    "build:lib": "babel src --out-dir lib",
    "test": "echo \"No tests yet...\""
  },
  "devDependencies": {
    "@babel/core": "^7.12.3",
    "@babel/preset-env": "^7.12.1",
    "babel-jest": "^26.6.3",
    "jest": "^26.6.3"
  },
  "dependencies": {}
}

在这里,我们使用了babel来进行编译和构建,同时添加了一些常见的依赖和插件。

4. 在app中使用common模块

现在,我们可以在app模块中引入common模块,并使用common模块中的函数。

首先,我们需要在app/package.json中添加对common模块的依赖:

"dependencies": {
  "react": "^17.0.1",
  "react-dom": "^17.0.1",
  "common": "^0.0.1"
}

然后,在app目录下的任意JavaScript文件中,可以轻松地引入和使用common模块中的函数:

// packages/app/src/Hello.js
import React from 'react';
import common from 'common';

export default function Hello({ name }) {
  return 

{common.sayHello(name)}

; }

在这里,我们使用common.sayHello来获取在common模块中定义的函数,并将结果渲染到页面中。

5. 构建和测试

现在,我们可以使用lerna来进行对我们的项目的构建和测试了:

npx lerna run build

这将会使用rollupbabel来构建我们的app和common模块。

npx lerna run test

这将会使用jest来运行我们的测试用例。

四、总结

Monorepo虽然在一些场景下使用起来会有些复杂,但是其优点同样非常明显,特别是在多个项目和模块之间有共享代码和统一依赖管理的需求时,使用Monorepo可以显著提高开发效率和可维护性。

在实际应用中,使用Monorepo需要清晰的项目结构和依赖关系管理,动态调整不同项目和模块的关系需要仔细考虑,同时需要合理地使用各种工具和技术实现代码隔离和部署。