monorepo 是一种组织管理代码的方法,从字面上来理解就是把与一个项目关的 package、module、project等都放在一个仓库中进行管理。与之相对应的管理方式是 multirepo,把与项目相关的业务拆分成不同的 module 或者 package 放在不同的仓库中,彼此之间独立维护。
随着一个项目的不断迭代更新,我们会根据公司复杂的业务将部分复用代码或者常用组件拆分成 module 或者 library。是将这些众多的独立的 module 放在不同的 repository 中,然后由划分了不同职责的小组来进行独立维护,还是将这些 module 放入一个 repository 中由所有人一起维护。这就就需要考虑到 monorepo 和 multirepo 各自的优缺点。在我接触公司业务之初,尝试使用过 mutirepo,当时我们使用私有的 npm 管理多个独立的业务模块,这样既保证了主项目代码的整洁程度,也在项目初期大幅度降低了代码耦合度。可是随之而来的问题是,每次独立模块的更改,都需要单独的打包、发布、编写 changelog。在人手不多的初期,这种做法增加了我们的维护成本。通过资料查阅,我知道了还有 monorepo 这种管理方式,并且随之了解到了 Nx 这款适配 Angular 框架的扩展工具,我们把所有 module 和 libaray 放在同一个 repository 下,这样我们可以使用同一套标准管理代码,独立模块中的修改能够立马展现,代码结构也能得到改善。
官网:nx.dev
Nx 是一个用来构建 monorepos 的开发工具。自己从 Angular 6.X 版本的时候开始尝试使用 Nx 管理项目代码,那时候的 Nx 还只支持对 Angular 框架的扩展,现在的 Nx 添加了对 React 框架的支持,并且可以集成使用 Cypress, Jest, Prettier, TypeScript 等现在构建工具,支持 NestJs (一款 nodejs 后端框架),完成了对整个全栈生态的覆盖。非常推荐大家上手尝试一下。
本篇文章通过 Nx 创建一个 fullstack 项目,以最简单的 todo 为例,分别有两个 application,两个 library,通过这个简单的例子展示一下 Nx 强大的工作流,以及如何管理 monorepo,如何集成最新的构建工具的能力。
创建一个空的 Nx workSpace 项目非常简单,你可以使用以下命令安装
npx create-nx-workspace@latest todoappnpm init nx-workspace todoappyarn create nx-workspace todoapp如果你已经拥有了一个自己的 Angular 项目,也可以使用 ng add @nrwl/workspace 命令将原有项目,改造成 monorepos 。脚本会在保留项目原有代码的基础上,修改文件夹结构。一个通过 Nx 创建的 Angular monorepo 空项目文件结构如下图所示:
简单介绍一下几个特殊文件/文件夹:
添加前端项目,通过以下命令创建:
ng generate @nrwl/angular:application frontend
这条命令会在 apps 目录下生成 fronted 和 frontend-e2e(后续用来编写 e2e 测试) 两个项目,生成的项目和 Angular Cli 生成出来的没有任何区别
添加后端项目,通过以下命令创建:
ng generate @nrwl/nest:application backend --frontendProject=frontend
这条命令会在 apps 目录下生成 backend 项目,后端项目我选择了使用 Nest,Nx 本身也对 Nest 有着很好的支持,语法更符合 Angular 程序员
接下来我们简单编写一下项目逻辑,首先是后端服务器
apps/backend/src/app/app.service.ts
import { Injectable } from '@nestjs/common';
import { Todo } from '@todoapp/data';
@Injectable()
export class AppService {
/** 原始数据 */
todos: Todo[] = [{ title: 'Todo1' }, { title: 'Todo2' }];
/** 增加数据 */
addTodo(): void {
this.todos.push({
title: `Random ${ Math.floor(Math.random() * 100) }`
});
}
}
apps/backend/src/app/app.controller.ts
import { Controller, Get, Post } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
/** get 方法,用来拉取初始数据 */
@Get('todos')
getData() {
return this.appService.todos;
}
/** post 方法,用来增加数据 */
@Post('add')
add() {
return this.appService.addTodo();
}
}
编写前端
apps/frontend/src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Todo } from '@todoapp/data';
@Component({
selector: 'todoapp-root',
templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
todos: Todo[];
constructor(private http: HttpClient) {
this.fetch();
}
/** 请求数据 */
fetch(): void {
this.http.get('/api/todos')
.subscribe((t: any) => {
this.todos = t;
});
}
/** 增加数据 */
addTodo(): void {
this.http.post('/api/add', {})
.subscribe(() => {
this.fetch();
});
}
}
apps/frontend/src/app/app.component.html
<h1>Todo2</h1>
<todoapp-task-list [todos]="todos"></todoapp-task-list>
<button id="add-todo" (click)="addTodo()">Add Todo</button>
创建一个 data library,用来存放 frontend 和 backend 中共用的数据结构 ToDo
ng generate @nrwl/workspace:library data
libs/data/src/index.ts
export interface Todo { title: string };
创建一个 ui library,用来存放 frontend 中抽象的组件逻辑,此处我们创建一个 task-list 组件,用来显示 todo-list
ng generate @nrwl/angular:library ui
ng generate @schematics/angular:component task-list --project=ui --export
最后我们的 libs 文件夹中会是这样的结构:
这样我们的公用逻辑都被拆分到了 libs 文件夹中,可以由不同小组成员去负责和维护,降低了代码耦合度。使用这些 lib 也很简单,例如 import { Todo } from '@todoapp/data' import { UiModule } from '@todoapp/ui'
如果只是以上这些功能,可能会觉得比较没意思,只是满足了基本的需求,现代的前端工具应该更多的是智能酷炫,方便团队之间复杂的协作。接下来我展示一些实用的功能。
我们不希望后端团队导入前端团队编写的 UiModule 。我们可以借用 tslint 实现,nx.json 可以给不同 app 和 lib 打上特殊的 tag,tslint 会在 import 语句中检查是否在当前项目中导入了不属于自己的模块。
在 nx.json 中,我们给 UiModule 打上 "platform:frontend" 的标签,backend 打上 "platform:backend" 标签
在 tslint.json 中增加 "nx-enforce-module-boundaries" 规则,并且规定 "platform:backend" 标签下只能导入标签为 "platform:backend" 的模块。
配置完毕,重启编辑器,我们能够顺利看到 backend 项目中 module 导入 UiModule 会提示错误。
当项目越来越庞大时,我们可能需要查看不同项目之间的依赖关系,Nx 提供数据可视化的方案,可以以下命令查看:
nx affected:dep-graph
ng run frontend-e2e:e2e --watch
强大的功能还有很多,不一一介绍了,可以上官网查阅
Nx 可以用来以 monorepos 的方式构建全栈项目,比较像是 Angular Cli 的扩展,所有 Angular Cli 能做的它都能做,Nx 对 Angular 项目的兼容度很高,熟悉 Cli 命令的人能够快速上手。如果想要尝试以更加工程化的方式管理业务代码,推荐大家尝试
武汉格发信息技术有限公司,格发许可优化管理系统可以帮你评估贵公司软件许可的真实需求,再低成本合规性管理软件许可,帮助贵司提高软件投资回报率,为软件采购、使用提供科学决策依据。支持的软件有: CAD,CAE,PDM,PLM,Catia,Ugnx, AutoCAD, Pro/E, Solidworks 等。