关于 nx-workspace-patterns
nx-workspace-patterns 面向搭建、维护或迁移 Nx 单体仓库的开发者,涵盖工作区结构、项目边界、依赖管理、CI 受影响任务和远程缓存,并提醒按已安装的 Nx 版本验证示例。
主要功能
- 01Nx 工作区结构与库模式
- 02项目边界和依赖管理建议
- 03用于 CI 工作流的受影响命令
- 04远程缓存配置指导
- 05迁移与生成器实践
适用场景
- 01创建新的 Nx 工作区
- 02定义项目之间的边界
- 03用受影响测试减少 CI 工作
- 04规划 Nx 迁移
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ sickn33/ agentic-awesome-skills --skill nx-workspace-patterns安装后在 Claude Code 中输入/nx-workspace-patterns确认已加载。
详细介绍
Nx 工作区模式
Nx 单体仓库管理的生产实践模式。
以下情况不要使用此技能
- 任务与 Nx 工作区模式无关
- 你需要使用此范围之外的其他领域知识或工具
说明
- 明确目标、限制条件和所需输入。
- 应用相关最佳实践并验证结果。
- 提供可执行的步骤和验证方法。
以下情况请使用此技能
- 设置新的 Nx 工作区
- 配置项目边界
- 使用 affected 命令优化 CI
- 实现远程缓存
- 管理项目间的依赖关系
- 迁移到 Nx
核心概念
1. Nx 架构
workspace/
├── apps/ # Deployable applications
│ ├── web/
│ └── api/
├── libs/ # Shared libraries
│ ├── shared/
│ │ ├── ui/
│ │ └── utils/
│ └── feature/
│ ├── auth/
│ └── dashboard/
├── tools/ # Custom executors/generators
├── nx.json # Nx configuration
└── workspace.json # Project configuration
2. 库类型
| 类型 | 用途 | 示例 |
|---|---|---|
| feature | 智能组件、业务逻辑 | feature-auth |
| ui | 展示型组件 | ui-buttons |
| data-access | API 调用、状态管理 | data-access-users |
| util | 纯函数、辅助工具 | util-formatting |
| shell | 应用引导 | shell-web |
模板
这些示例混用了旧版和现代 Nx 配置。请固定工作区的 Nx/插件版本,并根据已安装的 schema 进行验证;不要不加判断地跨版本复制 npmScope/tasksRunnerOptions 或生成器选项。使用环境变量/密钥存储来管理 Nx Cloud 令牌,切勿在已提交的文件中写入字面值 accessToken 字段。仅缓存具有完整输入/输出声明的确定性任务;针对可变服务的 E2E 测试可能需要禁用缓存执行。受影响项目的选择依赖完整的依赖关系图和正确的 base/head:对于策略、共享或未知变更,仍需运行所有必需的测试套件。迁移命令会修改清单文件/代码,因此需要使用干净、可恢复的主题分支,并进行审查。
模板 1:nx.json 配置
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"npmScope": "myorg",
"affected": {
"defaultBase": "main"
},
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": [
"build",
"lint",
"test",
"e2e",
"build-storybook"
],
"parallel": 3
}
}
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"],
"cache": true
},
"test": {
"inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"],
"cache": true
},
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"cache": true
},
"e2e": {
"inputs": ["default", "^production"],
"cache": true
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/jest.config.[jt]s",
"!{projectRoot}/.eslintrc.json"
],
"sharedGlobals": [
"{workspaceRoot}/babel.config.json",
"{workspaceRoot}/tsconfig.base.json"
]
},
"generators": {
"@nx/react": {
"application": {
"style": "css",
"linter": "eslint",
"bundler": "webpack"
},
"library": {
"style": "css",
"linter": "eslint"
},
"component": {
"style": "css"
}
}
}
}
模板 2:项目配置
// apps/web/project.json
{
"name": "web",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/web/src",
"projectType": "application",
"tags": ["type:app", "scope:web"],
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"compiler": "babel",
"outputPath": "dist/apps/web",
"index": "apps/web/src/index.html",
"main": "apps/web/src/main.tsx",
"tsConfig": "apps/web/tsconfig.app.json",
"assets": ["apps/web/src/assets"],
"styles": ["apps/web/src/styles.css"]
},
"configurations": {
"development": {
"extractLicenses": false,
"optimization": false,
"sourceMap": true
},
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractLicenses": true
}
}
},
"serve": {
"executor": "@nx/webpack:dev-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "web:build"
},
"configurations": {
"development": {
"buildTarget": "web:build:development"
},
"production": {
"buildTarget": "web:build:production"
}
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/web/jest.config.ts",
"passWithNoTests": true
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/web/**/*.{ts,tsx,js,jsx}"]
}
}
}
}
模板 3:模块边界规则
// .eslintrc.json
{
"root": true,
"ignorePatterns": ["**/*"],
"plugins": ["@nx"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "type:app",
"onlyDependOnLibsWithTags": [
"type:feature",
"type:ui",
"type:data-access",
"type:util"
]
},
{
"sourceTag": "type:feature",
"onlyDependOnLibsWithTags": [
"type:ui",
"type:data-access",
"type:util"
]
},
{
"sourceTag": "type:ui",
"onlyDependOnLibsWithTags": ["type:ui", "type:util"]
},
{
"sourceTag": "type:data-access",
"onlyDependOnLibsWithTags": ["type:data-access", "type:util"]
},
{
"sourceTag": "type:util",
"onlyDependOnLibsWithTags": ["type:util"]
},
{
"sourceTag": "scope:web",
"onlyDependOnLibsWithTags": ["scope:web", "scope:shared"]
},
{
"sourceTag": "scope:api",
"onlyDependOnLibsWithTags": ["scope:api", "scope:shared"]
},
{
"sourceTag": "scope:shared",
"onlyDependOnLibsWithTags": ["scope:shared"]
}
]
}
]
}
}
]
}
模板 4:自定义生成器
// tools/generators/feature-lib/index.ts
import {
Tree,
formatFiles,
generateFiles,
joinPathFragments,
names,
readProjectConfiguration,
} from '@nx/devkit';
import { libraryGenerator } from '@nx/react';
interface FeatureLibraryGeneratorSchema {
name: string;
scope: string;
directory?: string;
}
export default async function featureLibraryGenerator(
tree: Tree,
options: FeatureLibraryGeneratorSchema
) {
const { name, scope, directory } = options;
const projectDirectory = directory
? `${directory}/${name}`
: `libs/${scope}/feature-${name}`;
// Generate base library
await libraryGenerator(tree, {
name: `feature-${name}`,
directory: projectDirectory,
tags: `type:feature,scope:${scope}`,
style: 'css',
skipTsConfig: false,
skipFormat: true,
unitTestRunner: 'jest',
linter: 'eslint',
});
// Add custom files
const projectConfig = readProjectConfiguration(tree, `${scope}-feature-${name}`);
const projectNames = names(name);
generateFiles(
tree,
joinPathFragments(__dirname, 'files'),
projectConfig.sourceRoot,
{
...projectNames,
scope,
tmpl: '',
}
);
await formatFiles(tree);
}
模板 5:使用 affected 的 CI 配置
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Derive SHAs for affected commands
uses: nrwl/nx-set-shas@v4
- name: Run affected lint
run: npx nx affected -t lint --parallel=3
- name: Run affected test
run: npx nx affected -t test --parallel=3 --configuration=ci
- name: Run affected build
run: npx nx affected -t build --parallel=3
- name: Run affected e2e
run: npx nx affected -t e2e --parallel=1
模板 6:远程缓存设置
// nx.json with Nx Cloud
{
"tasksRunnerOptions": {
"default": {
"runner": "nx-cloud",
"options": {
"cacheableOperations": ["build", "lint", "test", "e2e"],
"accessToken": "your-nx-cloud-token",
"parallel": 3,
"cacheDirectory": ".nx/cache"
}
}
},
"nxCloudAccessToken": "your-nx-cloud-token"
}
// Self-hosted cache with S3
{
"tasksRunnerOptions": {
"default": {
"runner": "@nx-aws-cache/nx-aws-cache",
"options": {
"cacheableOperations": ["build", "lint", "test"],
"awsRegion": "us-east-1",
"awsBucket": "my-nx-cache-bucket",
"awsProfile": "default"
}
}
}
}
常用命令
# Generate new library
nx g @nx/react:lib feature-auth --directory=libs/web --tags=type:feature,scope:web
# Run affected tests
nx affected -t test --base=main
# View dependency graph
nx graph
# Run specific project
nx build web --configuration=production
# Reset cache
nx reset
# Run migrations
nx migrate latest
nx migrate --run-migrations
最佳实践
应做事项
- 统一使用标签 - 通过模块边界规则强制执行
- 尽早启用缓存 - 显著节省 CI 开销
- 让库职责聚焦 - 遵循单一职责原则
- 使用生成器 - 确保一致性
- 记录边界规则 - 帮助新开发者
不应做事项
- 不要创建循环依赖 - 依赖关系图应为无环图
- 不要跳过 affected - 只测试发生变更的部分
- 不要忽视边界规则 - 技术债会不断累积
- 不要过度细分 - 平衡库的数量
资源
局限性
- 仅当任务明确符合上述范围时,才使用此技能。
- 请勿将输出视为替代针对具体环境的验证、测试或专家审查。
- 如果缺少所需输入、权限、安全边界或成功标准,请暂停并要求澄清。
评论
支持 Markdown