- 年轻人的第一个 nx 插件
- 拒绝重复劳动,自动为项目添加 target
- 如何使用 nx 缓存构建产物
- 给大代码库的类型检查提速
完成了自制的插件过后,我们得往需要使用 lingui 的项目中添加相关的 target,项目少还好说,项目一多,这就变成纯体力劳动了。还好, nx 提供了 Project Inference 机制,给插件加上几行代码,就可以让 nx 自动为项目添加合适的 target。
与 executor 不同,project inference 功能需要在 nx.json
中注册:
{
"plugins": [
"my-nx-plugin"
]
}
Project inference 功能由 my-nx-plugin
模块默认导出的两个变量来实现:
projectFilePatterns
主要用来识别项目文件,项目目录中匹配的文件会作为参数传递给registerProjectTargets
registerProjectTargets
是一个用来根据项目文件推断 targets 的函数,它返回一个Record<string, TargetConfiguration>
,即我们在project.json/targets
中编写的内容。
一个给项目添加 lingui target 的实现如下:
import { workspaceRoot } from '@nrwl/devkit';
import * as path from 'path';
import * as fs from 'fs';
import type { TargetConfiguration } from '@nrwl/devkit';
export const projectFilePatterns = ['package.json'];
export function registerProjectTargets(projectFilePath: string): Record<string, TargetConfiguration> {
// 通过导入的 workspaceRoot 变量来获取当前 nx workspace 的根目录,这样可以将 projectFilePath 转换为绝对路径
const projectRoot = path.join(workspaceRoot, path.dirname(projectFilePath));
return {
...linguiTargets(projectRoot),
};
}
function linguiTargets(projectRoot) {
const packageJSON = fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8');
// 只要项目依赖了 lingui,就给它添加 lingui target
if (packageJSON.indexOf('lingui') > 0) {
return {
lingui: {
executor: 'nx-plugin:lingui',
options: {
locales: path.join(projectRoot, 'locales'),
}
}
}
}
}
在实现这个功能的时候需要注意几点 registerProjectTargets
必须是个同步函数,所以不能使用任何异步 API,也没法用之前提到的方法来引用 ESM。另外,nx 出于性能考量,会缓存 registerProjectTargets
的结果,所以在 Debug 的时候,一定要记得设置环境变量 NX_CACHE_PROJECT_GRAPH=false
。
在处理文件路径的时候,建议尽量使用绝对路径,使用相对路径需要思考是相对于 workspace root 还是 cwd,用绝对路径可以减少这方面的心智负担。
虽然功能实现了,但是现在还不能立马使用,需要将插件源码转译成 CJS 代码。前面的文章提到过用 postinstall 来编译自定义 nx 插件,在实现了今天的功能后,编译自定义插件的命令必须不能用 nx 来执行,因为 nx 执行任何命令都会加载插件模块,如果我们的插件没有编译成 CJS,通过 nx 调用的编译插件命令也会失败。

Project Inference 功能非常强大,可以很大地减少我们维护项目 targets 的负担,在我看来,这算得上 nx 的旗舰功能了,后面我也会再演示一些其他使用 Project Reference 的典型场景。
发表回复