Nx简介
Nx 是一个智能、快速和可拓展的构建系统,非常适用但不局限于 monorepo 场景(后面主要以介绍monorepo项目为主),它主要专注于两个方向:
-
代码生成器。
开发者在日常工作中,经常要处理多种开发工具和框架,比如构建工具、测试框架、storybook等。每个工具都有自己的配置和集成方式,这可能导致复杂性增加、维护难度加大,特别是在多个开发者甚至整个团队协作的情况下。为此 Nx 给开发者提供了大量的插件(代码生成器)帮助开发者自动生成最佳实践的代码。
-
智能任务运行器。
在多 package 架构的项目中,我们不仅可以运行单个库或者项目的任务,还可能需要管理项目和库之间数百个任务。为了帮助开发者简化项目的任务管理,Nx 提供了一个强大的任务运行器,包括:
- Run Tasks in Parallel(并行运行任务)
- Cache Task Results(缓存任务结果)
- task orchestrator(任务编排)
- Run Tasks Affected(仅运行受给定更改影响的项目的任务)
代码生成器
代码生成器就像自动化脚本,旨在简化开发者的工作流程。本质上,它们是 TypeScript 函数,可以接受参数并通过以下方式帮助提高工作效率:
- 搭建新项目或为现有项目添加功能:代码生成器可以帮助快速搭建新项目,或为现有项目添加新功能,比如增加 Storybook 支持。
- 自动化重复性任务:可以自动完成开发流程中重复性的任务,减少手工操作,比如生成 redux、component、hook 模板。
- 保证代码一致性并遵循最佳实践:使用代码生成器可以确保生成的代码始终符合一致的标准,并遵循行业公认的最佳实践。
生成器基本使用
Nx 官方提供了非常丰富的插件,下面是截取的一部分,更多插件可查看官网:
这些插件可以使用 nx generate
进行调用,模板为:nx generate <plugin-name>:<generator-name> [options]
.,比如使用 @nx/react
生成一个 react 组件库。
nx generate @nx/react:library --name=react-components --directory=xxx
如果要查看某个插件中可用的生成器列表,请运行 nx list <plugin-name>
。例如,列出 @nx/react
插件中的所有生成器:
nx list @nx/react
Nx Console
Nx Console 是一个 IDE 扩展,可以在这里下载安装包。Nx Console 提供了直观的图形化界面,无论是创建应用程序、生成组件、运行测试,还是构建项目,开发者只需要通过简单的按钮和表单即可完成,而不需要记住或输入复杂的 CLI(命令行)命令。
比如下面是通过 Nx Console 去调用代码生成器生成代码:
自定义插件
Nx 除了给开发者提供官方的插件之外,还允许开发者根据项目需求定制插件,同时 Nx 还为了开发者提供了开发自定义插件的工具,能够快速生成插件模板,自定义插件会在后面的章节详细介绍。
智能任务运行器
task orchestrator (任务编排)
在多 package 架构的项目中,库和库之间肯定会存在依赖关系,因此库的任务执行需要遵循一定的顺序,比如下面这个例子:
myReactApp 项目依赖于其他库。因此,在运行 myReactApp 的构建时,需要首先构建这些依赖项,以便 myReactApp 可以使用这些库的构建产物。虽然开发者可以自己管理这些关系并设置自定义脚本以按正确的顺序构建所有项目(例如,首先构建shared-ui、utils,然后构建 product1、product2,最后构建myReactApp),这种方法不可扩展,并且随着项目的迭代,需要不断维护。
为了解决这一问题,Nx 提供了智能的任务编排功能,而任务编排的核心功能依赖于项目关系图,即Nx 能够分析并展示 workspace 中所有库之间的依赖关系图,开发者只需要执行 npx nx graph
命令就能查看整个工作区的依赖关系图。
Run Tasks in Parallel(并行运行任务)
Nx 使用了 Node.js 提供的 Child Process API 来生成子进程,并在子进程中执行任务,以此来达到并行执行任务的目的,默认值是 3,比如使用一个进程来构所有库和应用:
npx nx run-many --target=build --parallel=1
从视频中可以看到 Nx 使用一个进程按顺序依次执行每个库的 build 任务。现在将进程数改为2:
npx nx run-many --target=build --parallel=2
现在 Nx 会按顺序并行执行两个库的 build 任务。
Cache Task Results(缓存任务结果)
Nx 拥有最先进和经过实战检验的计算缓存系统,无论使用 Nx 去 build、test 或者 lint 项目,最终的结果都会被缓存,这能够大大减少一遍又一遍地 rebuild、retest 相同的代码带来的高昂代价。
⚠️ 结果可缓存意味着给定相同的输入,Nx 应该总是产生相同的输出,比如涉及到后端 API 的端到端(e2e)测试无法被缓存,因为后端的响应会影响测试结果。
在运行任何可缓存任务之前,Nx 都会计算其计算哈希,只要计算哈希相同, 运行任务的输出就是相同的,默认情况下,Nx 计算 hash 值的输入包括:
- 当前项目及其依赖库的所有文件。
- 相关全局配置。
- 第三方依赖的版本。
- 命令行的配置参数。
- 用户配置的运行时,例如 Node 的版本。
如果 Nx 未找到相应的计算哈希,Nx 将运行任务,并在任务完成后,获取输出和终端日志并将其存储在本地下面通过运行以下命令两次来查看 Nx 缓存的效果:
npx nx run-many --target=build
第一次:
第二次:
Nx 默认情况下是保守的,它宁愿过度谨慎,也不愿因为遗漏某些输入而导致错误的结果,比如说对于 build 任务,Nx 只将以 spec/test 结尾的单测文件
、tsconfig.spec.json
、eslintrc.json
、eslint.config.js
文件排除,其他文件的更改都会导致缓存失效。但是对于开发者来说像 .md
文件、eslint
文件等都不应该导致构建缓存失效。
因此 Nx 给我们提供了每个任务的 inputs 属性,该属性允许我们定义一个范围,更改这个范围中的文件后该任务的缓存会失效并重新运行任务,针对上面这种情况我们在 nx.json 中可以进行如下配置:
{
// 提供input别名,避免在inputs中重复使用相同的定义
"namedInputs": {
"default": ["{projectRoot}/**/*"],
"production": [
"default",
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/.eslintrc.json",
"!{projectRoot}/eslint.config.js",
"!{projectRoot}/**/*.md"
],
},
"targetDefaults": {
"build": {
// 定义缓存失效的条件
"inputs": ["production", "^production", "{workspaceRoot}/tsconfig.base.ts"]
}
}
}
- {projectRoot} 表示当前项目的根目录。
- {workspaceRoot} 表示当前工作区根目录。
- ^ 表示项目的依赖,比如上面的 ^production 表示当前项目及其依赖中所有的 .md、eslint 等文件都不会做为缓存失效的条件。
因此当我们运行 build 命令时,Nx 只会考虑当前项目下除 .md 、eslint 等文件的的所有文件 ,并且只考虑其依赖库的除 .md 、eslint 等文件的所有文件,最后还将考虑工作区根目录下的 tsconfig.base.ts 配置文件。
Run Tasks Affected(仅运行受给定更改影响的项目的任务)
随着项目逐渐的扩大,每次更改代码都需要重新测试、重新构建和重新 linting 所有项目会变得很慢,虽然有缓存功能,但是也别忘了缓存的背后是大量的计算和文件的读取操作。为了解决这个问题,Nx 提供了 affected 功能,旨在当项目代码变更后运行任务时,Nx 仅会运行受给定更改影响的项目的任务。比如下面这个依赖关系图:
当修改 lib10 中的代码后运行 npx nx affected -t test
Nx 首先会分析项目并确定受变更影响的最小项目集,然后仅对受影响的项目运行 test 任务。
总结
Nx 为项目开发提供了良好的工具支持,特别是其插件生态、代码生成器、任务编排、依赖关系分析等功能,能够优化代码复用、提升开发效率并简化任务管理,成为多 package 项目中的理想选择,后面的章节将会通过实践带大家全面学习 Nx 的使用。