Nx带来极致的前端开发体验——任务编排

为什么需要任务编排?

在 monorepo(多 package)架构的项目中,package 和 package 之间肯定会存在依赖关系,因此在运行一个 package 的任务执行时,可能需要依赖其他 package 先执行对应的任务,比如下面这个例子:

myReactApp 项目依赖于其他库,因此,在运行 myReactApp 的 build 任务时,需要首先执行其依赖的 package 的 build 任务,以便 myReactApp 可以使用这些 package 的构建产物。虽然开发者可以自己管理这些关系并设置自定义脚本以按正确的顺序构建所有项目(例如,首先构建shared-ui、utils,然后构建 product1、product2,最后构建myReactApp),但是这种方法是不可扩展的,并且随着项目的迭代,需要不断维护。

为了解决这一问题,Nx 提供了智能的任务编排功能,而任务编排的核心功能依赖于项目关系图,即 Nx 能够分析并展示 workspace 中所有库之间的依赖关系图,开发者只需要执行 npx nx graph 命令就能查看整个工作区的依赖关系图。

配置任务依赖

Nx 允许开发者以"规则"的形式定义任务依赖关系,并在运行任务时遵循这些规则。任务的依赖关系可以全局定义也可以局部定义,全局定义任务依赖关系可以在根目录的 nx.json 文件中定义:

jsx 复制代码
{
  ...
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build", "prebuild"]
    },
    "test": {
      "dependsOn": ["build"]
    }
  }
}

除此之外,开发者还可以在每个 package 下的 project.json 或者 package.json 文件中单独为每个 package 定义任务依赖关系:

jsx 复制代码
// package.json
{
  "name": "myreactapp",
  "dependencies": {},
  "devDependencies": {},
  ...
  "nx": {
    "targets": {
      "build": {
		    "dependsOn": ["^build", "prebuild"]
		  },
	    "test": {
		    "dependsOn": ["build"]
	    }
    }
  }
}

// proejct.json
{
  "name": "myreactapp",
  ...
  "targets": {
    "prebuild": {
      "command": "echo Prebuild"
    },
    "build": {
      "command": "echo Build",
      "dependsOn": ["^build", "prebuild"]
    }
  }
}

在运行 nx build myreactapp 时,上述配置将告诉 Nx:

  • 在 myreactapp 中运行 build 命令。
  • 在运行 build 命令之前先运行 prebuild 命令,因为在配置中定义了 build 命令和 prebuild 命令之间的依赖关系("dependsOn": "prebuild")。
  • 在运行 build 命令之前先运行所有依赖的 package 对应的 build 命令,因为在配置中定义了 build 命令和所有依赖的 package 对应的 build 命令之间的依赖关系("dependsOn": "\^build")。

并行执行任务

Nx 使用了 Node.js 提供的 Child Process API 来生成子进程,并在子进程中执行任务,以此来达到并行执行任务的目的,默认值是 3。有了任务并行机制,执行 nx build myreactapp 的构建流程可能如下:

如果想调整并发量,可以在执行 build 命令时传入 parallel 变量:

jsx 复制代码
npx nx build myreactapp --parallel=5

同时 Nx 也允许开发者全局配置并发量,可以在根目录的 nx.json 文件中配置:

jsx 复制代码
// nx.json
{
  "parallel": 5
}

常见陷阱

循环依赖

在 monorepo(多 package)结构下,如果开发者将代码划分成定义明确的内聚单元,那即使是一个小型组织最终也会拥有数十或数百个package。如果他们都能自由地相互依赖,最终就会出现混乱,工作空间将变得难以管理,常见的问题就是循环依赖。

例如有四个 package,libA 依赖 libB,libB 依赖 libC,libC 依赖 libD,最后 libD 又依赖 libA,最终形成的依赖关系图如下:

Nx 为了帮助开发者解决这一问题,提出了enforce module boundaries 的方案,即通过声明的方式对项目如何相互依赖施加约束,可查看官方详细介绍。在项目中配置 enforce module boundaries 主要有两步:

  • 为 package 增加标签。

    Nx 引入了标签功能,开发者可以根据 package 的功能类型为 package 打上相应的标签,例如 scope:module 表示功能模块相关的 package,scope:utils 表示一个常用的工具函数,scope:components 表示业务性的通用组件,scope:shared-ui 表示基础的ui组件等。

    下面分别对这四个 package 打上标签,以 libA 为例,我如果想给他添加一个 scope:module 标签,可直接在 project.json 或者 package.json 中配置:

    jsx 复制代码
    // package.json
    {
      // ... more project configuration here
      "nx": {
        "tags": ["scope:module"]
      }
    }
    
    // project.json
    {
      // ... more project configuration here
      "tags": ["scope:module"]
    }

    libB 打上scope:components 标签,libC 和 libD 打上 scope:utils 标签。

  • 全局添加eslint配置

    在项目的根目录,Nx 会默认生成一个 eslint 配置文件,里面有一条规则项就表示模块边界,开发者可以在 depConstraints 配置中配置标签的依赖关系,以上面的标签为例:

    jsx 复制代码
    rules: {
        "@nx/enforce-module-boundaries": [
            "error",
            {
                enforceBuildableLibDependency: true,
                allow: [
                    "^.*/eslint(\\.base)?\\.config\\.[cm]?[jt]s$"
                ],
                depConstraints: [
                    {
                        sourceTag: "scope:module",
                        onlyDependOnLibsWithTags: [
                            "scope:components",
                            "scope:utils"
                        ]
                    },
                    {
                        sourceTag: "scope:components",
                        onlyDependOnLibsWithTags: [
                            "scope:utils"
                        ]
                    }
                ]
            }
        ]
    }

    如果此时在 libB 中引用 libA,eslint 会出现报错提示:

小结

本篇文章介绍了 Nx 在 monorepo (多package)项目中的任务编排机制,阐述了其如何通过依赖关系图自动分析和执行任务顺序,避免开发者手动维护复杂的构建流程。同时最后也指出了在monorepo 中常见的循环依赖问题,为了解决这一问题 Nx 提供了 enforce module boundaries 方案,通过为 package 添加标签及配置 ESLint 规则,有效规范依赖关系,防止架构混乱,从而保障大型多包项目的可维护性与扩展性。

相关推荐
code_slave(码畜)2 分钟前
微服务架构落地:消息队列架构设计(下篇)——消息堆积、死信治理与集群监控告警实战
spring boot·spring cloud·微服务·云原生·中间件·架构
茶底世界之下15 分钟前
预乘 Alpha 图层合成为什么会越叠越暗?
架构·swift
蜡台1 小时前
AI Agent 架构终极教程:从原理分层、四大范式到生产级落地实战
网络·人工智能·架构
粥里有勺糖1 小时前
安利一下最近用的桌面“Agent” | T3 Code
前端·github·ai编程
xy34531 小时前
Axure 9.0 动态面板基本操作
前端·ui·html·axure·原型·产品设计
不停喝水1 小时前
【前端转全栈java速通课】 项目实战④7-11节 操作数据库-登录-注册-修改密码-注销-mubatis-plus简化crud-
java·前端·数据库
一水鉴天2 小时前
统一对象拓扑(UOT)设计全链定稿:从三件事分离到三继承 之1 20261010(豆包“查看聊天内容”继续)
前端·css
杨杰3982 小时前
我给嵌入式设备写了个 6MB 的 JavaScript 运行时(xuan)
javascript
栖凤2 小时前
Java 的 try-catch 在 Agent 里失效了,我用了 5 个模式才兜住
java·前端·python
粥里有勺糖2 小时前
视野修炼第136期 | 前端小恐龙"Deno"被收购
前端·github·ai编程