@maciejtreder
@maciejtreder
Angular Schematics
Develop for developers
@maciejtreder
@maciejtreder
• Kraków, Poland
• Senior Software Development Engineer in Test
Akamai Technologies
• Angular passionate
• Open source contributor (founder of @ng-toolkit project)
• Articles author
@maciejtreder
• Cambridge, Massachusetts
• Content Delivery Network
• Over 240 00 servers deployed in more then 120 countries
• Serves 15%-30% of the Internet traffic
@maciejtreder
Innovation Begins With an Idea
• SEO friendly
• Works offline
• Cheap environment
- Angular Universal
- PWA
- Serverless
@maciejtreder
Do Not Reinvent the Wheel
• Angular CLI
• Angular Webpack starter (https://github.com/preboot/angular-webpack)
• Angular Universal starter (https://github.com/angular/universal-starter)
@maciejtreder
Yet Another Boilerplate…
• Progressive Web App
• Server-Side Rendering
• Hosted on AWS Lambda
• Uploaded to GitHub
• ~30 clones weekly
angular-universal-serverless-pwa
@maciejtreder
The Gray Eminence
Is GIT designed for hosting dev tools?
@esosanderelias
@maciejtreder
Schematics
@maciejtreder
Schematics
• Set of instructions (rules) consumed by the Angular CLI to manipulate the
file-system and perform NodeJS tasks
• Extensible - possible to combine multiple internal and external rules
• Atomic - “commit approach”/“all or nothing”
ng add/update/init/something
@maciejtreder
ng add @ng-toolkit/universal
@maciejtreder
package.json
{
"author": "Maciej Treder <contact@maciejtreder.com>",
"name": "@ng-toolkit/universal",
"main": "dist/index.js",
"version": "1.1.50",
"description": "Adds Angular Universal support for any Angular CLI project",
"repository": {
"type": "git",
"url": "git+https://github.com/maciejtreder/ng-toolkit.git"
},
"license": "MIT",
"schematics": "./collection.json",
"peerDependencies": {
},
"dependencies": {
},
"devDependencies": {
},
"publishConfig": {
"access": "public"
"schematics": "./collection.json",
@maciejtreder
collection.json
{
"$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
"schematics": {
"ng-add": {
"factory": "./schematics",
"description": "Update an application with server side rendering (Angular Universal)",
"schema": "./schema.json"
}
}
}
"factory": "./schematics",
"schema": "./schema.json"
@maciejtreder
schema.json{
"$schema": "http://json-schema.org/schema",
"id": "ng-toolkit universal",
"title": "Angular Application Options Schema",
"type": "object",
"properties": {
"directory": {
"description": "App root catalog",
"type": "string",
"default": "."
},
"http": {
"description": "Determines if you want to install TransferHttpCacheModule",
"type": "boolean",
"default": true
}
},
"required": []
ng add @ng-toolkit/universal —http false
@maciejtreder
schematics.ts
export default function index(options: any): Rule {
if (options.http) {
//true or nothing was passed (default value)
} else {
//false was passed
}
}
@maciejtreder
Tree
• Object which represents file system
• Supports CRUD operations and more:
• exists()
• getDir()
• visit()
• etc
@maciejtreder
Rule
• Set of instructions for the Angular CLI
• (tree: Tree, context: SchematicContext) => Tree | Observable<Tree> |
Rule | void
let rule: Rule = (tree: Tree) => {
tree.create('hello', 'world');
return tree;
}
krk-mps4m:angular-oslo mtreder$ ls
hello
krk-mps4m:angular-oslo mtreder$ cat hello
world
CLI
@maciejtreder
tree
Chaining
export default function index(options: any): Rule {
return chain([
rule1,
rule2,
rule3
])
}
rule1 rule2 rule3
@maciejtreder
Tree | Observable<Tree> | Rule |
void
export function performAdditionalAction(originalRule: Rule): Rule {
return (tree: Tree, context: SchematicContext) => {
originalRule.apply(tree, context)
.pipe(map(
(tree: Tree) => console.log(tree.exists('hello'))
)
);
}
}
@maciejtreder
export function applyAndLog(rule: Rule): Rule {
bugsnag.register('0b326fddc255310e516875c9874fed91');
return (tree: Tree, context: SchematicContext) => {
return (<Observable<Tree>> rule(tree, context))
.pipe(catchError((error: any) => {
let subject: Subject<Tree> = new Subject();
bugsnag.notify(error, (bugsnagError, response) => {
if (!bugsnagError && response === 'OK') {
console.log(`Stacktrace sent to tracking system.`);
}
subject.next(Tree.empty());
subject.complete();
});
return subject;
}));
}
}
@maciejtreder
@maciejtreder
ng update
{
"ng-update": {
"requirements": { "my-lib": "^5" },
"migrations": "./migrations/migration-collection.json"
}
} {
"schematics": {
"migration-01": { "version": "6", "factory": "./update-6" },
"migration-02": { "version": "6.2", "factory": "./update-6_2" },
"migration-03": { "version": "6.3", "factory": "./update-6_3" }
}
}
Updates one or multiple packages, its peer dependencies and the peer dependencies th
@maciejtreder
ng [what?] —collection
export default function(options: Schema): Rule {
const rule: Rule = chain([
externalSchematic('@schematics/angular', 'ng-new', options),
// other rules
]);
return rule;
}
Shadows default rules/schematics collection
• ng new —collection @ng-toolkit/init
• ng generate service —collection myCollection
@maciejtreder
Working with source-code
import * as ts from 'typescript';
export default function(options: any): Rule {
return (tree: Tree, context: SchematicContext) => {
const filePath = `${options.directory}/sourceFile.ts`;
const recorder = tree.beginUpdate(filePath);
let fileContent = getFileContent(tree, filePath);
let sourceFile: ts.SourceFile = ts.createSourceFile('temp.ts', fileContent, ts.ScriptTarget.Latest);
sourceFile.forEachChild(node => {
if (ts.isClassDeclaration(node)) {
node.members.forEach(node => {
if (ts.isConstructorDeclaration(node)) {
if (node.body) {
recorder.insertRight(node.body.pos + 1, 'console.log('constructor!');')
}
}
});
}
});
tree.commitUpdate(recorder);
const recorder = tree.beginUpdate(filePath);
let sourceFile: ts.SourceFile = ts.createSourceFile(
import * as ts from 'typescript';
recorder.insertRight(node.body.pos + 1, 'console.log('constructor!');')
tree.commitUpdate(recorder);
@maciejtreder
is…
• isImportDeclaration
• isVariableDeclaration
• isClassDeclaration
• isMethodDeclaration
• isStringLiteral
• isIfStatement
@maciejtreder
tree.
tree.create('path', 'content');
tree.exists('path')
tree.overwrite('path', 'new file content');
tree.getDir(`${options.directory}/src/environments`).visit( (path: Path) => {
if (path.endsWith('.ts')) {
addEntryToEnvironment(tree, path, 'line to be inserted');
}
});
const recorder = tree.beginUpdate(‘filePath’);
tree.commitUpdate(recorder);
@maciejtreder
Why should I use typescript?
Task:
change all ‘window’ occurences to ‘this.window’
Source code:export MyClass {
private message = 'Do not open window!';
console.log(window
.URL);
}
@maciejtreder
SchematicContext
export default function(options: any): Rule {
return (tree: Tree, context: SchematicContext) => {
context.addTask(new NodePackageInstallTask(options.directory));
return tree;
}
}
• NodePackageInstallTask
• NodePackageLinkTask
• RepositoryInitializerTask
• RunSchematicTask
• TslintFixTask
@maciejtreder
Testing
const collectionPath = path.join(__dirname, './collection.json');
describe('Universal', () => {
let appTree: UnitTestTree;
const schematicRunner = new SchematicTestRunner('@ng-toolkit/universal', collectionPath);
const appOptions: any = { name: 'foo', version: '7.0.0'};
beforeEach((done) => {
appTree = new UnitTestTree(Tree.empty());
schematicRunner.runExternalSchematicAsync(
'@schematics/angular',
'ng-new',
appOptions,
appTree
).subscribe(tree => {
appTree = tree
done();
});
});
@maciejtreder
Testing
const defaultOptions: any = {
project: 'foo',
disableBugsnag: true,
directory: '/foo'
};
it('Should add server build', (done) => {
schematicRunner.runSchematicAsync('ng-add', defaultOptions, appTree).subscribe(tree => {
const cliConfig = JSON.parse(getFileContent(tree, `${defaultOptions.directory}/angular.json`));
expect(cliConfig.projects.foo.architect.server).toBeDefined(`Can't find server build`);
done();
});
})
@maciejtreder
e2e
npm install -g verdaccio
verdaccio --config scripts/default.yaml >> verdacio_output &
npm set registry=http://localhost:4873/
echo "//localhost:4873/:_authToken="fooBar"" >> ~/.npmrc
@maciejtreder
e2e
npm install -g verdaccio
verdaccio --config scripts/default.yaml >> verdacio_output &
npm set registry=http://localhost:4873/
cp ~/.npmrc ~/.npmrc_original
echo "//localhost:4873/:_authToken="fooBar"" >> ~/.npmrc
@maciejtreder
e2e
cd dist
npm publish --registry http://localhost:4873
cd
ng new testApp
ng add @ng-toolkit/universal
npm set registry=https://registry.npmjs.org/
@maciejtreder
Use cases
@maciejtreder
No more boring readme!
Change long instructions to simple parameters
ng add @ng-toolkit/universal
• installs @ng-toolkit/universal package
• creates server configuration in angular.json
• adds NgtUniversalModule to your default app module
• adds TransferHttpCacheModule
• replaces all ocurences of window object with window wrapper
@maciejtreder
Corporate mess!
Anna
HYZ Corporation
Front-end team
Bob
@maciejtreder
Customize your codebase!
• install @xyz/forms
• add OurFormsModule to your app
• customize your code in 32123 files
• udpate @xyz/forms
• customize your code again!
• ng add @xyz/forms
• ng update @xyz/forms
VS
@maciejtreder

JS Fest 2019/Autumn. Maciej Treder. Angular Schematics - Develop for developers

  • 1.
  • 2.
  • 3.
    @maciejtreder @maciejtreder • Kraków, Poland •Senior Software Development Engineer in Test Akamai Technologies • Angular passionate • Open source contributor (founder of @ng-toolkit project) • Articles author
  • 4.
    @maciejtreder • Cambridge, Massachusetts •Content Delivery Network • Over 240 00 servers deployed in more then 120 countries • Serves 15%-30% of the Internet traffic
  • 5.
    @maciejtreder Innovation Begins Withan Idea • SEO friendly • Works offline • Cheap environment - Angular Universal - PWA - Serverless
  • 6.
    @maciejtreder Do Not Reinventthe Wheel • Angular CLI • Angular Webpack starter (https://github.com/preboot/angular-webpack) • Angular Universal starter (https://github.com/angular/universal-starter)
  • 7.
    @maciejtreder Yet Another Boilerplate… •Progressive Web App • Server-Side Rendering • Hosted on AWS Lambda • Uploaded to GitHub • ~30 clones weekly angular-universal-serverless-pwa
  • 8.
    @maciejtreder The Gray Eminence IsGIT designed for hosting dev tools? @esosanderelias
  • 9.
  • 10.
    @maciejtreder Schematics • Set ofinstructions (rules) consumed by the Angular CLI to manipulate the file-system and perform NodeJS tasks • Extensible - possible to combine multiple internal and external rules • Atomic - “commit approach”/“all or nothing” ng add/update/init/something
  • 11.
  • 12.
    @maciejtreder package.json { "author": "Maciej Treder<contact@maciejtreder.com>", "name": "@ng-toolkit/universal", "main": "dist/index.js", "version": "1.1.50", "description": "Adds Angular Universal support for any Angular CLI project", "repository": { "type": "git", "url": "git+https://github.com/maciejtreder/ng-toolkit.git" }, "license": "MIT", "schematics": "./collection.json", "peerDependencies": { }, "dependencies": { }, "devDependencies": { }, "publishConfig": { "access": "public" "schematics": "./collection.json",
  • 13.
    @maciejtreder collection.json { "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "ng-add":{ "factory": "./schematics", "description": "Update an application with server side rendering (Angular Universal)", "schema": "./schema.json" } } } "factory": "./schematics", "schema": "./schema.json"
  • 14.
    @maciejtreder schema.json{ "$schema": "http://json-schema.org/schema", "id": "ng-toolkituniversal", "title": "Angular Application Options Schema", "type": "object", "properties": { "directory": { "description": "App root catalog", "type": "string", "default": "." }, "http": { "description": "Determines if you want to install TransferHttpCacheModule", "type": "boolean", "default": true } }, "required": [] ng add @ng-toolkit/universal —http false
  • 15.
    @maciejtreder schematics.ts export default functionindex(options: any): Rule { if (options.http) { //true or nothing was passed (default value) } else { //false was passed } }
  • 16.
    @maciejtreder Tree • Object whichrepresents file system • Supports CRUD operations and more: • exists() • getDir() • visit() • etc
  • 17.
    @maciejtreder Rule • Set ofinstructions for the Angular CLI • (tree: Tree, context: SchematicContext) => Tree | Observable<Tree> | Rule | void let rule: Rule = (tree: Tree) => { tree.create('hello', 'world'); return tree; } krk-mps4m:angular-oslo mtreder$ ls hello krk-mps4m:angular-oslo mtreder$ cat hello world CLI
  • 18.
    @maciejtreder tree Chaining export default functionindex(options: any): Rule { return chain([ rule1, rule2, rule3 ]) } rule1 rule2 rule3
  • 19.
    @maciejtreder Tree | Observable<Tree>| Rule | void export function performAdditionalAction(originalRule: Rule): Rule { return (tree: Tree, context: SchematicContext) => { originalRule.apply(tree, context) .pipe(map( (tree: Tree) => console.log(tree.exists('hello')) ) ); } }
  • 20.
    @maciejtreder export function applyAndLog(rule:Rule): Rule { bugsnag.register('0b326fddc255310e516875c9874fed91'); return (tree: Tree, context: SchematicContext) => { return (<Observable<Tree>> rule(tree, context)) .pipe(catchError((error: any) => { let subject: Subject<Tree> = new Subject(); bugsnag.notify(error, (bugsnagError, response) => { if (!bugsnagError && response === 'OK') { console.log(`Stacktrace sent to tracking system.`); } subject.next(Tree.empty()); subject.complete(); }); return subject; })); } }
  • 21.
  • 22.
    @maciejtreder ng update { "ng-update": { "requirements":{ "my-lib": "^5" }, "migrations": "./migrations/migration-collection.json" } } { "schematics": { "migration-01": { "version": "6", "factory": "./update-6" }, "migration-02": { "version": "6.2", "factory": "./update-6_2" }, "migration-03": { "version": "6.3", "factory": "./update-6_3" } } } Updates one or multiple packages, its peer dependencies and the peer dependencies th
  • 23.
    @maciejtreder ng [what?] —collection exportdefault function(options: Schema): Rule { const rule: Rule = chain([ externalSchematic('@schematics/angular', 'ng-new', options), // other rules ]); return rule; } Shadows default rules/schematics collection • ng new —collection @ng-toolkit/init • ng generate service —collection myCollection
  • 24.
    @maciejtreder Working with source-code import* as ts from 'typescript'; export default function(options: any): Rule { return (tree: Tree, context: SchematicContext) => { const filePath = `${options.directory}/sourceFile.ts`; const recorder = tree.beginUpdate(filePath); let fileContent = getFileContent(tree, filePath); let sourceFile: ts.SourceFile = ts.createSourceFile('temp.ts', fileContent, ts.ScriptTarget.Latest); sourceFile.forEachChild(node => { if (ts.isClassDeclaration(node)) { node.members.forEach(node => { if (ts.isConstructorDeclaration(node)) { if (node.body) { recorder.insertRight(node.body.pos + 1, 'console.log('constructor!');') } } }); } }); tree.commitUpdate(recorder); const recorder = tree.beginUpdate(filePath); let sourceFile: ts.SourceFile = ts.createSourceFile( import * as ts from 'typescript'; recorder.insertRight(node.body.pos + 1, 'console.log('constructor!');') tree.commitUpdate(recorder);
  • 25.
    @maciejtreder is… • isImportDeclaration • isVariableDeclaration •isClassDeclaration • isMethodDeclaration • isStringLiteral • isIfStatement
  • 26.
    @maciejtreder tree. tree.create('path', 'content'); tree.exists('path') tree.overwrite('path', 'newfile content'); tree.getDir(`${options.directory}/src/environments`).visit( (path: Path) => { if (path.endsWith('.ts')) { addEntryToEnvironment(tree, path, 'line to be inserted'); } }); const recorder = tree.beginUpdate(‘filePath’); tree.commitUpdate(recorder);
  • 27.
    @maciejtreder Why should Iuse typescript? Task: change all ‘window’ occurences to ‘this.window’ Source code:export MyClass { private message = 'Do not open window!'; console.log(window .URL); }
  • 28.
    @maciejtreder SchematicContext export default function(options:any): Rule { return (tree: Tree, context: SchematicContext) => { context.addTask(new NodePackageInstallTask(options.directory)); return tree; } } • NodePackageInstallTask • NodePackageLinkTask • RepositoryInitializerTask • RunSchematicTask • TslintFixTask
  • 29.
    @maciejtreder Testing const collectionPath =path.join(__dirname, './collection.json'); describe('Universal', () => { let appTree: UnitTestTree; const schematicRunner = new SchematicTestRunner('@ng-toolkit/universal', collectionPath); const appOptions: any = { name: 'foo', version: '7.0.0'}; beforeEach((done) => { appTree = new UnitTestTree(Tree.empty()); schematicRunner.runExternalSchematicAsync( '@schematics/angular', 'ng-new', appOptions, appTree ).subscribe(tree => { appTree = tree done(); }); });
  • 30.
    @maciejtreder Testing const defaultOptions: any= { project: 'foo', disableBugsnag: true, directory: '/foo' }; it('Should add server build', (done) => { schematicRunner.runSchematicAsync('ng-add', defaultOptions, appTree).subscribe(tree => { const cliConfig = JSON.parse(getFileContent(tree, `${defaultOptions.directory}/angular.json`)); expect(cliConfig.projects.foo.architect.server).toBeDefined(`Can't find server build`); done(); }); })
  • 31.
    @maciejtreder e2e npm install -gverdaccio verdaccio --config scripts/default.yaml >> verdacio_output & npm set registry=http://localhost:4873/ echo "//localhost:4873/:_authToken="fooBar"" >> ~/.npmrc
  • 32.
    @maciejtreder e2e npm install -gverdaccio verdaccio --config scripts/default.yaml >> verdacio_output & npm set registry=http://localhost:4873/ cp ~/.npmrc ~/.npmrc_original echo "//localhost:4873/:_authToken="fooBar"" >> ~/.npmrc
  • 33.
    @maciejtreder e2e cd dist npm publish--registry http://localhost:4873 cd ng new testApp ng add @ng-toolkit/universal npm set registry=https://registry.npmjs.org/
  • 34.
  • 35.
    @maciejtreder No more boringreadme! Change long instructions to simple parameters ng add @ng-toolkit/universal • installs @ng-toolkit/universal package • creates server configuration in angular.json • adds NgtUniversalModule to your default app module • adds TransferHttpCacheModule • replaces all ocurences of window object with window wrapper
  • 36.
  • 37.
    @maciejtreder Customize your codebase! •install @xyz/forms • add OurFormsModule to your app • customize your code in 32123 files • udpate @xyz/forms • customize your code again! • ng add @xyz/forms • ng update @xyz/forms VS
  • 38.

Editor's Notes

  • #12 kreska od npm do angular + info,ze będzie coś robic
  • #16 zmienić tytuł
  • #18 dopisać CLI do obrazka
  • #21 zmienić nazwę zmiennej errorów wewn.
  • #25 dodać slajd window -> this.window private someVar = „do not ocen the widnow”;
  • #29 sprawdzić interfejs potrzebny do napisania własnego taska
  • #32 verdaccio na dol
  • #34 cd testApp