企業應用中常定製一些通用的組件,提供統一的用戶界面、數據呈現方式等,以便在不一樣的應用中重複使用。能夠將通用組件構建成Angular庫,這些庫能夠在Workspace本地使用,也能夠把它們發佈成 npm 包,共享給其它項目或其餘Angular開發者。css
Angular過期了麼?
Angular 與 angular.js 不是同一種前端框架,angular.js在2010年10月發佈,而 Angular 誕生於2016 年 9 月,比 React 和 Vue都要晚。整體而言,Angular、Vue 與 React 三種框架的運行速度沒有太大差別,不會是項目運行快慢的決定因素。html
國內華爲、阿里、中興等大廠都開發了Angular企業級組件庫,分別爲DevUI、NG-ZORRO、Jigsaw。另外,Angular Material是官方Angular組件庫。前端
開源組件庫是咱們學習Angular庫開發的最好教材,也能夠在他們的基礎上定製咱們的組件。node
Schematic,Angular中文版譯做原理圖,我更喜歡稱之爲腳手架。Schematic是一個基於模板的支持複雜邏輯的代碼生成器,能夠建立、修改和維護軟件項目。Schematic是Angular生態系統的一部分,咱們經常使用的Angular CLI命令ng generate、ng add和ng update,爲咱們添加/更新庫、建立構件提供了便利的工具。webpack
Angular CLI默認調用Schematics集合@schematics/angular,下面兩個命令功能是相同的:git
ng g component hello-world ng g @schematics/angular:component hello-world
在庫開發中,一般要建立本身的schematics。es6
本文GitHub源碼:https://github.com/sunjc/ng-itrunnergithub
用如下命令生成一個新庫的骨架:web
ng new ng-itrunner --new-project-root --create-application=false cd ng-itrunner ng generate library ng-itrunner --prefix ni
這會在工做區中建立 ng-itrunner 文件夾,裏面包含 NgModule、一個組件和一個服務。工做區的配置文件 angular.json 中添加了一個 'library' 類型的項目:shell
"projects": { "ng-itrunner": { "projectType": "library", "root": "ng-itrunner", "sourceRoot": "ng-itrunner/src", "prefix": "ni", "architect": { "build": { "builder": "@angular-devkit/build-ng-packagr:build", "options": { "tsConfig": "ng-itrunner/tsconfig.lib.json", "project": "ng-itrunner/ng-package.json" }, "configurations": { "production": { "tsConfig": "ng-itrunner/tsconfig.lib.prod.json" } } } ...
源文件 | 用途 |
---|---|
src/lib | 包含庫項目的邏輯和數據。像應用項目同樣,庫項目也能夠包含組件、服務、模塊、指令和管道 |
src/test.ts | 單元測試主入口點,含一些庫專屬的配置 |
src/public-api.ts | 指定從庫中導出的全部文件 |
karma.conf.js | Karma 配置 |
ng-package.json | 構建庫時,ng-packagr 用到的配置文件 |
package.json | 配置庫所需的 npm 包依賴 |
tsconfig.lib.json | 庫專屬的 TypeScript 配置,包括 TypeScript 和 Angular 模板編譯器選項 |
tsconfig.spec.json | 測試庫時用到的 TypeScript 配置 |
tslint.json | 庫專屬的 TSLint 配置 |
要讓庫代碼能夠複用,必須定義一個公共的 API public-api.ts。當庫被導入應用時,從該文件導出的全部內容都會公開。
運行以下命令:
ng build ng-itrunner ng test ng-itrunner ng lint ng-itrunner
說明,庫與應用的構建器不一樣:
庫編譯後,默認會生成esm五、esm201五、fesm五、fesm201五、es2015幾種格式。
增量構建
增量構建功能能夠改善庫的開發體驗,每當文件發生變化時,都會執行局部構建:
ng build ng-itrunner --watch
前面編譯時能夠看到下面的輸出:
****************************************************************************** It is not recommended to publish Ivy libraries to NPM repositories. Read more here: https://v9.angular.io/guide/ivy#maintaining-library-compatibility ******************************************************************************
Angular 9使用Ivy編譯,不建議把 Ivy 格式的庫發佈到 NPM 倉庫。在tsconfig.lib.prod.json文件的配置中禁用了Ivy,會使用老的View Engine編譯器和運行時:
{ "extends": "./tsconfig.lib.json", "angularCompilerOptions": { "enableIvy": false } }
生產編譯時使用--prod選項,而後再發布:
ng build ng-itrunner --prod cd dist/ng-itrunner npm publish
默認發佈到公共NPM registry https://registry.npmjs.org 。也能夠發佈到私有Registry,好比Nexus,配置.npmrc以下:
registry=http://localhost:8081/repository/itrunner/ email=sjc-925@163.com always-auth=true _auth=YWRtaW46YWRtaW4xMjM=
其中 _auth 項爲用戶名:密碼的Base64編碼,生成命令以下:
echo -n 'admin:admin123' | openssl base64
也能夠發佈到指定registry:
npm publish --registry=http://localhost:8081/repository/itrunner/
連接庫
在開發要發佈的庫時,可使用 npm link 把庫連接到全局 node_modules 文件夾中,避免每次構建時都從新安裝庫。
cd dist/ng-itrunner npm link or npm link dist/ng-itrunner
沒必要把庫發佈到 npm 包管理器,也能夠在本身的應用中使用它。
Angular 庫是一個 Angular 項目,它與應用的不一樣之處在於它自己是不能運行的。咱們先建立一個應用:
ng g application demo
在AppModule中導入NgItrunnerModule:
import {NgItrunnerModule} from 'ng-itrunner'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AppRoutingModule, NgItrunnerModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
說明:
當在 Angular 應用中從某個庫導入東西時,Angular會尋找庫名和磁盤某個位置之間的映射關係。當用 npm 包安裝庫時,映射到 node_modules 目錄下。當本身構建庫時,就會在 tsconfig 路徑中查找這個映射。
用 Angular CLI 生成庫時,會自動把它的路徑添加到 tsconfig 文件中。 Angular CLI 使用 tsconfig 路徑告訴構建系統在哪裏尋找這個庫。
修改app.component.html,添加咱們庫中的組件:
<ni-ng-itrunner></ni-ng-itrunner> <!-- Resources --> <h2>Resources</h2>
啓動Demo,查看效果:
ng serve demo
首先,學習NG-ZORRO的目錄結構和命名習慣,調整一下咱們的庫配置。
{ "ngPackage": { "lib": { "entryFile": "public-api.ts" } } }
export * from './public-api';
index.ts文件能夠減小import語句,如:
import {NiHelloLibComponent, NiHelloLibService} from '../hello-lib';
export default {};
{ "$schema": "../node_modules/ng-packagr/ng-package.schema.json", "dest": "../dist/ng-itrunner", "deleteDestPath": true, "lib": { "entryFile": "index.ts" } }
"paths": { "ng-itrunner/*": [ "dist/ng-itrunner/*" ] }
ng build ng-itrunner ng test ng-itrunner
在Workspace執行如下命令,安裝ng-zorro-antd:
ng add ng-zorro-antd Installing packages for tooling via npm. Installed packages for tooling via npm. ? Enable icon dynamic loading [ Detail: https://ng.ant.design/components/icon/en ] Yes ? Set up custom theme file [ Detail: https://ng.ant.design/docs/customize-theme/en ] Yes ? Choose your locale code: en_US ? Choose template to create project: blank UPDATE package.json (1311 bytes) UPDATE demo/src/app/app.component.html (276 bytes) √ Packages installed successfully. CREATE demo/src/theme.less (28746 bytes) UPDATE demo/src/app/app.module.ts (895 bytes) UPDATE angular.json (3867 bytes)
編輯components/package.json,添加ng-zorro-antd:
"peerDependencies": { "@angular/common": "^9.0.0", "@angular/core": "^9.0.0", "ng-zorro-antd": "^9.0.0", "tslib": "^1.10.0" }
Angular CDK,組件開發工具包,實現了通用交互模式和核心功能,是庫與腳手架開發的必備工具。
在Workspace執行如下命令,安裝@angular/cdk:
ng add @angular/cdk
編輯components/package.json,添加@angular/cdk:
"peerDependencies": { "@angular/cdk": "^9.0.0", "@angular/common": "^9.0.0", "@angular/core": "^9.0.0", "ng-zorro-antd": "^9.0.0", "tslib": "^1.10.0" }
咱們簡單地封裝NG-ZORRO內聯登陸欄爲新組件,演示以NG-ZORRO組件爲基礎定製本身的組件。
在components目錄下建立inline-login-form文件夾,在其下建立如下文件:
index.ts
export * from './public-api';
inline-login-form.component.ts
import {Component, EventEmitter, OnInit, Output} from '@angular/core'; import {FormBuilder, FormGroup, Validators} from '@angular/forms'; @Component({ selector: 'ni-inline-login-form', template: ` <form nz-form [nzLayout]="'inline'" [formGroup]="loginForm" (ngSubmit)="submitForm()"> <nz-form-item> <nz-form-control nzErrorTip="Please input your username!"> <nz-input-group nzPrefixIcon="user"> <input formControlName="username" nz-input placeholder="Username"/> </nz-input-group> </nz-form-control> </nz-form-item> <nz-form-item> <nz-form-control nzErrorTip="Please input your Password!"> <nz-input-group nzPrefixIcon="lock"> <input formControlName="password" nz-input type="password" placeholder="Password"/> </nz-input-group> </nz-form-control> </nz-form-item> <nz-form-item> <nz-form-control> <button nz-button nzType="primary" [disabled]="!loginForm.valid">Log in</button> </nz-form-control> </nz-form-item> </form> ` }) export class NiInlineLoginFormComponent implements OnInit { @Output() login: EventEmitter<any> = new EventEmitter(); loginForm: FormGroup; submitForm(): void { this.login.emit(this.loginForm.value); } constructor(private fb: FormBuilder) { } ngOnInit(): void { this.loginForm = this.fb.group({ username: [null, [Validators.required]], password: [null, [Validators.required]] }); } }
inline-login-form.module.ts
import {NgModule} from '@angular/core'; import {NiInlineLoginFormComponent} from './inline-login-form.component'; import {NzButtonModule, NzFormModule, NzInputModule} from 'ng-zorro-antd'; import {ReactiveFormsModule} from '@angular/forms'; @NgModule({ declarations: [NiInlineLoginFormComponent], imports: [ ReactiveFormsModule, NzButtonModule, NzFormModule, NzInputModule ], exports: [NiInlineLoginFormComponent] }) export class NiInlineLoginFormModule { }
package.json
{ "ngPackage": { "lib": { "entryFile": "public-api.ts", "umdModuleIds": { "ng-zorro-antd": "ng-zorro-antd" } } } }
咱們引入了外部模塊ng-zorro-antd,若未配置UMD 標識符映射,編譯時則會輸出如下信息:
WARNING: No name was provided for external module 'ng-zorro-antd' in output.globals – guessing 'ngZorroAntd'
public-api.ts
export * from './inline-login-form.module'; export * from './inline-login-form.component';
測試組件
從新編譯庫後,在AppModule中引入NiHelloLibModule、NiInlineLoginFormModule:
... import {NiInlineLoginFormModule} from 'ng-itrunner/inline-login-form'; ... @NgModule({ declarations: [ AppComponent ], imports: [ ... NiHelloLibModule, NiInlineLoginFormModule, ], providers: [{provide: NZ_I18N, useValue: en_US}], bootstrap: [AppComponent] }) export class AppModule { }
在app.component.html中引入組件:
<nz-divider></nz-divider> <!-- NI-iTRunner --> <div nz-row nzJustify="center"> <ni-hello></ni-hello> </div> <div nz-row nzJustify="center"> <ni-inline-login-form (login)="login($event)"></ni-inline-login-form> </div>
在app.component.ts中添加login()方法:
login(user: { username: string, password: string }) { ... }
啓動demo,效果以下:
Schematics有本身的命令行工具schematics cli,運行如下命令安裝:
npm install -g @angular-devkit/schematics-cli
Schematics最多見用途是將 Angular 庫與 Angular CLI 集成在一塊兒。能夠直接在 Angular 工做空間的庫項目中建立Schematics文件,而無需使用 Schematics CLI。
下面咱們使用 CLI 建立一個Schematics集合,僅爲介紹文件和目錄結構,以及一些基本概念。
建立Schematics集合
執行以下命令在同名的新項目文件夾中建立一個名爲 hello-world 的Schematic:
schematics blank --name=hello-world
生成項目的src/ 文件夾包含hello-world子文件夾,以及一個模式文件(collection.json)。
Schematic文件結構
每一個schematic通常都有如下主要部分:
文件 | 說明 |
---|---|
index.ts | 定義schematic中轉換邏輯的代碼 |
schema.json | schematic變量定義 |
schema.ts | schematic變量 |
files/ | 要複製的可選組件/模板文件 |
src/hello-world中的主文件 index.ts 定義實現Schematic邏輯的規則:
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics'; // You don't have to export the function as default. You can also have more than one rule factory // per file. export function helloWorld(_options: any): Rule { return (tree: Tree, _context: SchematicContext) => { return tree; }; }
入口函數helloWorld是一個規則工廠,能夠經過調用外部工具和實現邏輯來修改項目。規則能夠利用 @schematics/angular 包提供的實用工具來處理模塊、依賴、TypeScript、AST、JSON、Angular CLI 工做空間和項目等等:
import { JsonAstObject, JsonObject, JsonValue, Path, normalize, parseJsonAst, strings, } from '@angular-devkit/core';
collection.json
collection.json是集合中各個schematic的模式定義。每一個schematic都是用名稱、描述和工廠函數建立的:
{ "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "hello-world": { "description": "A blank schematic.", "factory": "./hello-world/index#helloWorld" } } }
還有兩個可選屬性:schema和aliases。
例如:
... "ng-add": { "description": "Adds Angular Material to the application without affecting any templates", "factory": "./ng-add/index", "schema": "./ng-add/schema.json", "aliases": ["material-shell", "install"] } ...
schema.ts
定義schematic變量,例如:
export interface Schema { /** Name of the project. */ project: string; /** Whether Angular browser animations should be set up. */ animations: boolean; /** Name of pre-built theme to install. */ theme: 'indigo-pink' | 'deeppurple-amber' | 'pink-bluegrey' | 'purple-green' | 'custom'; /** Whether to set up global typography styles. */ typography: boolean; }
schema.json
定義輸入選項及其容許的值和默認值,例如:
{ "$schema": "http://json-schema.org/schema", "id": "angular-material-ng-add", "title": "Angular Material ng-add schematic", "type": "object", "properties": { "project": { "type": "string", "description": "Name of the project.", "$default": { "$source": "projectName" } }, "theme": { "description": "The theme to apply", "type": "string", "default": "indigo-pink", "x-prompt": { "message": "Choose a prebuilt theme name, or \"custom\" for a custom theme:", "type": "list", "items": [ { "value": "indigo-pink", "label": "Indigo/Pink [ Preview: https://material.angular.io?theme=indigo-pink ]" }, { "value": "deeppurple-amber", "label": "Deep Purple/Amber [ Preview: https://material.angular.io?theme=deeppurple-amber ]" }, { "value": "pink-bluegrey", "label": "Pink/Blue Grey [ Preview: https://material.angular.io?theme=pink-bluegrey ]" }, { "value": "purple-green", "label": "Purple/Green [ Preview: https://material.angular.io?theme=purple-green ]" }, { "value": "custom", "label": "Custom" } ] } }, "typography": { "type": "boolean", "default": false, "description": "Whether to set up global typography styles.", "x-prompt": "Set up global Angular Material typography styles?" }, "animations": { "type": "boolean", "default": true, "description": "Whether Angular browser animations should be set up.", "x-prompt": "Set up browser animations for Angular Material?" } }, "required": [] }
schema.json語法請查看官方文檔。
安裝依賴、編譯Schematic
cd hello-world npm install npm run build
運行Schematic
按如下格式提供項目路徑、Schematic名稱和全部必選項,使用 schematics 命令運行Schematic:
schematics <path-to-schematics-project>:<schematics-name> --<required-option>=<value>
路徑能夠是絕對路徑,也能夠是執行該命令的當前工做目錄的相對路徑:
schematics .:hello-world
接下來回到ng-itrunner workspace,建立庫的Schematics。做爲一名庫開發人員,一般要開發add schematic、generation schematic、update schematic,以便把庫與 Angular CLI 集成在一塊兒,能夠運行ng add來安裝庫,運行ng generate來修改項目、添加構件等,運行ng update更新庫依賴、調整變動等。
建立Add Schematic
import {Rule, SchematicContext, Tree} from '@angular-devkit/schematics'; import {NodePackageInstallTask, RunSchematicTask} from '@angular-devkit/schematics/tasks'; import {addPackageToPackageJson} from '../utils/package-config'; import {angularCdkVersion, zorroVersion} from '../utils/version-names'; import {Schema} from './schema'; /** * Schematic factory entry-point for the `ng-add` schematic. The ng-add schematic will be * automatically executed if developers run `ng add ng-itrunner`. * * Since the NG-iTRunner schematics depend on the schematic utility functions from the CDK, * we need to install the CDK before loading the schematic files that import from the CDK. */ export default function(options: Schema): Rule { return (host: Tree, context: SchematicContext) => { addPackageToPackageJson(host, '@angular/cdk', angularCdkVersion); addPackageToPackageJson(host, 'ng-zorro-antd', zorroVersion); const installTaskId = context.addTask(new NodePackageInstallTask()); context.addTask(new RunSchematicTask('ng-add-setup-project', options), [installTaskId]); }; }
在運行ng-add schematic前,CLI會自動添加ng-itrunner到宿主項目的package.json中。咱們的schematic會用到CDK工具函數,咱們的庫依賴ng-zorro-antd,所以首先須要將二者添加到package.json中。接下來,SchematicContext觸發安裝任務NodePackageInstallTask,將依賴安裝到項目的 node_modules 目錄下。最後調用另外一Schematic任務ng-add-setup-project配置項目。
說明:代碼中涉及的utils方法,請查看GitHub源碼。
在庫開發中,通常會定義主題、依賴某些module等,能夠在此配置這些項目。前面咱們安裝了Angular CDK,如今可使用@angular/cdk的工具函數了。
import {chain, Rule, SchematicContext, Tree} from '@angular-devkit/schematics'; import {RunSchematicTask} from '@angular-devkit/schematics/tasks'; import {getProjectFromWorkspace} from '@angular/cdk/schematics'; import {getWorkspace} from '@schematics/angular/utility/config'; import {getProjectStyle} from '../utils/project-style'; import {Schema} from './schema'; /** * Scaffolds the basics of a NG-iTRunner application, this includes: * - Add Template */ export default function(options: Schema): Rule { return chain([ addTemplate(options) ]); } function addTemplate(options: Schema) { return (host: Tree, context: SchematicContext) => { if (options.template) { const workspace = getWorkspace(host); const project = getProjectFromWorkspace(workspace, options.project); const style = getProjectStyle(project); context.addTask(new RunSchematicTask(options.template, {...options, style: style})); } return host; }; }
chain() 方法容許把多個規則組合到一個規則中,這樣就能夠在一個Schematic中執行多個操做。這裏僅爲示例,只添加了一個建立模板組件方法。模板組件Schematic將在下一節介紹,爲成功運行ng-add能夠先暫時註釋此部分代碼。
export enum ProjectTemplate { Blank = 'blank', Login = 'login' } export interface Schema { /** Name of the project to target. */ project?: string; template?: ProjectTemplate; }
{ "$schema": "http://json-schema.org/schema", "id": "ni-ng-add", "title": "NG-iTRunner ng-add schematic", "type": "object", "properties": { "project": { "type": "string", "description": "Name of the project.", "$default": { "$source": "projectName" } }, "template": { "type": "string", "default": "blank", "description": "Create an Angular project with using preset template.", "x-prompt": { "message": "Choose template to create project:", "type": "list", "items": [ "blank", "login" ] } } }, "required": [] }
{ "$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "ng-add": { "description": "Add NG-iTRunner", "factory": "./ng-add/index", "schema": "./ng-add/schema.json", "hidden": true }, "ng-add-setup-project": { "description": "Sets up the specified project after the ng-add dependencies have been installed.", "private": true, "factory": "./ng-add/setup-project", "schema": "./ng-add/schema.json" }, "blank": { "description": "Set up boot page", "private": true, "factory": "./ng-generate/blank/index", "schema": "./ng-generate/blank/schema.json" }, "login": { "description": "Create a login component", "factory": "./ng-generate/login/index", "schema": "./ng-generate/login/schema.json" } } }
... "schematics": "./schematics/collection.json" ...
構建Schematic
要把Schematic和庫打包到一塊兒,必須把庫配置成單獨構建Schematic,而後再把它們添加到發佈包中。所以必須先構建庫再構建Schematic,才能把它們放到正確的目錄下。
{ "compilerOptions": { "baseUrl": ".", "lib": [ "es2018", "dom" ], "declaration": true, "module": "commonjs", "moduleResolution": "node", "noEmitOnError": true, "noFallthroughCasesInSwitch": true, "noImplicitAny": false, "noImplicitThis": true, "noUnusedParameters": true, "noUnusedLocals": true, "rootDir": "schematics", "outDir": "../dist/ng-itrunner/schematics", "skipDefaultLibCheck": true, "skipLibCheck": true, "sourceMap": true, "strictNullChecks": true, "target": "es6", "types": [ "jasmine", "node" ] }, "include": [ "schematics/**/*" ], "exclude": [ "schematics/*/files/**/*" ] }
"scripts": { "build": "../node_modules/.bin/tsc -p tsconfig.schematics.json", "copy:schemas": "cp --parents schematics/*/schema.json schematics/*/*/schema.json ../dist/ng-itrunner/", "copy:files": "cp --parents -r schematics/*/*/files/** ../dist/ng-itrunner/", "copy:collection": "cp schematics/collection.json ../dist/ng-itrunner/schematics/collection.json", "copy:migration": "cp schematics/migration.json ../dist/ng-itrunner/schematics/migration.json", "postbuild": "npm run copy:schemas && npm run copy:files && npm run copy:collection && npm run copy:migration" }
說明:上面是本示例中完整的build腳本,須要根據實際狀況調整路徑、postbuild。後面再也不說明。
ng build ng-itrunner --prod cd components npm run build
運行Schematic
cd dist/ng-itrunner npm publish npm link
ng add ng-itrunner
運行ng add ng-itrunner將自動執行ng-add schematic。
運行ng generate --help能夠查看@schematics/angular提供的默認Schematic:
Available Schematics: Collection "@schematics/angular" (default): appShell application class component directive enum guard interceptor interface library module pipe service serviceWorker webWorker
collection.json中未設置"hidden"和"private"屬性爲true的schematics會顯示在Available Schematics列表中。
接下來咱們將新建blank和login兩個schematic,用於建立初始頁面和登陸組件,collection.json以下:
{ "$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "ng-add": { "description": "Add NG-iTRunner", "factory": "./ng-add/index", "schema": "./ng-add/schema.json", "hidden": true }, "ng-add-setup-project": { "description": "Sets up the specified project after the ng-add dependencies have been installed.", "private": true, "factory": "./ng-add/setup-project", "schema": "./ng-add/schema.json" }, "blank": { "description": "Set up boot page", "private": true, "factory": "./ng-generate/blank/index", "schema": "./ng-generate/blank/schema.json" }, "login": { "description": "Create a login component", "factory": "./ng-generate/login/index", "schema": "./ng-generate/login/schema.json" } } }
其中ng-add的"hidden"屬性爲true,ng-add-setup-project和blank的"private"屬性爲true。"hidden"屬性爲true則運行ng generate --help時不會顯示在Available Schematics列表中;"private"屬性爲true代表該Schematic僅供內部調用,同時暗示"hidden"屬性爲true。
當發佈咱們的庫後,因僅login可顯示,運行如下命令會直接顯示login的幫助:
ng g ng-itrunner: --help Generates and/or modifies files based on a schematic. usage: ng generate ng-itrunner:login <name> [options] arguments: schematic The schematic or collection:schematic to generate. options: --defaults When true, disables interactive input prompts for options with a default. --dry-run (-d) When true, runs through and reports activity without writing out results. --force (-f) When true, forces overwriting of existing files. --help Shows a help message for this command in the console. --interactive When false, disables interactive input prompts. Help for schematic ng-itrunner:login arguments: name The name of the component. options: --prefix (-p) The prefix to apply to generated selectors. --project The name of the project. --skip-import Flag to skip the module import. --style The file extension to be used for style files.
不管是否設置"hidden"和"private"屬性,實際上不會影響運行ng generate,下面的命令能夠正常執行:
ng g ng-itrunner:blank ng g ng-itrunner:ng-add
在schematics/文件夾中建立ng-generate文件夾。
blank schematic
blank schematic將覆蓋app.component.html,其內容僅包含一個圖片連接和一個ni-hello組件。
在ng-generate文件夾中建立blank文件夾,而後分別建立如下文件:
import {Rule, Tree} from '@angular-devkit/schematics'; import {addModuleImportToRootModule, getProjectFromWorkspace} from '@angular/cdk/schematics'; import {getWorkspace} from '@schematics/angular/utility/config'; import {existsSync, statSync as fsStatSync} from 'fs'; import {Schema} from './schema'; import {itRunnerImage} from '../../utils/image'; const bootPageHTML = `<!-- NG-iTRunner --> <a href="https://github.com/sunjc/ng-itrunner" target="_blank" style="display: flex;align-items: center;justify-content: center;width: 100%;"> <img height="382" src="${itRunnerImage}" > </a> <div style="text-align: center"> <ni-hello></ni-hello> </div>`; export default function(options: Schema): Rule { return (host: Tree) => { const workspace = getWorkspace(host); const project = getProjectFromWorkspace(workspace, options.project); const appHTMLFile = `${project.sourceRoot}/app/app.component.html`; const buffer = host.read(appHTMLFile); if (!buffer) { console.error(`Could not find the project ${appHTMLFile} file inside of the workspace config`); return; } if (existsSync(appHTMLFile)) { const stat = fsStatSync(appHTMLFile); if (stat.mtimeMs === stat.ctimeMs) { host.overwrite(appHTMLFile, bootPageHTML); } } else { host.overwrite(appHTMLFile, bootPageHTML); } // import NiHelloLibModule addModuleImportToRootModule(host, 'NiHelloLibModule', 'ng-itrunner/hello-lib', project); return host; }; }
export interface Schema { /** Name of the project to target. */ project?: string; }
{ "$schema": "http://json-schema.org/schema", "id": "ni-ng-generate-boot", "title": "NG-iTRunner boot page schematic", "type": "object", "properties": { "project": { "type": "string", "description": "Name of the project.", "$default": { "$source": "projectName" } } }, "required": [] }
login schematic
login schematic利用模板文件封裝了ni-inline-login-form組件。
在ng-generate文件夾中建立login文件夾,而後建立如下文件:
在login文件夾中建立下面的目錄:
files\__path__\__name@dasherize@if-flat__
而後在其下建立四個component模板文件:
__name@dasherize__.component.html.template __name@dasherize__.component.ts.template __name@dasherize__.component.spec.ts.template __name@dasherize__.component.__style__.template
內容分別爲:
<ni-inline-login-form (login)="login($event)"></ni-inline-login-form>
import { Component } from '@angular/core'; @Component({ selector: '<%= prefix %>-login', templateUrl: './<%= dasherize(name) %>.component.html', styleUrls: ['./<%= dasherize(name) %>.component.<%= style %>'] }) export class <%= classify(name) %>Component { login(user: { username: string, password: string }) { console.log(`{username: ${user.username}, password: ${user.password}}`); } }
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; import { NzButtonModule, NzFormModule, NzInputModule } from 'ng-zorro-antd'; import { <%= classify(name) %>Component } from './<%= dasherize(name) %>.component'; describe('<%= classify(name) %>Component', () => { let component: <%= classify(name) %>Component; let fixture: ComponentFixture<<%= classify(name) %>Component>; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [<%= classify(name) %>Component], imports: [ ReactiveFormsModule, NzButtonModule, NzFormModule, NzInputModule ] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(<%= classify(name) %>Component); component = fixture.componentInstance; fixture.detectChanges(); }); it('should compile', () => { expect(component).toBeTruthy(); }); });
import {chain, noop, Rule, Tree} from '@angular-devkit/schematics'; import {addModuleImportToModule, buildComponent, findModuleFromOptions} from '@angular/cdk/schematics'; import {Schema} from './schema'; export default function(options: Schema): Rule { return chain([ buildComponent({...options}, { template: './__path__/__name@dasherize@if-flat__/__name@dasherize__.component.html.template', stylesheet: './__path__/__name@dasherize@if-flat__/__name@dasherize__.component.__style__.template', }), options.skipImport ? noop() : addRequiredModulesToModule(options) ]); } /** * Adds the required modules to the relative module. */ function addRequiredModulesToModule(options: Schema) { return (host: Tree) => { const modulePath = findModuleFromOptions(host, options)!; addModuleImportToModule(host, modulePath, 'NiInlineLoginFormModule', 'ng-itrunner/inline-login-form'); return host; }; }
調用ng generate建立組件時,buildComponent方法將自動替換路徑、文件名稱、文件內容中的變量,addModuleImportToModule方法添加NiInlineLoginFormModule到指定的module。
import {Schema as ComponentSchema} from '@schematics/angular/component/schema'; export interface Schema extends ComponentSchema { }
{ "$schema": "http://json-schema.org/schema", "id": "login", "title": "Login Component", "type": "object", "properties": { "path": { "type": "string", "format": "path", "description": "The path to create the component.", "visible": false }, "project": { "type": "string", "description": "The name of the project.", "$default": { "$source": "projectName" } }, "name": { "type": "string", "description": "The name of the component.", "$default": { "$source": "argv", "index": 0 }, "x-prompt": "What should be the name of the component?" }, "prefix": { "type": "string", "format": "html-selector", "description": "The prefix to apply to generated selectors.", "default": "app", "alias": "p" }, "style": { "description": "The file extension to be used for style files.", "type": "string" }, "skipImport": { "type": "boolean", "description": "Flag to skip the module import.", "default": false }, "module": { "type": "string", "description": "Allows specification of the declaring module.", "alias": "m" } }, "required": ["name"] }
運行Schematic
從新構建Schematic、發佈庫後便可完整的運行ng add ng-itrunner了:
ng add ng-itrunner Installing packages for tooling via npm. Installed packages for tooling via npm. ? Choose template to create project: blank UPDATE package.json (1614 bytes) √ Packages installed successfully. UPDATE src/app/app.component.html (362933 bytes)
啓動應用,效果以下:
固然,您也能夠運行ng g ng-itrunner:login,建立login組件。
Update Schematic能夠更新庫依賴,也能夠調整組件庫的變動。先看一下@angular/cdk/schematics提供的用於Update Schematic的一個重要函數createUpgradeRule():
/** * Creates a Angular schematic rule that runs the upgrade for the * specified target version. */ export function createUpgradeRule( targetVersion: TargetVersion, extraRules: NullableMigrationRule[], upgradeData: RuleUpgradeData, onMigrationCompleteFn?: PostMigrationFn): Rule { ... }
其中包含四個參數:
ng update
時可自動升級的Angular版本export interface RuleUpgradeData { attributeSelectors: VersionChanges<AttributeSelectorUpgradeData>; classNames: VersionChanges<ClassNameUpgradeData>; constructorChecks: VersionChanges<ConstructorChecksUpgradeData>; cssSelectors: VersionChanges<CssSelectorUpgradeData>; elementSelectors: VersionChanges<ElementSelectorUpgradeData>; inputNames: VersionChanges<InputNameUpgradeData>; methodCallChecks: VersionChanges<MethodCallUpgradeData>; outputNames: VersionChanges<OutputNameUpgradeData>; propertyNames: VersionChanges<PropertyNameUpgradeData>; }
建立Update Schematic
attribute-selectors.ts class-names.ts constructor-checks.ts css-selectors.ts element-selectors.ts index.ts input-names.ts method-call-checks.ts output-names.ts property-names.ts
文件內容以下:
import { AttributeSelectorUpgradeData, VersionChanges } from '@angular/cdk/schematics'; export const attributeSelectors: VersionChanges<AttributeSelectorUpgradeData> = {};
import { ClassNameUpgradeData, TargetVersion, VersionChanges } from '@angular/cdk/schematics'; export const classNames: VersionChanges<ClassNameUpgradeData> = { [ TargetVersion.V9 ]: [ ] };
這裏咱們定義了空的規則,升級時不會對項目作出更改。更詳細的規則定義方法能夠查看NG-ZORRO和Angular Material源碼。
import { RuleUpgradeData } from '@angular/cdk/schematics'; import { attributeSelectors, classNames, constructorChecks, cssSelectors, elementSelectors, inputNames, methodCallChecks, outputNames, propertyNames } from './data'; /** Upgrade data that will be used for the NG-iTRunner ng-update schematic. */ export const ruleUpgradeData: RuleUpgradeData = { attributeSelectors, classNames, constructorChecks, cssSelectors, elementSelectors, inputNames, methodCallChecks, outputNames, propertyNames };
import {Rule, SchematicContext} from '@angular-devkit/schematics'; import {createUpgradeRule, TargetVersion} from '@angular/cdk/schematics'; import {ruleUpgradeData} from './upgrade-data'; /** Entry point for the migration schematics with target of NG-iTRunner v9 */ export function updateToV9(): Rule { return createUpgradeRule(TargetVersion.V9, [], ruleUpgradeData, onMigrationComplete); } /** Function that will be called when the migration completed. */ function onMigrationComplete(context: SchematicContext, targetVersion: TargetVersion, hasFailures: boolean) { context.logger.info(''); context.logger.info(` ✓ Updated NG-iTRunner to ${targetVersion}`); context.logger.info(''); if (hasFailures) { context.logger.warn( ' ⚠ Some issues were detected but could not be fixed automatically. Please check the ' + 'output above and fix these issues manually.'); } }
{ "$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "migration-v9": { "version": "9.0.0", "description": "Updates NG-iTRunner to v9", "factory": "./ng-update/index#updateToV9" }, "ng-post-update": { "description": "Prints out results after ng-update.", "factory": "./ng-update/index#postUpdate", "private": true } } }
"ng-update": { "migrations": "./schematics/migration.json", "packageGroup": [ "ng-itrunner" ] }
運行
將庫ng-itrunner的版本號改成9.0.0,與Angular主版本號保持一致。而後,從新構建庫、schematic,發佈庫。
運行ng update:
ng update ng-itrunner Repository is not clean. Update changes will be mixed with pre-existing changes. Using package manager: 'npm' Collecting installed dependencies... Found 34 dependencies. Fetching dependency metadata from registry... Updating package.json with dependency ng-itrunner @ "9.0.0" (was "1.0.0")... UPDATE package.json (1614 bytes) √ Packages installed successfully. ** Executing migrations of package 'ng-itrunner' ** > Updates NG-iTRunner to v9 ✓ Updated NG-iTRunner to version 9 Migration completed.
總結: 本文主要參考了Angular官方文檔與NG-ZORRO和Angular Material源碼,介紹了庫與Schematic開發的基本過程。您要更深刻的學習,能夠查看NG-ZORRO、Angular Material源碼。