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. Scaffolding mit Nx Local Generators
    Christian Liebel
    @christianliebel
    [email protected]

    View full-size slide

  2. 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

    View full-size slide

  3. 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

    View full-size slide

  4. 09:00–10:30 Block 1
    10:30–11:00 Break
    11:00–12:30 Block 2
    Scaffolding mit Nx Local Generators
    Timetable

    View full-size slide

  5. Setup complete?
    (Code editor, Node.js 16+, Git)
    Setup (1/2)
    Scaffolding mit Nx Local Generators

    View full-size slide

  6. git clone https://github.com/thinktecture/
    angular-days-2023-fall-gen.git
    cd angular-days-2023-fall-gen
    npm i
    Scaffolding mit Nx Local Generators
    Setup (2/2)
    LAB #0

    View full-size slide

  7. Nx
    Introduction
    Generator
    Concepts
    Generator
    Techniques
    Summary
    Agenda
    Scaffolding mit Nx Local Generators

    View full-size slide

  8. Nx
    Introduction
    Generator
    Concepts
    Generator
    Techniques
    Summary
    Agenda
    Scaffolding mit Nx Local Generators

    View full-size slide

  9. 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

    View full-size slide

  10. npx create-nx-workspace@latest [my-workspace]
    Scaffolding mit Nx Local Generators
    Generating a new Nx workspace

    View full-size slide

  11. Scaffolding mit Nx Local Generators
    Old: Nx Workspace Generators

    View full-size slide

  12. Nx 16+
    Scaffolding mit Nx Local Generators
    New: Nx Local Generators
    https://nx.dev/extending-nx/recipes/local-generators

    View full-size slide

  13. 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

    View full-size slide

  14. 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

    View full-size slide

  15. Devkit
    Angular
    Devkit
    Schematics Builders
    Nx Introduction
    Scaffolding mit Nx Local Generators
    Nrwl
    Devkit
    Generators Executors

    View full-size slide

  16. 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

    View full-size slide

  17. Nx
    Introduction
    Generator
    Concepts
    Generator
    Techniques
    Summary
    Agenda
    Scaffolding mit Nx Local Generators

    View full-size slide

  18. 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

    View full-size slide

  19. 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

    View full-size slide

  20. 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

    View full-size slide

  21. 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

    View full-size slide

  22. JSON Schema
    Example:
    https://github.com/nrwl/nx/blob/master/packages/angular/src/generato
    rs/application/schema.json
    Generator Concepts
    Scaffolding mit Nx Local Generators

    View full-size slide

  23. Nx
    Introduction
    Generator
    Concepts
    Generator
    Techniques
    Summary
    Agenda
    Scaffolding mit Nx Local Generators

    View full-size slide

  24. 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

    View full-size slide

  25. npx nx g @nx/angular:library my-library
    Scaffolding mit Nx Local Generators
    Generating a new Angular library LAB #2

    View full-size slide

  26. 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

    View full-size slide

  27. 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

    View full-size slide

  28. Test your generator
    npx nx g my-generator --dry-run
    Generator Techniques
    Scaffolding mit Nx Local Generators
    LAB #4

    View full-size slide

  29. 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

    View full-size slide

  30. 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

    View full-size slide

  31. 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

    View full-size slide

  32. Generate files
    3. Test your generator: npx nx g my-generator app --dry-run
    Generator Techniques
    Scaffolding mit Nx Local Generators
    LAB #5

    View full-size slide

  33. 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

    View full-size slide

  34. 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

    View full-size slide

  35. Generate files – Substitutions
    generateFiles(tree, sourceDir, destinationDir, {
    tpl: '',
    projectFileName: dasherize(schema.name),
    projectClassName: classify(schema.name),
    });
    Generator Techniques
    Scaffolding mit Nx Local Generators

    View full-size slide

  36. 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

    View full-size slide

  37. 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

    View full-size slide

  38. 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

    View full-size slide

  39. High-level utility functions
    Run your generator: npx nx g my-generator app
    Generator Techniques
    Scaffolding mit Nx Local Generators
    LAB #8

    View full-size slide

  40. Invoking Angular schematics
    import { wrapAngularDevkitSchematic } from 'nx/src/commands/ngcli-adapter';
    wrapAngularDevkitSchematic('@schematics/angular', 'module');
    Generator Techniques
    Scaffolding mit Nx Local Generators

    View full-size slide

  41. Convert to Angular Schematic
    import { convertNxGenerator } from '@nx/devkit';
    export const mygeneratorSchematic = convertNxGenerator(myGenerator);
    Generator Techniques
    Scaffolding mit Nx Local Generators

    View full-size slide

  42. 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

    View full-size slide

  43. 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

    View full-size slide

  44. 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

    View full-size slide

  45. 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

    View full-size slide

  46. 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

    View full-size slide

  47. 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

    View full-size slide

  48. 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

    View full-size slide

  49. 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

    View full-size slide

  50. TypeScript AST
    Run your generator:
    npx nx g my-step step --project app
    Generator Techniques
    Scaffolding mit Nx Local Generators
    LAB #10

    View full-size slide

  51. import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
    describe('app', () => {
    let appTree: Tree;
    beforeEach(() => { appTree = createTreeWithEmptyWorkspace(); });
    it('…', () => {});
    });
    Testing
    Scaffolding mit Nx Local Generators
    LAB #11

    View full-size slide

  52. 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

    View full-size slide

  53. Nx
    Introduction
    Generator
    Concepts
    Generator
    Techniques
    Summary
    Agenda
    Scaffolding mit Nx Local Generators

    View full-size slide

  54. 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

    View full-size slide

  55. 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

    View full-size slide

  56. 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

    View full-size slide

  57. Q&A
    Scaffolding mit Nx Local Generators

    View full-size slide

  58. Thank you
    for your kind attention!
    Christian Liebel
    @christianliebel
    [email protected]

    View full-size slide