Skip to content

Nx Generator Generator

Nx GeneratorをTypeScriptプロジェクトに追加し、コンポーネントのスキャフォールディングや特定のプロジェクト構造の強制など、反復的なタスクの自動化を支援します。

Generatorは2つの方法で生成できます:

Terminal window
pnpm nx g @aws/nx-plugin:ts#nx-generator
変更されるファイルを確認するためにドライランを実行することもできます
Terminal window
pnpm nx g @aws/nx-plugin:ts#nx-generator --dry-run
パラメータデフォルト説明
project 必須string-ジェネレーターを追加するTypeScriptプロジェクト。ts#nx-pluginジェネレーターを使用して作成することを推奨します。
name 必須string-ジェネレーター名
description string-ジェネレーターの説明
directory string-ジェネレーターを追加するプラグインプロジェクトのソースフォルダー内のディレクトリ (デフォルト: <name>)
preferInstallDependencies booleantrueジェネレーター実行後に依存関係のインストールを優先するかどうか。複数のジェネレーターをバッチ処理する際にインストールを延期する場合はfalseに設定します(後続のジェネレーターがNxプロジェクトグラフを計算できるよう、必要に応じてインストールは実行されます)。最後に一度だけインストールします。

Generatorは、指定されたproject内に以下のプロジェクトファイルを作成します:

  • Directorysrc/<name>/
    • schema.json Generatorへの入力のスキーマ
    • schema.d.ts スキーマのTypeScript型
    • generator.ts スタブGenerator実装
    • generator.spec.ts Generatorのテスト
    • README.md Generatorのドキュメント
  • generators.json Generatorを定義するNx設定
  • package.json “generators”エントリを追加するために作成または更新
  • tsconfig.json CommonJSを使用するように更新

プロジェクトの変更

このGeneratorは、選択されたprojectをCommonJSを使用するように更新します。これは、Nx GeneratorがESMサポートについては現在CommonJSのみをサポートしているためです(ESMサポートについてはこのGitHub issueを参照)。

ts#nx-generator Generatorを実行する際にローカルのnx-pluginプロジェクトを選択し、名前とオプションのディレクトリおよび説明を指定します。

schema.jsonファイルは、Generatorが受け入れるオプションを定義します。これはJSON Schema形式にNx固有の拡張を加えたものに従います。

schema.jsonファイルは以下の基本構造を持ちます:

{
"$schema": "https://json-schema.org/schema",
"$id": "YourGeneratorName",
"title": "Your Generator Title",
"description": "Description of what your generator does",
"type": "object",
"properties": {
// Your generator options go here
},
"required": ["requiredOption1", "requiredOption2"]
}

以下は、いくつかの基本的なオプションを持つシンプルな例です:

{
"$schema": "https://json-schema.org/schema",
"$id": "ComponentGenerator",
"title": "Create a Component",
"description": "Creates a new React component",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Component name",
"x-priority": "important"
},
"directory": {
"type": "string",
"description": "Directory where the component will be created",
"default": "src/components"
},
"withTests": {
"type": "boolean",
"description": "Whether to generate test files",
"default": true
}
},
"required": ["name"]
}

インタラクティブプロンプト(CLI)

Section titled “インタラクティブプロンプト(CLI)”

x-promptプロパティを追加することで、CLI経由でGeneratorを実行する際に表示されるプロンプトをカスタマイズできます:

"name": {
"type": "string",
"description": "Component name",
"x-prompt": "What is the name of your component?"
}

ブール値オプションの場合、yes/noプロンプトを使用できます:

"withTests": {
"type": "boolean",
"description": "Whether to generate test files",
"x-prompt": "Would you like to generate test files?"
}

固定された選択肢のセットを持つオプションの場合、enumを使用して、ユーザーがオプションの1つから選択できるようにします。

"style": {
"type": "string",
"description": "The styling approach to use",
"enum": ["css", "scss", "styled-components", "none"],
"default": "css"
}

プロジェクト選択ドロップダウン

Section titled “プロジェクト選択ドロップダウン”

一般的なパターンは、ワークスペース内の既存のプロジェクトからユーザーに選択させることです:

"project": {
"type": "string",
"description": "The project to add the component to",
"x-prompt": "Which project would you like to add the component to?",
"x-dropdown": "projects"
}

x-dropdown: "projects"プロパティは、Nxにワークスペース内のすべてのプロジェクトでドロップダウンを埋めるように指示します。

コマンドラインからGeneratorを実行する際に、位置引数として渡されるようにオプションを設定できます:

"name": {
"type": "string",
"description": "Component name",
"x-priority": "important",
"$default": {
"$source": "argv",
"index": 0
}
}

これにより、ユーザーはnx g your-generator --name=my-componentの代わりにnx g your-generator my-componentのようにGeneratorを実行できます。

x-priorityプロパティを使用して、どのオプションが最も重要かを示します:

"name": {
"type": "string",
"description": "Component name",
"x-priority": "important"
}

オプションは"important"または"internal"の優先度を持つことができます。これにより、Nx VSCode拡張機能とNx CLIでプロパティを順序付けするのに役立ちます。

オプションにデフォルト値を提供できます:

"directory": {
"type": "string",
"description": "Directory where the component will be created",
"default": "src/components"
}

スキーマの詳細については、Nx Generator Optionsドキュメントを参照してください。

schema.jsonと共に、Generatorはschema.d.tsファイルを作成し、Generatorオプションに対するTypeScript型を提供します:

export interface YourGeneratorSchema {
name: string;
directory?: string;
withTests?: boolean;
}

このインターフェースは、型安全性とコード補完を提供するために、Generator実装で使用されます:

import { YourGeneratorSchema } from './schema';
export default async function (tree: Tree, options: YourGeneratorSchema) {
// TypeScript knows the types of all your options
const { name, directory = 'src/components', withTests = true } = options;
// ...
}

上記のように新しいGeneratorを作成した後、generator.tsに実装を記述できます。

Generatorは仮想ファイルシステム(Tree)を変更する関数で、ファイルを読み書きして望ましい変更を行います。Treeからの変更は、Generatorの実行が完了した後にのみディスクに書き込まれます(“dry-run”モードで実行されている場合を除く)。空のGeneratorは以下のようになります:

export const myGenerator = async (tree: Tree, options: MyGeneratorSchema) => {
// Use the tree to apply changes
};
export default myGenerator;

Generatorで実行したい一般的な操作をいくつか紹介します:

// Read a file
const content = tree.read('path/to/file.ts', 'utf-8');
// Write a file
tree.write('path/to/new-file.ts', 'export const hello = "world";');
// Check if a file exists
if (tree.exists('path/to/file.ts')) {
// Do something
}

テンプレートからのファイル生成

Section titled “テンプレートからのファイル生成”

@nx/devkitgenerateFilesユーティリティを使用してファイルを生成できます。これにより、EJS構文でテンプレートを定義し、変数を置換できます。

import { generateFiles, joinPathFragments } from '@nx/devkit';
// Generate files from templates
generateFiles(
tree,
joinPathFragments(import.meta.dirname, 'files'), // Template directory
'path/to/output', // Output directory
{
// Variables to replace in templates
name: options.name,
nameCamelCase: camelCase(options.name),
nameKebabCase: kebabCase(options.name),
// Add more variables as needed
},
);

GritQLを使用して、Generator内でソースコードを宣言的に検索および変換できます。GritQLは、TypeScript、JavaScript、Python、HCL(Terraform)などの複数の言語をサポートしているため、スタック全体で同じパターン構文を使用できます。

Nx Plugin for AWSは2つのヘルパーを公開しています:

  • applyGritQL(tree, filePath, pattern) — GritQL書き換えパターンをファイルに適用し、変更が行われたかどうかを示すPromise<boolean>を返します
  • matchGritQL(tree, filePath, pattern) — GritQLパターンがファイル内のどこかに一致するかどうかをチェックし、Promise<boolean>を返します
import { applyGritQL, matchGritQL } from '@aws/nx-plugin/sdk/utils/ast';
// Replace a function call
await applyGritQL(
tree,
'src/app.ts',
'`console.log($msg)` => `logger.info($msg)`',
);
// Add an element to an array only if not already present
await applyGritQL(
tree,
'src/plugins.ts',
'`plugins: [$items]` => `plugins: [$items, myPlugin()]` where { $items <: not contains `myPlugin` }',
);
// Check if a pattern exists before making changes
if (!(await matchGritQL(tree, filePath, '`import { Auth } from "./auth"`'))) {
// Add the import
}

GritQLパターンは非TypeScriptファイルでも機能します。他の言語をターゲットにするには、パターンの前にlanguage <name>を付けます:

// Python: replace print statements with logging calls
await applyGritQL(
tree,
'src/handler.py',
'language python\n`print($msg)` => `logger.info($msg)`',
);

GritQLパターンは、バッククォートで区切られたコードスニペットと、ワイルドカードとしての$metavariablesを使用します。書き換えには=>を、条件にはwhere句を使用します。

import { addDependenciesToPackageJson } from '@nx/devkit';
// Add dependencies to package.json
addDependenciesToPackageJson(
tree,
{
'new-dependency': '^1.0.0',
},
{
'new-dev-dependency': '^2.0.0',
},
);

生成されたファイルのフォーマット

Section titled “生成されたファイルのフォーマット”
import { formatFilesInSubtree } from '@aws/nx-plugin/sdk/utils/format';
// Format all files that were modified
await formatFilesInSubtree(tree, 'optional/path/to/format');
import { readJson, updateJson } from '@nx/devkit';
// Read a JSON file
const packageJson = readJson(tree, 'package.json');
// Update a JSON file
updateJson(tree, 'tsconfig.json', (json) => {
json.compilerOptions = {
...json.compilerOptions,
strict: true,
};
return json;
});

Nx Plugin for AWSからGeneratorをインポートし、必要に応じて拡張または構成できます。例えば、TypeScriptプロジェクトの上に構築されるGeneratorを作成したい場合があります:

import { tsProjectGenerator } from '@aws/nx-plugin/sdk/ts';
export const myGenerator = async (tree: Tree, schema: MyGeneratorSchema) => {
const callback = await tsProjectGenerator(tree, { ... });
// Extend the TypeScript project generator here
// Return the callback to ensure dependencies are installed.
// You can wrap the callback if you wish to perform additional operations in the generator callback.
return callback;
};

上記と同様の方法で、TypeScriptクライアントとフックに使用するGeneratorを使用および拡張できます:

import { openApiTsClientGenerator } from '@aws/nx-plugin/sdk/open-api';
export const myGenerator = async (tree: Tree, schema: MyGeneratorSchema) => {
await openApiTsClientGenerator(tree, { ... });
// Add additional files here
};

また、OpenAPI仕様内の操作を反復処理するために使用できるデータ構造を構築できるメソッドも公開しており、独自のコード生成を実装できます。例:

import { buildOpenApiCodeGenerationData } from '@aws/nx-plugin/sdk/open-api.js';
export const myGenerator = async (tree: Tree, schema: MyGeneratorSchema) => {
const data = await buildOpenApiCodeGenerationData(tree, 'path/to/spec.json');
generateFiles(
tree,
joinPathFragments(import.meta.dirname, 'files'), // Template directory
'path/to/output', // Output directory
data,
);
};

これにより、次のようなテンプレートを記述できます:

files/my-operations.ts.template
export const myOperationNames = [
<%_ allOperations.forEach((op) => { _%>
'<%- op.name %>',
<%_ }); _%>
];

より複雑なテンプレートの例については、GitHubのコードベースを参照してください。

Generatorは2つの方法で実行できます:

Terminal window
pnpm nx g @my-project/nx-plugin:my-generator
変更されるファイルを確認するためにドライランを実行することもできます
Terminal window
pnpm nx g @my-project/nx-plugin:my-generator --dry-run

Generatorの単体テストは簡単に実装できます。以下は典型的なパターンです:

import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { yourGenerator } from './generator.js';
describe('your generator', () => {
let tree;
beforeEach(() => {
// Create an empty workspace tree
tree = createTreeWithEmptyWorkspace();
// Add any files that should already exist in the tree
tree.write(
'project.json',
JSON.stringify({
name: 'test-project',
sourceRoot: 'src',
}),
);
tree.write('src/existing-file.ts', 'export const existing = true;');
});
it('should generate expected files', async () => {
// Run the generator
await yourGenerator(tree, {
name: 'test',
// Add other required options
});
// Check that files were created
expect(tree.exists('src/test/file.ts')).toBeTruthy();
// Check file content
const content = tree.read('src/test/file.ts', 'utf-8');
expect(content).toContain('export const test');
// You can also use snapshots
expect(tree.read('src/test/file.ts', 'utf-8')).toMatchSnapshot();
});
it('should update existing files', async () => {
// Run the generator
await yourGenerator(tree, {
name: 'test',
// Add other required options
});
// Check that existing files were updated
const content = tree.read('src/existing-file.ts', 'utf-8');
expect(content).toContain('import { test } from');
});
it('should handle errors', async () => {
// Expect the generator to throw an error in certain conditions
await expect(
yourGenerator(tree, {
name: 'invalid',
// Add options that should cause an error
}),
).rejects.toThrow('Expected error message');
});
});

Generatorをテストする際の重要なポイント:

  • createTreeWithEmptyWorkspace()を使用して仮想ファイルシステムを作成
  • Generatorを実行する前に前提条件となるファイルをセットアップ
  • 新しいファイルの作成と既存のファイルの更新の両方をテスト
  • 複雑なファイル内容にはスナップショットを使用
  • Generatorが適切に失敗することを確認するためにエラー条件をテスト

ts#nx-generatorを使用して、@aws/nx-plugin内にGeneratorをスキャフォールドすることもできます。

このGeneratorが私たちのリポジトリで実行されると、以下のファイルが生成されます:

  • Directorypackages/nx-plugin/src/<name>/
    • schema.json Generatorへの入力のスキーマ
    • schema.d.ts スキーマのTypeScript型
    • generator.ts Generator実装
    • generator.spec.ts Generatorのテスト
  • Directorydocs/src/content/docs/guides/
    • <name>.mdx Generatorのドキュメントページ
  • packages/nx-plugin/generators.json Generatorを含めるように更新
  • packages/nx-plugin/sdk/<prefix>.ts SDKからGeneratorを公開するように更新(ts#およびpy# Generatorの場合)

その後、Generatorの実装を開始できます。