Slide 1

Slide 1 text

Scaffolding mit Nx Local Generators Christian Liebel @christianliebel [email protected]

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

Scaffolding mit Nx Local Generators Old: Nx Workspace Generators

Slide 12

Slide 12 text

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

Slide 13

Slide 13 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. (https://nx.dev/core-features/plugin-features/use-code-generators) Nx Introduction Scaffolding mit Nx Local Generators

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 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 Scaffolding mit Nx Local Generators

Slide 20

Slide 20 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 Scaffolding mit Nx Local Generators

Slide 21

Slide 21 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 Scaffolding mit Nx Local Generators

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 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 Scaffolding mit Nx Local Generators Generator Techniques

Slide 25

Slide 25 text

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

Slide 26

Slide 26 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; } Scaffolding mit Nx Local Generators Generator Techniques

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 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 Scaffolding mit Nx Local Generators

Slide 30

Slide 30 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 Scaffolding mit Nx Local Generators

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 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 Scaffolding mit Nx Local Generators

Slide 46

Slide 46 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 Scaffolding mit Nx Local Generators

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 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 Scaffolding mit Nx Local Generators LAB #12

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

Q&A Scaffolding mit Nx Local Generators

Slide 58

Slide 58 text

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