Aller au contenu

Contribuer à un Générateur

Créons un nouveau générateur pour contribuer à @aws/nx-plugin. Notre objectif sera de générer une nouvelle procédure pour une API tRPC.

D’abord, clonons le plugin :

Fenêtre de terminal
git clone git@github.com:awslabs/nx-plugin-for-aws.git

Ensuite, installez et compilez :

Fenêtre de terminal
cd nx-plugin-for-aws
pnpm i
pnpm nx run-many --target build --all

Créons le nouveau générateur dans packages/nx-plugin/src/trpc/procedure.

Nous fournissons un générateur pour créer de nouveaux générateurs afin que vous puissiez rapidement structurer le vôtre ! Vous pouvez exécuter ce générateur comme suit :

  1. Installez le Nx Console VSCode Plugin si ce n'est pas déjà fait
  2. Ouvrez la console Nx dans VSCode
  3. Cliquez sur Generate (UI) dans la section "Common Nx Commands"
  4. Recherchez @aws/nx-plugin - ts#nx-generator
  5. Remplissez les paramètres requis
    • pluginProject: @aws/nx-plugin
    • name: ts#trpc-api#procedure
    • directory: trpc/procedure
    • description: Adds a procedure to a tRPC API
  6. Cliquez sur Generate

Vous remarquerez que les fichiers suivants ont été générés pour vous :

  • Répertoirepackages/nx-plugin/src/trpc/procedure
    • schema.json Définit les entrées du générateur
    • schema.d.ts Une interface TypeScript correspondant au schéma
    • generator.ts Fonction exécutée par Nx comme générateur
    • generator.spec.ts Tests pour le générateur
  • Répertoiredocs/src/content/docs/guides/
    • trpc-procedure.mdx Documentation du générateur
  • packages/nx-plugin/generators.json Mis à jour pour inclure le générateur

Mettons à jour le schéma pour ajouter les propriétés nécessaires au générateur :

{
"$schema": "https://json-schema.org/schema",
"$id": "tRPCProcedure",
"title": "Adds a procedure to a tRPC API",
"type": "object",
"properties": {
"project": {
"type": "string",
"description": "tRPC API project",
"x-prompt": "Select the tRPC API project to add the procedure to",
"x-dropdown": "projects",
"x-priority": "important"
},
"procedure": {
"description": "The name of the new procedure",
"type": "string",
"x-prompt": "What would you like to call your new procedure?",
"x-priority": "important",
},
"type": {
"description": "The type of procedure to generate",
"type": "string",
"x-prompt": "What type of procedure would you like to generate?",
"x-priority": "important",
"default": "query",
"enum": ["query", "mutation"]
}
},
"required": ["project", "procedure"]
}

Vous remarquerez que le générateur a déjà été connecté dans packages/nx-plugin/generators.json :

...
"generators": {
...
"ts#trpc-api#procedure": {
"factory": "./src/trpc/procedure/generator",
"schema": "./src/trpc/procedure/schema.json",
"description": "Adds a procedure to a tRPC API"
}
},
...

Pour ajouter une procédure à une API tRPC, nous devons faire deux choses :

  1. Créer un fichier TypeScript pour la nouvelle procédure
  2. Ajouter la procédure au routeur

Pour créer le fichier TypeScript de la nouvelle procédure, nous utiliserons un utilitaire appelé generateFiles. Avec celui-ci, nous pouvons définir un modèle EJS que nous rendrons dans notre générateur avec des variables basées sur les options sélectionnées par l’utilisateur.

D’abord, définissons le modèle dans packages/nx-plugin/src/trpc/procedure/files/procedures/__procedureNameKebabCase__.ts.template :

files/procedures/__procedureNameKebabCase__.ts.template
import { publicProcedure } from '../init.js';
import { z } from 'zod';
export const <%- procedureNameCamelCase %> = publicProcedure
.input(z.object({
// TODO: define input
}))
.output(z.object({
// TODO: define output
}))
.<%- procedureType %>(async ({ input, ctx }) => {
// TODO: implement!
return {};
});

Dans le modèle, nous avons référencé trois variables :

  • procedureNameCamelCase
  • procedureNameKebabCase
  • procedureType

Nous devons donc nous assurer de les passer à generateFiles, ainsi que le répertoire de génération des fichiers, à savoir l’emplacement des fichiers sources (c’est-à-dire sourceRoot) pour le projet tRPC sélectionné par l’utilisateur, que nous pouvons extraire de la configuration du projet.

Mettons à jour le générateur pour cela :

procedure/generator.ts
import {
generateFiles,
joinPathFragments,
readProjectConfiguration,
Tree,
} from '@nx/devkit';
import { TrpcProcedureSchema } from './schema';
import { formatFilesInSubtree } from '../../utils/format';
import camelCase from 'lodash.camelcase';
import kebabCase from 'lodash.kebabcase';
export const trpcProcedureGenerator = async (
tree: Tree,
options: TrpcProcedureSchema,
) => {
const projectConfig = readProjectConfiguration(tree, options.project);
const procedureNameCamelCase = camelCase(options.procedure);
const procedureNameKebabCase = kebabCase(options.procedure);
generateFiles(
tree,
joinPathFragments(__dirname, 'files'),
projectConfig.sourceRoot,
{
procedureNameCamelCase,
procedureNameKebabCase,
procedureType: options.type,
},
);
await formatFilesInSubtree(tree);
};
export default trpcProcedureGenerator;

Ensuite, nous voulons que le générateur connecte la nouvelle procédure au routeur. Cela implique de lire et modifier le code source de l’utilisateur !

Nous utilisons la manipulation d’AST TypeScript pour mettre à jour les parties pertinentes du fichier source. Il existe des helpers appelés replace et destructuredImport pour faciliter cela.

procedure/generator.ts
import {
generateFiles,
joinPathFragments,
readProjectConfiguration,
Tree,
} from '@nx/devkit';
import { TrpcProcedureSchema } from './schema';
import { formatFilesInSubtree } from '../../utils/format';
import camelCase from 'lodash.camelcase';
import kebabCase from 'lodash.kebabcase';
import { destructuredImport, replace } from '../../utils/ast';
import { factory, ObjectLiteralExpression } from 'typescript';
export const trpcProcedureGenerator = async (
tree: Tree,
options: TrpcProcedureSchema,
) => {
const projectConfig = readProjectConfiguration(tree, options.project);
const procedureNameCamelCase = camelCase(options.procedure);
const procedureNameKebabCase = kebabCase(options.procedure);
generateFiles(
tree,
joinPathFragments(__dirname, 'files'),
projectConfig.sourceRoot,
{
procedureNameCamelCase,
procedureNameKebabCase,
procedureType: options.type,
},
);
const routerPath = joinPathFragments(projectConfig.sourceRoot, 'router.ts');
destructuredImport(
tree,
routerPath,
[procedureNameCamelCase],
`./procedures/${procedureNameKebabCase}.js`,
);
replace(
tree,
routerPath,
'CallExpression[expression.name="router"] > ObjectLiteralExpression',
(node) =>
factory.createObjectLiteralExpression([
...(node as ObjectLiteralExpression).properties,
factory.createShorthandPropertyAssignment(procedureNameCamelCase),
]),
);
await formatFilesInSubtree(tree);
};
export default trpcProcedureGenerator;

Maintenant que nous avons implémenté le générateur, compilons-le pour qu’il soit disponible pour le tester dans notre projet d’aventure donjon.

Fenêtre de terminal
pnpm nx run @aws/nx-plugin:compile

Pour tester le générateur, nous allons lier notre Nx Plugin for AWS local à un codebase existant.

Dans un répertoire séparé, créez un nouvel espace de travail de test :

Terminal window
npx create-nx-workspace@~21.0.3 trpc-generator-test --pm=pnpm --preset=@aws/nx-plugin --ci=skip

Ensuite, générons une API tRPC à laquelle ajouter la procédure :

  1. Installez le Nx Console VSCode Plugin si ce n'est pas déjà fait
  2. Ouvrez la console Nx dans VSCode
  3. Cliquez sur Generate (UI) dans la section "Common Nx Commands"
  4. Recherchez @aws/nx-plugin - ts#trpc-api
  5. Remplissez les paramètres requis
    • apiName: test-api
  6. Cliquez sur Generate

Dans votre codebase, lions notre @aws/nx-plugin local :

Terminal window
cd path/to/trpc-generator-test
pnpm link path/to/nx-plugin-for-aws/dist/packages/nx-plugin

Essayons le nouveau générateur :

  1. Installez le Nx Console VSCode Plugin si ce n'est pas déjà fait
  2. Ouvrez la console Nx dans VSCode
  3. Cliquez sur Generate (UI) dans la section "Common Nx Commands"
  4. Recherchez @aws/nx-plugin - ts#trpc-api#procedure
  5. Remplissez les paramètres requis
    • Cliquez sur Generate

    Si cela réussit, nous devrions avoir généré une nouvelle procédure et l’avoir ajoutée à notre routeur dans router.ts.

    Si vous êtes arrivé jusqu’ici et avez encore du temps pour expérimenter avec les générateurs Nx, voici quelques suggestions de fonctionnalités à ajouter au générateur de procédures :

    Essayez de modifier le générateur pour supporter les routeurs imbriqués en :

    • Acceptant une notation par points pour l’entrée procedure (ex: games.query)
    • Générant une procédure avec un nom basé sur l’inversion de la notation par points (ex: queryGames)
    • Ajoutant le routeur imbriqué approprié (ou le mettant à jour s’il existe déjà)

    Notre générateur devrait se prémunir contre les problèmes potentiels, comme un utilisateur sélectionnant un project qui n’est pas une API tRPC. Consultez le générateur api-connection pour un exemple.

    Écrivez des tests unitaires pour le générateur. Ceux-ci sont assez simples à implémenter et suivent généralement le flux suivant :

    1. Créer un arbre d’espace de travail vide avec createTreeUsingTsSolutionSetup()
    2. Ajouter les fichiers qui devraient déjà exister dans l’arbre (ex: project.json et src/router.ts pour un backend tRPC)
    3. Exécuter le générateur sous test
    4. Valider que les modifications attendues sont apportées à l’arbre

    Actuellement, nous avons un seul “smoke test” qui exécute tous les générateurs et vérifie que la compilation réussit. Celui-ci devrait être mis à jour pour inclure le nouveau générateur.