Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Scaffolding mit Nx Local Generators

Scaffolding mit Nx Local Generators

Manche Entwickler müssen in Ihren Angular-Workspaces wiederkehrende Aufgaben erledigen: Etwa die Neuanlage gleichartiger Projekte, Bibliotheken sowie Komponenten oder aber die Sicherstellung einer bestimmten Ordnerstruktur. Während im Angular-CLI-Umfeld hierfür typischerweise Schematics zum Einsatz kommen, führt Nrwl bei Nx-Workspaces ein eigenes Konzept ins Feld: Workspace Generators.

Christian Liebel von Thinktecture zeigt Ihnen die grundlegenden Konzepte dieser Generatoren, wie sie sich von Schematics abgrenzen und welche Techniken man für die Implementierung beherrschen muss.

Christian Liebel

October 19, 2023
Tweet

More Decks by Christian Liebel

Other Decks in Programming

Transcript

  1. Hello, it’s me. Scaffolding mit Nx Local Generators Christian Liebel

    X: @christianliebel Email: christian.liebel @thinktecture.com Angular & PWA Slides: thinktecture.com /christian-liebel
  2. Things NOT to expect - Pretty code - A super

    polished workshop— this is its debut :) Things To Expect - Basic concepts - Nx local generators vs. Angular CLI schematics - Patterns & practices - 12 Hands-on exercises Scaffolding mit Nx Local Generators
  3. What is Nx? – “Build system with first class monorepo

    support and powerful integrations” (nx.dev) – Similar to Angular CLI, but far more advanced – Nx workspaces can host frontends and backends based on various frameworks – Used by Storybook, Microsoft Fluent UI, Babylon.js, and many of our customers – Nrwl team was founded by former Angular team members Nx Introduction Scaffolding mit Nx Local Generators
  4. Nx 16+ Scaffolding mit Nx Local Generators New: Nx Local

    Generators https://nx.dev/extending-nx/recipes/local-generators
  5. Generators – Similar to Angular Schematics – Generators provide a

    way to automate many tasks you regularly perform as part of your development workflow. – Whether it is scaffolding out components, features, ensuring libraries are generated and structured in a certain way, or updating your configuration files, generators help you standardize these tasks in a consistent, and predictable manner. (https://nx.dev/core-features/plugin-features/use-code-generators) Nx Introduction Scaffolding mit Nx Local Generators
  6. Generator Types Type Plugin Generators Available when an Nx plugin

    has been installed in your workspace nx generate [plugin]:[generator-name] [options] Local Generators Generated for your own workspace. Allows codifying processes unique to your own organization or project. Update Generators Invoked by Nx plugins when updating Nx to keep your config files in sync with the latest versions of third-party tools Nx Introduction Scaffolding mit Nx Local Generators https://nx.dev/core-features/plugin-features/use-code-generators
  7. Devkit Angular Devkit Schematics Builders Nx Introduction Scaffolding mit Nx

    Local Generators Nrwl Devkit Generators Executors
  8. Generators vs. Schematics Generators – Based on Promises – Directly

    perform actions – Easier to debug and test – Convenient helper methods – Supports Nx generators & Angular CLI schematics Schematics – Based on RxJS – Return rules (executed later) – Hard to debug and test – Fewer helper methods – Only supports Angular CLI schematics Scaffolding mit Nx Local Generators Nx Introduction https://nx.dev/guides/nx-devkit-angular-devkit
  9. npm install @nx/plugin@latest npx nx g @nx/plugin:plugin my-plugin npx nx

    g @nx/plugin:generator my-generator --project=my-plugin Scaffolding mit Nx Local Generators Generating a generator LAB #1
  10. import { addProjectConfiguration, formatFiles, generateFiles, Tree } from '@nx/devkit'; import

    * as path from 'path'; import { MyGeneratorGeneratorSchema } from './schema'; export async function myGeneratorGenerator(tree: Tree, options: MyGeneratorGeneratorSchema) { const projectRoot = `libs/${options.name}`; addProjectConfiguration(tree, options.name, { root: projectRoot, projectType: 'library', sourceRoot: `${projectRoot}/src`, targets: {} }); generateFiles(tree, path.join(__dirname, 'files'), projectRoot, options); await formatFiles(tree); } export default myGeneratorGenerator; Generator Concepts Scaffolding mit Nx Local Generators
  11. Tree – The tree represents a virtual file system –

    It contains all files that already existed in the real file system – All generator actions are executed against the tree, but are only added to the staging area – If all actions complete successfully, the staging area is written to the filesystem – In case of an error, it is simply discarded Generator Concepts Scaffolding mit Nx Local Generators
  12. JSON Schema – Defines the generator inputs – Allows specifying

    required/optional properties, defaults (e.g., read from argv), allowed values (enums), … – Used by the CLI to map the user’s inputs to a schema object – The CLI may show an interactive prompt (x-prompt, e.g. for style sheet flavor selection) – You can also provide a TypeScript interface for the resulting object so your generator can be called from another one Generator Concepts Scaffolding mit Nx Local Generators
  13. Demo Path 1. Create a common Angular library 2. Create

    a new login application a) Invoke application generator b) Generate common files c) Import common library 3. Create a new login step a) Invoke Angular schematic b) Use Typescript AST to update other file Scaffolding mit Nx Local Generators Generator Techniques
  14. Typed schema { "$schema": "http://json-schema.org/schema", "$id": "MyGenerator", "title": "", "type":

    "object", "properties": { "name": { "type": "string", "description": "", "$default": { "$source": "argv", "index": 0 }, "x-prompt": "What name would you like to use?" } }, "required": ["name"] } export interface MyGeneratorGeneratorSchema { name: string; } Scaffolding mit Nx Local Generators Generator Techniques
  15. Invoking other generators // New imports import { applicationGenerator }

    from '@nx/angular/generators'; // Update code export async function (tree: Tree, options: MyGeneratorGeneratorSchema) { await applicationGenerator(tree, { name: options.name, routing: true, prefix: options.name, style: 'scss', }); } Generator Techniques Scaffolding mit Nx Local Generators LAB #3
  16. Generate files generateFiles(tree, sourceDir, destinationDir, {}); Does not work for

    empty directories! Tip: Create .gitkeep files for that, and remove them with another generator later! Generator Techniques Scaffolding mit Nx Local Generators
  17. Utility functions The @nx/devkit package provides many utility functions that

    can be used in schematics to help with modifying files, reading and updating configuration files, and working with an Abstract Syntax Tree (AST). applyChangesToString() joinPathFragments() readProjectConfiguration() formatFiles() installPackagesTask() readJson() Generator Techniques Scaffolding mit Nx Local Generators
  18. Generate files 1. Create my-generator/files/app.config.ts: export const loginSteps = [];

    2. Adjust your local generator as follows: // New imports import {generateFiles, joinPathFragments, readProjectConfiguration, Tree} from '@nx/devkit'; // Add code const sourceDir = joinPathFragments(__dirname, 'files'); const projectDir = readProjectConfiguration(tree, options.name).root; const destinationDir = joinPathFragments(projectDir, 'src', 'app'); generateFiles(tree, sourceDir, destinationDir, {}); Generator Techniques Scaffolding mit Nx Local Generators LAB #5
  19. Generate files 3. Test your generator: npx nx g my-generator

    app --dry-run Generator Techniques Scaffolding mit Nx Local Generators LAB #5
  20. Generate files 1. Rename files/app.config.ts to files/app.config.ts__tpl__ 2. Adjust your

    local generator as follows: generateFiles(tree, sourceDir, destinationDir, { tpl: '' }); 3. Test your generator: npx nx g my-generator app --dry-run Generator Techniques Scaffolding mit Nx Local Generators LAB #6
  21. Generate files – Utils import { classify, dasherize } from

    '@nx/devkit/src/utils/string-utils'; Generator Techniques Scaffolding mit Nx Local Generators Type Input myCoolApp classify() MyCoolApp dasherize() my-cool-app
  22. Generate files – Substitutions generateFiles(tree, sourceDir, destinationDir, { tpl: '',

    projectFileName: dasherize(schema.name), projectClassName: classify(schema.name), }); Generator Techniques Scaffolding mit Nx Local Generators
  23. High-level utility functions (@nx/angular) import { insertNgModuleImport } from '@nx/angular/src/generators/utils';

    insertNgModuleImport(tree, modulePath, moduleToAdd); Generator Techniques Scaffolding mit Nx Local Generators
  24. Writing a custom utility function Introduce a new file generators/utils/insert-import.ts:

    import { Tree } from '@nx/devkit'; import { createSourceFile, ScriptTarget } from 'typescript'; import { insertImport as nxInsertImport } from '@nx/js'; export function insertImport(tree: Tree, filePath: string, symbolName: string, fileName: string) { const moduleSrc = tree.read(filePath, 'utf-8'); const sourceFile = createSourceFile(filePath, moduleSrc, ScriptTarget.Latest, true); nxInsertImport(tree, sourceFile, filePath, symbolName, fileName); } Generator Techniques Scaffolding mit Nx Local Generators LAB #7
  25. High-level utility functions Adjust your local generator as follows: //

    New imports import {insertNgModuleImport} from '@nx/angular/src/generators/utils'; import {insertImport} from '../utils/insert-import'; // Add at the end const modulePath = joinPathFragments(projectDir, 'src', 'app', 'app.module.ts'); insertImport(tree, modulePath, 'MyLibraryModule', '@my-workspace/my- library'); insertNgModuleImport(tree, modulePath, 'MyLibraryModule'); Generator Techniques Scaffolding mit Nx Local Generators LAB #8
  26. High-level utility functions Run your generator: npx nx g my-generator

    app Generator Techniques Scaffolding mit Nx Local Generators LAB #8
  27. Convert to Angular Schematic import { convertNxGenerator } from '@nx/devkit';

    export const mygeneratorSchematic = convertNxGenerator(myGenerator); Generator Techniques Scaffolding mit Nx Local Generators
  28. Invoking Angular schematics 1. Generate a new generator: npx nx

    g @nx/plugin:generator my-step --project my-plugin 2. Adjust the JSON schema to include a project property: "project": { "type": "string", "description": "Target project" } 3. Add the following interface to the schema.d.ts file: interface Schema { name: string; project: string; } Generator Techniques Scaffolding mit Nx Local Generators LAB #9
  29. Invoking Angular schematics 4. Adjust the generator to call the

    Angular module schematic: // New imports import { wrapAngularDevkitSchematic } from 'nx/src/adapter/ngcli-adapter'; import { dasherize } from '@nx/devkit/src/utils/string-utils'; // New code export default async function (tree: Tree, options: MyStepGeneratorSchema) { const moduleSchematic = wrapAngularDevkitSchematic('@schematics/angular', 'module'); await moduleSchematic(tree, { name: dasherize(options.name), project: options.project }); } Generator Techniques Scaffolding mit Nx Local Generators LAB #9
  30. Invoking Angular schematics 5. Test your generator: npx nx g

    my-step step --project app --dry-run Generator Techniques Scaffolding mit Nx Local Generators LAB #9
  31. String replacements fileContents.replace( /(title.*?;)/g, `constructor(public readonly loginService: LoginService) { this.loginService.initialize();

    }` ); Note: This is very simple, but may lead to unintended side effects. Generator Techniques Scaffolding mit Nx Local Generators
  32. TypeScript AST TypeScript package allows parsing TS source files to

    an abstract syntax tree (AST) and traverse it, which is a safer approach compared to string replacements createSourceFile() isIdentifier() isVariableStatement() Generator Techniques Scaffolding mit Nx Local Generators
  33. TypeScript AST // New imports import {ChangeType, joinPathFragments, readProjectConfiguration, Tree}

    from '@nx/devkit'; Import {createSourceFile, ScriptTarget} from 'typescript'; // Add code const projectDir = readProjectConfiguration(tree, options.project).root; const configFileName = joinPathFragments(projectDir, 'src', 'app', 'app.config.ts'); const fileContents = tree.read(configFileName).toString('utf-8'); const sourceFile = createSourceFile(configFileName, fileContents, ScriptTarget.Latest); Generator Techniques Scaffolding mit Nx Local Generators LAB #10
  34. TypeScript AST // New imports import {createSourceFile, Identifier, isIdentifier, isVariableStatement,

    ScriptTarget} from 'typescript'; // Add code const stepsDeclaration = sourceFile.statements .filter(isVariableStatement) .map(s => s.declarationList.declarations[0]) .filter(d => isIdentifier(d.name)) .find(d => (d.name as Identifier).escapedText === 'loginSteps'); Generator Techniques Scaffolding mit Nx Local Generators LAB #10
  35. TypeScript AST // New imports import {applyChangesToString, ChangeType, joinPathFragments, readProjectConfiguration,

    Tree} from '@nx/devkit'; // Add code const stepToAdd = `"${classify(options.name)}",`; const newContents = applyChangesToString(fileContents, [{ type: ChangeType.Insert, index: stepsDeclaration.end - 1, text: stepToAdd }]); tree.write(configFileName, newContents); Generator Techniques Scaffolding mit Nx Local Generators LAB #10
  36. TypeScript AST Run your generator: npx nx g my-step step

    --project app Generator Techniques Scaffolding mit Nx Local Generators LAB #10
  37. import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; describe('app', () => {

    let appTree: Tree; beforeEach(() => { appTree = createTreeWithEmptyWorkspace(); }); it('…', () => {}); }); Testing Scaffolding mit Nx Local Generators LAB #11
  38. Visual Studio Code Command Palette Debug: Create JavaScript Debug Terminal

    WebStorm/IntelliJ IDEA Debug npm run script Tip: --dry-run does not write any updates to the file system Debugging Scaffolding mit Nx Local Generators LAB #12
  39. Trade-off Implementing a generalized/dynamic solution takes time! Always consider how

    much time you can save by using local generators. In one of our projects, implementing the generators took as much time as creating a new application from scratch, so the investment quickly paid off. Summary Scaffolding mit Nx Local Generators
  40. Documentation Generator/schematics documentation isn’t great Reading the code from Nx’s

    own generators was more helpful https://github.com/nrwl/nx/tree/master/packages/angular/src/generators Entirely different programming field Defensive programming style recommended (including meaningful error messages) Summary Scaffolding mit Nx Local Generators
  41. Documentation Not all handy functions are exported by @nx/devkit. Indeed

    better than Angular CLI, but requires customers to use Nx Part of Nrwl’s DNA Generators are fun, but complicated, still it may pay off! Summary Scaffolding mit Nx Local Generators