企业应用中常定制一些通用的组件,提供统一的用户界面、数据呈现方式等,以便在不一样的应用中重复使用。能够将通用组件构建成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源码。