JavaScript内核与高级编程之:`Nx` 的 `Monorepo` 架构:其在代码共享和任务执行图中的应用。

各位观众老爷,晚上好!我是今晚的讲师,很高兴能跟大家聊聊 NxMonorepo 架构,以及它在代码共享和任务执行图中的应用。别担心,今天咱们不搞那些晦涩难懂的理论,力求用最接地气的方式,把 Nx 的精髓给扒个精光。

首先,咱们先来热热身,聊聊啥是 Monorepo

啥是 Monorepo?别被吓着,其实很简单!

Monorepo,顾名思义,就是把多个项目(可以是库、应用等等)的代码都放在同一个代码仓库里。这跟传统的 Multirepo (每个项目一个仓库)是相对的。

想象一下:你家有个大花园(Monorepo),里面种了各种各样的花草树木(不同的项目)。你可以很方便地管理它们,修剪枝叶,施肥浇水,一览无余。而 Multirepo 就像你家有好几个小盆栽(每个项目一个仓库),你需要分别照顾,比较麻烦。

Monorepo 有啥好处?

  • 代码共享更方便: 不同的项目可以轻松共享代码,避免重复造轮子。
  • 依赖管理更简单: 所有项目都在一个地方,版本冲突更容易发现和解决。
  • 重构更容易: 修改一个底层库,所有依赖它的项目都能立即更新。
  • 协作更高效: 开发者可以更容易地了解整个项目的结构和依赖关系。

当然,Monorepo 也有缺点,比如仓库体积可能比较大,构建时间可能比较长。但这些问题都可以通过工具来解决,比如我们今天要讲的 Nx

Nx:Monorepo 的瑞士军刀

Nx 是一个强大的 Monorepo 构建工具,它可以帮助你更轻松地管理和构建 Monorepo 项目。它提供了很多开箱即用的功能,比如:

  • 代码生成器: 快速创建项目和组件,提高开发效率。
  • 任务执行图: 智能分析项目之间的依赖关系,并行构建和测试。
  • 缓存: 缓存构建结果,避免重复构建,加速构建过程。
  • 代码影响分析: 只构建和测试受影响的项目,提高构建效率。

简单来说,Nx 就像是 Monorepo 的瑞士军刀,帮你解决各种问题。

Nx 的 Monorepo 架构

NxMonorepo 架构主要包含以下几个核心概念:

  • Workspace: 代表整个 Monorepo 的根目录,包含所有项目和配置文件。
  • Projects: 指的是 Workspace 中的各个项目,可以是库、应用、工具等等。
  • Libraries: 可共享的代码模块,可以被多个应用或其他库依赖。
  • Applications: 可独立运行的应用程序,例如前端应用、后端服务等等。
  • Tasks: 需要执行的操作,例如构建、测试、部署等等。
  • Task Pipeline: 定义了任务之间的依赖关系,决定了任务的执行顺序。
  • Affected Commands: 只执行受代码更改影响的任务,提高构建效率。

代码共享:Nx 如何让代码“串门”?

Nx 提供了多种方式来共享代码:

  1. 共享库 (Shareable Libraries): 这是最常见的代码共享方式。你可以创建一个 Library,然后让其他项目依赖它。

    # 创建一个名为 "util" 的库
    nx generate @nx/js:library util

    这个命令会在 libs/util 目录下创建一个新的库。你可以在这个库里编写一些通用的工具函数,然后在其他项目中使用。

    // libs/util/src/lib/util.ts
    export function greet(name: string): string {
      return `Hello, ${name}!`;
    }

    然后,在你的应用或者其他库中,你可以这样使用这个函数:

    // apps/my-app/src/app/app.component.ts
    import { greet } from '@my-org/util'; // 注意这里的别名 @my-org/util
    
    export class AppComponent {
      title = greet('World');
    }

    Nx 会自动处理依赖关系,确保你的应用可以正确地找到并使用这个库。

  2. 路径映射 (Path Mapping): Nx 使用 tsconfig.base.json 文件来配置路径映射。这意味着你可以为你的库定义一个别名,然后在其他项目中使用这个别名来引用它。 这样,即使你移动了库的位置,也不需要修改所有引用它的代码。

    打开 tsconfig.base.json 文件,你会看到类似这样的配置:

    {
      "compilerOptions": {
        "paths": {
          "@my-org/util": ["libs/util/src/index.ts"]
        }
      }
    }

    这个配置告诉 TypeScript 编译器,当你使用 @my-org/util 时,实际上是指向 libs/util/src/index.ts 文件。

  3. 工具函数 (Utility Functions): Nx 鼓励你将一些通用的工具函数放在一个单独的库里,然后在其他项目中使用。 这样可以避免代码重复,提高代码的可维护性。

    例如,你可以创建一个名为 "api-client" 的库,用于封装 API 请求。

    nx generate @nx/js:library api-client

    然后,在 libs/api-client/src/lib/api-client.ts 文件中,你可以编写一些通用的 API 请求函数:

    // libs/api-client/src/lib/api-client.ts
    import axios from 'axios';
    
    export async function get(url: string): Promise<any> {
      try {
        const response = await axios.get(url);
        return response.data;
      } catch (error) {
        console.error(`Error fetching data from ${url}:`, error);
        throw error;
      }
    }

    然后,在你的应用或者其他库中,你可以这样使用这个函数:

    // apps/my-app/src/app/app.component.ts
    import { get } from '@my-org/api-client';
    
    export class AppComponent {
      async ngOnInit() {
        const data = await get('/api/users');
        console.log(data);
      }
    }

任务执行图:Nx 如何让任务“排队”?

Nx 的任务执行图 (Task Execution Graph) 是一个非常有用的功能。它可以分析项目之间的依赖关系,然后智能地安排任务的执行顺序。 这意味着 Nx 可以并行构建和测试你的项目,从而大大提高构建效率。

Nx 是怎么知道项目之间的依赖关系的呢? 主要通过以下几种方式:

  1. package.json 文件: Nx 会分析 package.json 文件中的 dependenciesdevDependencies 字段,来确定项目之间的依赖关系。

  2. tsconfig.json 文件: Nx 会分析 tsconfig.json 文件中的 paths 字段,来确定项目之间的依赖关系。

  3. nx.json 文件: nx.json 文件是 Nx 的配置文件。你可以在这个文件中定义任务之间的依赖关系。

    // nx.json
    {
      "tasksRunnerOptions": {
        "default": {
          "runner": "nx/tasks-runners/default",
          "options": {
            "cacheableOperations": ["build", "test", "lint", "e2e"],
            "dependsOn": [
              "{workspaceRoot}/node_modules/.bin/nx affected --target=build --files={files}",
              "{workspaceRoot}/node_modules/.bin/nx affected --target=test --files={files}"
            ]
          }
        }
      },
      "affected": {
        "defaultBase": "main"
      },
      "targetDependencies": {
        "build": [
          { "target": "lint", "projects": "dependencies" }
        ]
      }
    }

    在这个例子中,targetDependencies 字段定义了 build 任务依赖于 lint 任务。这意味着在执行 build 任务之前,Nx 会先执行 lint 任务。 projects: "dependencies" 表示只对 build 任务所依赖的项目执行 lint 任务。

  4. 代码分析: Nx 还可以通过分析代码来确定项目之间的依赖关系。 例如,如果一个项目导入了另一个项目的模块,Nx 就会认为这两个项目之间存在依赖关系。

有了这些依赖关系,Nx 就可以构建任务执行图了。 你可以使用 nx graph 命令来可视化任务执行图。

nx graph

这个命令会打开一个网页,显示你的 Monorepo 中所有项目和任务的依赖关系。

Affected Commands:只打“有鬼”的地方

Affected CommandsNx 的另一个非常有用的功能。 它可以只执行受代码更改影响的任务。 这意味着如果你只修改了一个库的代码,Nx 就只会构建和测试这个库,以及依赖于这个库的项目。 这样可以大大提高构建效率。

你可以使用 nx affected 命令来执行受影响的任务。

# 构建受影响的项目
nx affected:build

# 测试受影响的项目
nx affected:test

# 运行 lint 受影响的项目
nx affected:lint

Nx 是怎么知道哪些项目受到代码更改的影响的呢? 主要通过以下几种方式:

  1. Git: Nx 会使用 Git 来比较当前分支和基准分支之间的差异。 默认情况下,基准分支是 main 分支。 你可以使用 --base 参数来指定基准分支。

    nx affected:build --base=develop

    这个命令会比较当前分支和 develop 分支之间的差异,然后构建受影响的项目。

  2. 文件列表: 你可以使用 --files 参数来指定一个文件列表。 Nx 会根据这些文件来确定受影响的项目。

    nx affected:build --files=libs/util/src/lib/util.ts

    这个命令会构建 libs/util 库,以及依赖于这个库的项目。

  3. 环境变量: Nx 还可以从环境变量中读取文件列表。 这在 CI/CD 环境中非常有用。

    例如,你可以设置一个名为 NX_AFFECTED_FILES 的环境变量,然后在 nx affected 命令中使用它。

    NX_AFFECTED_FILES=libs/util/src/lib/util.ts nx affected:build

一个简单的例子:

假设我们有一个 Monorepo,包含以下项目:

  • libs/ui: 一个 UI 组件库。
  • libs/data: 一个数据访问库。
  • apps/admin: 一个后台管理应用,依赖于 libs/uilibs/data
  • apps/store: 一个电商应用,依赖于 libs/ui

如果我修改了 libs/ui 的代码,那么 Nx 会自动构建 libs/uiapps/adminapps/store。 如果我修改了 libs/data 的代码,那么 Nx 会自动构建 libs/dataapps/admin。 这就是 Affected Commands 的威力。

Nx 的一些高级用法:

  • 自定义任务: 你可以定义自己的任务,并在 nx.json 文件中配置它们的依赖关系。
  • 插件: Nx 提供了很多插件,可以帮助你更轻松地管理你的 Monorepo。 例如,@nx/react 插件可以帮助你创建 React 应用和库。
  • 代码生成器: 你可以使用代码生成器来快速创建项目和组件。

总结:

Nx 是一个强大的 Monorepo 构建工具,它可以帮助你更轻松地管理和构建 Monorepo 项目。 它提供了很多开箱即用的功能,比如代码生成器、任务执行图、缓存和代码影响分析。 通过使用 Nx,你可以提高开发效率,减少构建时间,并改善代码质量。

希望今天的讲座能对你有所帮助。 如果你还有其他问题,欢迎随时提问。 谢谢大家!

为了方便大家理解,我把今天讲的内容整理成了一个表格:

功能 描述 优点 缺点
Monorepo 将多个项目放在同一个代码仓库中。 代码共享方便、依赖管理简单、重构容易、协作高效。 仓库体积可能比较大、构建时间可能比较长。
Nx 一个强大的 Monorepo 构建工具。 提供了很多开箱即用的功能,比如代码生成器、任务执行图、缓存和代码影响分析。 学习曲线稍陡峭。
代码共享 不同的项目可以轻松共享代码,避免重复造轮子。 提高代码复用率、减少代码冗余、降低维护成本。 需要仔细规划代码结构,避免循环依赖。
任务执行图 智能分析项目之间的依赖关系,并行构建和测试。 提高构建效率、减少构建时间。 需要正确配置任务之间的依赖关系,否则可能导致构建失败。
Affected Commands 只执行受代码更改影响的任务,提高构建效率。 大幅提高构建效率、减少构建时间。 需要正确配置代码影响分析规则,否则可能导致漏构建或误构建。
共享库 可共享的代码模块,可以被多个应用或其他库依赖。 方便代码复用,减少代码冗余。 需要维护库的版本,避免版本冲突。
路径映射 为你的库定义一个别名,然后在其他项目中使用这个别名来引用它。 方便重构,即使你移动了库的位置,也不需要修改所有引用它的代码。 需要维护 tsconfig.base.json 文件,确保路径映射正确。
工具函数 将一些通用的工具函数放在一个单独的库里,然后在其他项目中使用。 避免代码重复,提高代码的可维护性。 需要仔细选择哪些函数应该放在工具库里。
nx graph 可视化任务执行图。 方便理解项目之间的依赖关系,优化构建流程。 需要安装 nx 命令行工具。
nx affected 执行受影响的任务。 提高构建效率、减少构建时间。 需要正确配置代码影响分析规则,否则可能导致漏构建或误构建。

希望这个表格能帮助大家更好地理解 NxMonorepo 架构。 再次感谢大家的参与!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注