Slide 1

Slide 1 text

Angular Code Generators and Executors Christian Liebel @christianliebel christian.liebel@thinktecture.com

Slide 2

Slide 2 text

Hello, it’s me. Angular Code Generators and Executors Christian Liebel X: @christianliebel Email: christian.liebel @thinktecture.com Angular & PWA Slides: thinktecture.com /christian-liebel

Slide 3

Slide 3 text

Things NOT to expect − Pretty code − A super polished workshop—I’m filling in for a colleague Things To Expect − Basic concepts − Nx generators vs. Angular CLI schematics − Nx executors vs. Angular CLI builders − Focus is on Nx, but you can do more or less the same with Angular CLI − Patterns & practices − 20 Hands-on exercises Angular Code Generators and Executors

Slide 4

Slide 4 text

09:00–10:30 Block 1 10:30–11:00 Break 11:00–12:30 Block 2 12:30–13:30 Lunch Break 13:30–15:00 Block 3 15:00–15:30 Break 15:30–17:00 Block 4 Angular Code Generators and Executors Timetable

Slide 5

Slide 5 text

https://bit.ly/ijscg Slides Angular Code Generators and Executors

Slide 6

Slide 6 text

Setup complete? (Code editor, Node.js 18+, Git) Setup (1/2) Angular Code Generators and Executors

Slide 7

Slide 7 text

git clone https://github.com/thinktecture/ ijs-ny-2024-codegen.git cd ijs-ny-2024-codegen npm i Angular Code Generators and Executors Setup (2/2) LAB #0

Slide 8

Slide 8 text

Angular Code Generators and Executors LIVE DEMO

Slide 9

Slide 9 text

Nx Introduction Generator Concepts Generator Techniques Executor Concepts Executor Techniques Summary Agenda Angular Code Generators and Executors

Slide 10

Slide 10 text

Nx Introduction Generator Concepts Generator Techniques Executor Concepts Executor Techniques Summary Agenda Angular Code Generators and Executors

Slide 11

Slide 11 text

What is Nx? – “Build system with first class monorepo support and powerful integrations” (nx.dev) – Similar to Angular CLI, but 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 Angular Code Generators and Executors

Slide 12

Slide 12 text

npx create-nx-workspace@latest my-workspace Angular Code Generators and Executors Generating a new Nx workspace

Slide 13

Slide 13 text

Nx 16+ Angular Code Generators and Executors Nx Generators https://nx.dev/extending-nx/recipes/local-generators

Slide 14

Slide 14 text

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. – They ensure your codebase follows your organization’s best practices and style guides Nx Introduction Angular Code Generators and Executors https://nx.dev/features/generate-code

Slide 15

Slide 15 text

Executors – Similar to Angular Builders – Pre-packaged node scripts that can be used to run and automate tasks in a standardized way, ensuring they are executed consistently across different environments and projects. – While generators modify your workspace, executors usually don’t – Examples: Build/serve/test/lint/deploy the app, extract i18n data, … – Executors can be used by developers, CI/CD pipelines, or other executors Nx Introduction Angular Code Generators and Executors https://nx.dev/concepts/executors-and-configurations

Slide 16

Slide 16 text

Devkit Angular Devkit Schematics Builders Nx Introduction Angular Code Generators and Executors Nrwl Devkit Generators Executors

Slide 17

Slide 17 text

Nx Introduction Generator Concepts Generator Techniques Executor Concepts Executor Techniques Summary Agenda Angular Code Generators and Executors

Slide 18

Slide 18 text

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 Angular Code Generators and Executors Generators https://nx.dev/nx-api/angular/documents/nx-devkit-angular-devkit#generators

Slide 19

Slide 19 text

npx nx add @nx/plugin npx nx generate @nx/plugin:plugin my-plugin --directory=tools/my-plugin npx nx generate @nx/plugin:generator my-generator --directory=tools/my-plugin/src/generators/my-generator Angular Code Generators and Executors Generating a generator LAB #1

Slide 20

Slide 20 text

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 Angular Code Generators and Executors

Slide 21

Slide 21 text

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 Angular Code Generators and Executors

Slide 22

Slide 22 text

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 Angular Code Generators and Executors

Slide 23

Slide 23 text

JSON Schema Example: https://github.com/nrwl/nx/blob/master/packages/angular/src/generators/application /schema.json Generator Concepts Angular Code Generators and Executors

Slide 24

Slide 24 text

Nx Introduction Generator Concepts Generator Techniques Executor Concepts Executor Techniques Summary Agenda Angular Code Generators and Executors

Slide 25

Slide 25 text

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 Angular Code Generators and Executors Generator Techniques

Slide 26

Slide 26 text

npx nx g @nx/angular:library my-library Angular Code Generators and Executors Generating a new Angular library LAB #2

Slide 27

Slide 27 text

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; } Angular Code Generators and Executors Generator Techniques

Slide 28

Slide 28 text

Invoking other generators tools/my-plugin/src/generators/my-generator/generator.ts // New imports import { applicationGenerator } from '@nx/angular/generators'; // Update code export async function … { await applicationGenerator(tree, { name: options.name, routing: true, prefix: options.name, style: 'scss', }); } Generator Techniques Angular Code Generators and Executors LAB #3

Slide 29

Slide 29 text

Test your generator npx nx g my-generator --dry-run Generator Techniques Angular Code Generators and Executors LAB #4

Slide 30

Slide 30 text

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 Angular Code Generators and Executors

Slide 31

Slide 31 text

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 Angular Code Generators and Executors

Slide 32

Slide 32 text

Generate files (1/2) 1. Delete tools/my-plugin/src/generators/my-generator/files/src/index.ts.template. 2. Create tools/my-plugin/src/generators/my-generator/files/app.workflow.ts: export const loginSteps = []; Generator Techniques Angular Code Generators and Executors LAB #5

Slide 33

Slide 33 text

Generate files (2/2) 3. Adjust your local generator as follows: // New imports import {generateFiles, joinPathFragments, readProjectConfiguration, Tree} from '@nx/devkit'; // Add this code to the end of the myGeneratorGenerator function const sourceDir = joinPathFragments(__dirname, 'files'); const projectDir = readProjectConfiguration(tree, options.name).root; const destinationDir = joinPathFragments(projectDir, 'src', 'app'); generateFiles(tree, sourceDir, destinationDir, {}); 4. Test your generator: npx nx g my-generator app --dry-run Generator Techniques Angular Code Generators and Executors LAB #5

Slide 34

Slide 34 text

Generate files – Templates You may experience problems with your IDE or other tooling when dealing with source code files that are incomplete or contain generator template syntax. To resolve this, add the “.template” suffix to the extension of such files. The generator will automatically remove the suffix for you. Generator Techniques Angular Code Generators and Executors

Slide 35

Slide 35 text

Generate files – Templates 1. Rename my-generator/files/app.workflow.ts to my-generator/files/app.workflow.ts.template 2. Test your generator again: npx nx g my-generator app --dry-run Generator Techniques Angular Code Generators and Executors LAB #6

Slide 36

Slide 36 text

Generate files – Utility Functions import { classify, dasherize } from '@nx/devkit/src/utils/string-utils'; Generator Techniques Angular Code Generators and Executors Type Input myCoolApp classify() MyCoolApp dasherize() my-cool-app

Slide 37

Slide 37 text

Generate files – Substitutions generateFiles(tree, sourceDir, destinationDir, { projectFileName: dasherize(schema.name), projectClassName: classify(schema.name), }); Generator Techniques Angular Code Generators and Executors

Slide 38

Slide 38 text

High-level utility functions (@nx/angular) import { addImportToComponent } from '@nx/angular/src/utils'; addImportToComponent(host, source, componentPath, symbolName); Generator Techniques Angular Code Generators and Executors

Slide 39

Slide 39 text

Writing a custom utility function Introduce a new file tools/my-plugin/src/generators/utils/get-source-file.ts: import { Tree } from '@nx/devkit'; import { createSourceFile, ScriptTarget } from 'typescript'; export function getSourceFile(tree: Tree, filePath: string) { const moduleSrc = tree.read(filePath, 'utf-8'); return createSourceFile(filePath, moduleSrc, ScriptTarget.Latest, true); } Generator Techniques Angular Code Generators and Executors LAB #7

Slide 40

Slide 40 text

High-level utility functions (1/2) 1. Adjust your generator implementation: // New imports import { addImportToComponent } from '@nx/angular/src/utils'; import { insertImport } from '@nx/js'; import { getSourceFile } from '../utils/get-source-file'; // Add this code to the end of the myGeneratorGenerator function const appComponentPath = joinPathFragments(destinationDir, 'app.component.ts'); addImportToComponent(tree, getSourceFile(tree, appComponentPath), appComponentPath, 'MyLibraryComponent'); insertImport(tree, getSourceFile(tree, appComponentPath), appComponentPath, 'MyLibraryComponent', '@my-workspace/my-library'); Generator Techniques Angular Code Generators and Executors LAB #8

Slide 41

Slide 41 text

High-level utility functions (2/2) 2. Run your generator: npx nx g my-generator app Generator Techniques Angular Code Generators and Executors LAB #8

Slide 42

Slide 42 text

Invoking Angular schematics import { wrapAngularDevkitSchematic } from 'nx/src/adapter/ngcli-adapter'; wrapAngularDevkitSchematic('@schematics/angular', 'module'); Generator Techniques Angular Code Generators and Executors

Slide 43

Slide 43 text

Convert to Angular Schematic import { convertNxGenerator } from '@nx/devkit'; export const myGeneratorSchematic = convertNxGenerator(myGenerator); Generator Techniques Angular Code Generators and Executors

Slide 44

Slide 44 text

Invoking Angular schematics (1/4) 1. Generate a new generator: npx nx g @nx/plugin:generator my-step --directory=tools/my-plugin/src/generators/my-step 2. Adjust the JSON schema (tools/my-plugin/src/generators/my-step/ schema.json) and add a project property to the properties object: "project": { "type": "string", "description": "Target project" } Generator Techniques Angular Code Generators and Executors LAB #9

Slide 45

Slide 45 text

Invoking Angular schematics (2/4) 3. Add the project property to the TypeScript schema definition file (generators/my- step/schema.d.ts): interface MyStepGeneratorSchema { name: string; project: string; } Generator Techniques Angular Code Generators and Executors LAB #9

Slide 46

Slide 46 text

Invoking Angular schematics (3/4) 4. Adjust the MyStep generator to call the Angular cmp schematic: // New imports import { wrapAngularDevkitSchematic } from 'nx/src/adapter/ngcli-adapter'; import { dasherize } from '@nx/devkit/src/utils/string-utils'; // Replace the code inside the myStepGenerator function export async function myStepGenerator(…) { const componentSchematic = wrapAngularDevkitSchematic('@schematics/angular', 'component'); await componentSchematic(tree, { name: dasherize(options.name), project: options.project }); } Generator Techniques Angular Code Generators and Executors LAB #9

Slide 47

Slide 47 text

Invoking Angular schematics (4/4) 5. Test your generator: npx nx g my-step step1 --project app --dry-run Generator Techniques Angular Code Generators and Executors LAB #9

Slide 48

Slide 48 text

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 Angular Code Generators and Executors

Slide 49

Slide 49 text

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 Angular Code Generators and Executors

Slide 50

Slide 50 text

TypeScript AST (1/4) tools/my-plugin/src/generators/my-step/generator.ts: // New imports import { joinPathFragments, readProjectConfiguration, Tree } from '@nx/devkit'; import { getSourceFile } from '../utils/get-source-file'; // Add this code to the end of the myStepGenerator function const projectDir = readProjectConfiguration(tree, options.project).root; const workflowFileName = joinPathFragments(projectDir, 'src', 'app', 'app.workflow.ts'); const sourceFile = getSourceFile(tree, workflowFileName); Generator Techniques Angular Code Generators and Executors LAB #10

Slide 51

Slide 51 text

TypeScript AST (2/4) // New imports import { Identifier, isIdentifier, isVariableStatement } from 'typescript’; // Add this code to the end of the myStepGenerator function 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 Angular Code Generators and Executors LAB #10

Slide 52

Slide 52 text

TypeScript AST (3/4) // New imports import { applyChangesToString, ChangeType, joinPathFragments, readProjectConfiguration, Tree } from '@nx/devkit'; import { classify, dasherize } from '@nx/devkit/src/utils/string-utils’; // Add this code to the end of the myStepGenerator function const stepToAdd = `"${classify(options.name)}",`; const newContents = applyChangesToString(sourceFile.text, [{ type: ChangeType.Insert, index: stepsDeclaration.end - 1, text: stepToAdd }]); tree.write(workflowFileName, newContents); Generator Techniques Angular Code Generators and Executors LAB #10

Slide 53

Slide 53 text

TypeScript AST (4/4) Run your generator: npx nx g my-step step --project app Generator Techniques Angular Code Generators and Executors LAB #10

Slide 54

Slide 54 text

import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; describe('app', () => { let appTree: Tree; beforeEach(() => { appTree = createTreeWithEmptyWorkspace(); }); it('…', () => {}); }); Testing Angular Code Generators and Executors

Slide 55

Slide 55 text

1. Delete the my-step generator test spec (tools/my-plugin/src/generators/my- step/generator.spec.ts). 2. Add the following test to the my-generator test file (generators/my-generator/generator.spec.ts): it('should create workflow file', async () => { await myGeneratorGenerator(tree, options); expect(tree.exists('test/src/app/app.workflow.ts')).toBeTruthy(); }); 3. Run: npx nx test --project my-plugin Testing Angular Code Generators and Executors LAB #11

Slide 56

Slide 56 text

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 Angular Code Generators and Executors LAB #12

Slide 57

Slide 57 text

Add Storybook Component Story https://github.com/nrwl/nx/blob/master/packages/angular/src/generators/componen t-story/component-story.ts Angular Code Generators and Executors Nx Built-in Generator Example

Slide 58

Slide 58 text

– Nx 19.8 (released in September 2024) introduced nx sync and sync generators – Sync generators ensure the file system is in the correct state (e.g., code formatting applied, etc.) – Option-less generators invoked before running tasks, CI, … Angular Code Generators and Executors Appendix: Sync Generators

Slide 59

Slide 59 text

Nx Introduction Generator Concepts Generator Techniques Executor Concepts Executor Techniques Summary Agenda Angular Code Generators and Executors

Slide 60

Slide 60 text

Overview – Executors are scripts that are used to automate and run tasks that don’t alter source files in the workspace (e.g., building/testing an app) – They are part of a plug-in (e.g., @nx/webpack package) – They have a name (e.g., @nx/webpack:webpack) Angular Code Generators and Executors Executor Concepts

Slide 61

Slide 61 text

Overview – Executors must be configured in the project’s project.json file (Nx’s version of angular.json) – Each project has targets to run executors (e.g., build or test) – These can be invoked via the command line (e.g., nx build, nx test) – Each target has options that contain the default configuration the executor is invoked with – Each target can have configurations that override the default options for certain scenarios (e.g., enabling source maps for dev builds) – Command line arguments override the configuration Angular Code Generators and Executors Executor Concepts https://nx.dev/concepts/executors-and-configurations

Slide 62

Slide 62 text

Example target { "name": "my-workspace", "projectType": "application", "targets": { "build": { "executor": "@angular-devkit/build-angular:application", "options": { "outputPath": "dist/apps/my-workspace", "index": "apps/my-workspace/src/index.html" }, "configurations": { "production": { "outputHashing": "all" }, "development": { "sourceMap": true } }, Angular Code Generators and Executors Executor Concepts

Slide 63

Slide 63 text

Running from the command line nx build mylib --configuration=production nx run mylib:build:production Angular Code Generators and Executors Executor Concepts

Slide 64

Slide 64 text

Contents import { PromiseExecutor } from '@nx/devkit'; import { EchoExecutorSchema } from './schema'; const runExecutor: PromiseExecutor = async (options) => { console.log('Executor ran for Echo', options); return { success: true, }; }; export default runExecutor; Angular Code Generators and Executors Executor Concepts

Slide 65

Slide 65 text

JSON Schema Example: https://github.com/nrwl/nx/blob/master/packages/angular/src/executors/extract- i18n/schema.json Executor Concepts Angular Code Generators and Executors

Slide 66

Slide 66 text

vs. Builders Executors – Return promise/async iterable – Free functions, don’t have to be wrapped – Supports Nx executors & Angular CLI builders Builders – Based on RxJS – Have to be wrapped in createBuilder() – Only supports Angular CLI builders Angular Code Generators and Executors Executor Concepts https://nx.dev/nx-api/angular/documents/nx-devkit-angular-devkit#executors

Slide 67

Slide 67 text

Nx Introduction Generator Concepts Generator Techniques Executor Concepts Executor Techniques Summary Agenda Angular Code Generators and Executors

Slide 68

Slide 68 text

Demo Path 1. Call terminal commands without any code 2. Create a new echo executor that echoes the value passed via CLI 3. Invoke the build executor provided by Nx 4. Call custom terminal code (create file with current Git commit hash) 5. Make Git hash file creation conditional based on configuration Angular Code Generators and Executors Executor Techniques

Slide 69

Slide 69 text

Running simple terminal commands (no code) Shorthand method for invoking the nx:run-command executor: { "root": "apps/cart", "sourceRoot": "apps/cart/src", "projectType": "application", "generators": {}, "targets": { "echo": { "command": "echo 'hello world'" } } } Angular Code Generators and Executors Executor Techniques https://nx.dev/concepts/executors-and-configurations#run-a-terminal-command-from-an-executor

Slide 70

Slide 70 text

Running simple terminal commands (no code) 1. Add the echo target to the apps/my-workspace/project.json configuration file: "targets": { "echo": { "command": "echo 'hello world'" } } 2. Run: npx nx echo my-workspace Angular Code Generators and Executors Executor Techniques https://nx.dev/concepts/executors-and-configurations#run-a-terminal-command-from-an-executor LAB #13

Slide 71

Slide 71 text

Running simple terminal commands (no code) The run-command executor supports many more configuration options, including running commands in parallel. https://nx.dev/nx-api/nx/executors/run-commands Angular Code Generators and Executors Executor Techniques

Slide 72

Slide 72 text

Generating a new executor (with a generator) Run the following command to create the new echo executor: npx nx generate @nx/plugin:executor echo --directory=tools/my-plugin/src/executors/echo Angular Code Generators and Executors Executor Techniques LAB #14

Slide 73

Slide 73 text

Reading options (1/2) 1. Adjust the JSON schema to take a value string property and read it from the command line arguments (argv) in tools/my-plugin/src/executors/echo/schema.json: "properties": { "value": { "type": "string", "description": "The value to echo.", "$default": { "$source": "argv", "index": 0 } } }, Angular Code Generators and Executors Executor Techniques LAB #15

Slide 74

Slide 74 text

Reading options (2/2) 2. Adjust the TypeScript schema: export interface EchoExecutorSchema { value: string; } 3. Adjust the console.log in executors/echo/executor.ts (line 5): console.log(options.value); Angular Code Generators and Executors Executor Techniques LAB #15

Slide 75

Slide 75 text

Running our executor 1. Change the echo executor in apps/my-workspace/project.json: targets: { "echo": { "executor": "@my-workspace/my-plugin:echo", "options": {} } } 2. Run: npx nx echo my-workspace "Hello it’s me" Angular Code Generators and Executors Executor Techniques LAB #16

Slide 76

Slide 76 text

Executor context – The executor is invoked with a context containing metadata (e.g., name of the project, target or configuration, root folder). – The context is passed as the second argument to the executor function. const runExecutor: PromiseExecutor = async (options: ExecutorSchema, context: ExecutorContext) => { } Executor Techniques Angular Code Generators and Executors

Slide 77

Slide 77 text

Invoking other executors – The Nx DevKit contains a runExecutor() method that allows you to call other executors. – This function takes the target, project and configuration to call, any overrides, and an executor context. Note: The executor function Nx generates is also called runExecutor() by default. The Nx DevKit function needs to be renamed during import to use the function. Executor Techniques Angular Code Generators and Executors

Slide 78

Slide 78 text

Invoking other executors (1/3) 1. Add and import for the runExecutor() function and rename it: import { PromiseExecutor, runExecutor as run } from '@nx/devkit'; 2. Add the context parameter to the executor function: const runExecutor: PromiseExecutor = async (options, context) => { } Executor Techniques Angular Code Generators and Executors LAB #17

Slide 79

Slide 79 text

Invoking other executors (2/3) 3. Invoke the build executor in the executor function (after the console.log(), but before returning the success object): const result = await run({ target: 'build', project: context.projectName, configuration: 'production' }, {}, context); for await (const res of result) { if (!res.success) return res; } Executor Techniques Angular Code Generators and Executors LAB #17

Slide 80

Slide 80 text

Invoking other executors (3/3) 4. Run the echo executor again: npx nx echo my-workspace "Hello it’s me" Executor Techniques Angular Code Generators and Executors LAB #17

Slide 81

Slide 81 text

Additional DevKit Helper Functions Angular Code Generators and Executors Executor Techniques https://nx.dev/extending-nx/recipes/compose-executors#devkit-helper-functions Property Description logger() Wraps console to add some formatting getPackageManagerCommand() Returns commands for the package manager used in the workspace parseTargetString() Parses a target string into {project, target, configuration} readTargetOptions() Reads and combines options for a given target runExecutor() Constructs options and invokes an executor

Slide 82

Slide 82 text

Custom scripting (1/2) 1. Add the following imports: import { joinPathFragments, PromiseExecutor, runExecutor as run } from '@nx/devkit'; import { EchoExecutorSchema } from './schema'; import { promisify } from 'util'; import { exec } from 'child_process'; import { writeFile } from 'fs/promises'; Executor Techniques Angular Code Generators and Executors LAB #18

Slide 83

Slide 83 text

Custom scripting (2/2) 2. Add the following implementation after the previous code in the executor function, but before returning the success object: const execPromise = promisify(exec); const execResult = await execPromise('git rev-parse HEAD'); const path = joinPathFragments(context.root, 'dist', 'apps’, context.projectName, 'browser', 'hash.txt'); await writeFile(path, execResult.stdout); Executor Techniques Angular Code Generators and Executors LAB #18

Slide 84

Slide 84 text

Multiple configurations – In the project configuration, each executor can be assigned multiple configurations. – Configurations override the default options set for the executor. – The configurations are set via a CLI parameter: --configuration=ci – This can be used to change the executor's behavior based on a certain environment, e.g., dev or prod builds or running on a CI server. – One of the configurations can be selected as the default configuration. If no configuration is specified, the default configuration will be used. Angular Code Generators and Executors Executor Techniques https://nx.dev/concepts/executors-and-configurations#default-configuration

Slide 85

Slide 85 text

Multiple configurations (1/5) 1. Add the createGitHash property to executors/echo/schema.json: "properties": { "createGitHash": { "type": "boolean", "description": "Create a git hash.", "default": false } } Angular Code Generators and Executors Executor Techniques LAB #19

Slide 86

Slide 86 text

Multiple configurations (2/5) 2. Update executors/echo/schema.d.ts to reflect the changes: export interface EchoExecutorSchema { value: string; createGitHash: boolean; } Angular Code Generators and Executors Executor Techniques LAB #19

Slide 87

Slide 87 text

Multiple configurations (3/5) 3. Adjust the implementation in executors/echo/executor.ts to make Git hash creation conditional: if (options.createGitHash) { const execPromise = promisify(exec); const execResult = await execPromise('git rev-parse HEAD'); const path = joinPathFragments(context.root, 'dist', 'apps', context.projectName, 'browser', 'hash.txt'); await writeFile(path, execResult.stdout); } Angular Code Generators and Executors Executor Techniques LAB #19

Slide 88

Slide 88 text

Multiple configurations (4/5) 4. Adjust the echo config in apps/my-workspace/project.json as follows: "echo": { "executor": "@my-workspace/my-plugin:echo", "options": { "value": "Hello from options" }, "configurations": { "dev": { "value": "Hello from dev" }, "ci": { "createGitHash": true } }, "defaultConfiguration": "dev" }, Angular Code Generators and Executors Executor Techniques LAB #19

Slide 89

Slide 89 text

Multiple configurations (5/5) 5. Run the echo executor in various ways to see the effects: npx nx echo my-workspace npx nx echo my-workspace --configuration=ci npx nx echo my-workspace --value "cmd" --createGitHash true Angular Code Generators and Executors Executor Techniques LAB #19

Slide 90

Slide 90 text

Call executor from CI Add the following line at the end of .github/workflows/ci.yml: - run: npx nx echo my-workspace --configuration=ci Angular Code Generators and Executors Executor Techniques LAB #20

Slide 91

Slide 91 text

Convert to Angular Builder import { convertNxExecutor } from '@nx/devkit'; export const myExecutorBuilder = convertNxExecutor(myExecutor); Executor Techniques Angular Code Generators and Executors

Slide 92

Slide 92 text

Convert from Angular builder – There is no utility function to convert an Angular builder to an Nx executor. – You can import the Angular builder implementation and call it from your Nx executor though. Executor Techniques Angular Code Generators and Executors https://nx.dev/deprecated/angular-schematics-builders

Slide 93

Slide 93 text

Extract i18n executor Example: https://github.com/nrwl/nx/blob/master/packages/angular/src/executors/extract- i18n/extract-i18n.impl.ts Angular Code Generators and Executors Nx Built-in Executor Example

Slide 94

Slide 94 text

– Currently, all executors and generators are local, i.e. they can only be used as a part of this workspace. – You can publish your @my-workspace/my-plugin plug-in to a private or public npm package registry to make it accessible to a larger audience, company-wide or generally public. Angular Code Generators and Executors Local Executors/Generators & Plug-ins

Slide 95

Slide 95 text

Nx Introduction Generator Concepts Generator Techniques Executor Concepts Executor Techniques Summary Agenda Angular Code Generators and Executors

Slide 96

Slide 96 text

Trade-off – Implementing a generalized/dynamic solution takes time! – Consider how much time you can save by using generators/executors. – In one of our projects, implementing the generators took as much time as creating a single new application from scratch, so the investment quickly paid off. Summary Angular Code Generators and Executors

Slide 97

Slide 97 text

Documentation – Generator/executor (schematics/builder) documentation generally isn’t great, the Nx documentation is better – Reading the code from Nx’s own generators was more helpful (https://github.com/nrwl/nx/tree/master/packages/angular/src/) – Entirely different programming field (dealing with OS-specifics, the file system, AST parsing, etc.) – Defensive programming style recommended (including meaningful error messages) Summary Angular Code Generators and Executors

Slide 98

Slide 98 text

Nx – 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 & executors are fun, but complicated, still it may pay off! Summary Angular Code Generators and Executors

Slide 99

Slide 99 text

Q&A Angular Code Generators and Executors

Slide 100

Slide 100 text

Angular Code Generators and Executors

Slide 101

Slide 101 text

Thank you for your kind attention! Christian Liebel @christianliebel christian.liebel@thinktecture.com