使用 Schematics 自定義 ng generate
來自專欄 NG-ZORRO
導言
- 本文將介紹哪些內容
- 什麼是 Schematics,以及它與 Angular CLI generate 的關係
- 如何創建一個 Schematics 項目,以及如何使用 Angular CLI 調用
- 創建一個自定義的 Component schematic
- 如何定義 ng-add 和 ng-update
介紹
Schematics 是什麼?
Schematics 是現代前端開發工作流的工具;它可以將變化應用到你的項目中。比如創建一個組件、添加配置項、將框架添加到現有項目,或者更新你的代碼來修復更新依賴時帶來的 break change。 Schematics?—?An Introduction
如何使用?
就如我們在 Angular 項目中常用的 ng generate component [name]
一樣,Angular6 允許三方庫自定義 Schematics,現在 NG-ZORRO 對其做了支持,比如你現在就可以在 Angular6 的項目中試試:
ng add ng-zorro-antd
將 ng-zorro-antd 添加到你的項目中ng g ng-zorro-antd:layout-top-side --name=[name]
創建一個帶布局的組件
Angular 是如何找到它的?
下面這張圖說明了當我們輸入 ng g ng-zorro-antd:schematic2
時,Angular 如何找到對應的 schematic。
其實就是 Angular CLI 幫我們調用了一下 Schematics,Schematics 自身也有 CLI 工具,換句話說你在非 Angular 項目中也可以使用,只要你願意你可以生成任何類似的東西。
起步
接下來我們來創建一個簡單的 scheamtics
首先需要安裝全局依賴
$ npm install -g @angular-devkit/schematics-cli
之後使用下面的命令新建一個 scheamtics 項目
$ schematics schematic --name my-schematics$ cd my-schematics$ npm install
這裡 Schematics CLI 以及為我們創建了一個簡單的 schematic,我們可以直接編譯運行。
編譯與調試
運行下面的命令編譯 ts 文件,並使用 npm link
將 schematics 鏈接到全局(這樣可以使我們在其它項目中使用未發布的包)
$ npm run build # 編譯 ts$ npm link # link 當前項目
使用 Angular CLI 運行
新建一個 Angular 項目,並且將剛才的 schematics link
入進來。
$ ng new schematics-test # 新建一個 Angular 項目$ cd schematics-test # 進入項目目錄$ npm link my-schematics # 將上一步的 schematics link 進來
之後使用 ng generate
命令來運行 schematics
$ ng g my-schematics:my-full-schematic --name hello
現在你應該可以看到 Angular 項目目錄多了幾個文件
我們已經知道如何在 Angular 項目中運行自定義 schematic 了,現在我們來創建一個生成 Angular 組件的 schematic
創建一個真正的 Component
因為官方 API 還處於未完成狀態,所以我們先將 material2/src/lib/schematics 下的 utils
文件下載拷貝到 my-schematics/src
下。
然後安裝一下新的依賴
$ npm install parse5 @schematics/angular --save-dev
接下來按照下面目錄結構新建一個名為 my-component
的 schematic。
.├── node_modules/├── src│ ├── utils│ ├── my-component│ │ ├── files│ │ │ └── __path__│ │ │ └── __name@dasherize@if-flat__│ │ │ ├── __name@dasherize__.component.__styleext__│ │ │ ├── __name@dasherize__.component.html│ │ │ ├── __name@dasherize__.component.spec.ts│ │ │ └── __name@dasherize__.component.ts│ │ ├── index.ts│ │ ├── schema.json│ │ └── schema.ts│ └── collection.json├── README.md├── package.json└── tsconfig.json
修改下列文件
- collection.json
collection.json
中包含了我們提供的每個 schematic 位置。
factory
欄位指向需要執行的方法 (一般來說是index.js
中默認導出的方法)schema
欄位指向一個 JSON Schema 格式的 JSON 文件aliases
這條命令的縮寫,比如ng g c [name]
{ "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "my-component": { "description": "Create a component", "factory": "./my-component/index", "schema": "./my-component/schema.json", "aliases": ["mc"] } }}
2. schema.json
該文件用於定義輸入項 (比如 --name
),以及一些參數的默認值。
{ "$schema": "http://json-schema.org/schema", "id": "MyComponent", "title": "My Component Options Schema", "type": "object", "properties": { "name": { "type": "string", "description": "The name of the component.", }, "prefix": { "type": "string", "format": "html-selector", "description": "The prefix to apply to generated selectors.", "alias": "p" }, "styleext": { "description": "The file extension to be used for style files.", "type": "string", "default": "css" }, "spec": { "type": "boolean", "description": "Specifies if a spec file is generated.", "default": true }, "flat": { "type": "boolean", "description": "Flag to indicate if a dir is created.", "default": false }, "selector": { "type": "string", "format": "html-selector", "description": "The selector to use for the component." } }, "required": [ "name" ]}
3. tsconfig.json
TyleScript 的編譯配置。
{ "compilerOptions": { "baseUrl": "tsconfig", "lib": [ "es2017", "dom" ], "module": "commonjs", "moduleResolution": "node", "noEmitOnError": false, "skipDefaultLibCheck": true, "skipLibCheck": true, "sourceMap": true, "target": "es6", "types": [ "jasmine", "node" ] }, "include": [ "src/**/*" ], "exclude": [ "src/**/*/files/**/*" ]}
4. index.ts
這裡的 buildComponent 是官方包 utils
中包含的,但是還沒有單獨發布,這裡是直接拷貝出來的。
import { chain, Rule } from @angular-devkit/schematics;import { Schema } from ./schema;import { buildComponent } from ../utils/devkit-utils/component;export default function (options: Schema): Rule { return chain([ buildComponent({ ...options }) ]);}
5. schema.ts
輸入項的 interface,這裡直接 extends Angular 組件的 interface。
import {Schema as ComponentSchema} from @schematics/angular/component/schema;export interface Schema extends ComponentSchema {}
模版文件
下面幾個文件奇怪的路徑、文件名、以及內容將會被 @angular-devkit/schematics 的 template<T>(options: T)
方法解析,將參數應用到模版內容及路徑。如果你願意的話你也可以直接實現一個模板系統。
1. __name@dasherize__.component.ts
import { Component } from @angular/core;@Component({ selector: <%= selector %>, templateUrl: ./<%= dasherize(name) %>.component.html, styleUrls: [./<%= dasherize(name) %>.component.<%= styleext %>]})export class <%= classify(name) %>Component {}
2. __name@dasherize__.component.html
<h2>My Component</h2>
3. __name@dasherize__.component.__styleext__
h2 { color: red;}
4. __name@dasherize__.component.spec.ts
import { fakeAsync, ComponentFixture, TestBed } from @angular/core/testing;import { <%= classify(name) %>Component } from ./<%= dasherize(name) %>.component;describe(<%= classify(name) %>Component, () => { let component: <%= classify(name) %>Component; let fixture: ComponentFixture<<%= classify(name) %>Component>; beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ declarations: [ <%= classify(name) %>Component ] }) .compileComponents(); fixture = TestBed.createComponent(<%= classify(name) %>Component); component = fixture.componentInstance; fixture.detectChanges(); })); it(should compile, () => { expect(component).toBeTruthy(); });});
修改完成後我們進行下一步。
編譯運行
現在運行 npm run build
編譯我們的 schematics。
然後切換到 Angular 項目中試一試下面的命令:
$ ng g my-schematics:mc --prefix app --styleext less --name test-component
不出意外的話你因該可以在控制台看到這樣的信息。
下面是我們傳入的幾個參數,它們在 schema.json
中被定義。
styleext
參數為樣式文件的擴展名prefix
參數為組件 selector 的前綴name
參數定義組件名
之後我們就可以在此基礎上定義一些布如局、表格、表單等常見的義務組合組件或者服務。
ng-add
作為 Angular6 新特性之一 ng add [package name]
,讓我們可以更方便的添加三方庫。庫開發者可以在此過程中為用戶進行必要的配置。
其實 ng add
也是一個 schematic,只是在 collection.json
中有一個固定的名稱 ng-add
,當用戶使用 ng add [package name]
時,Angular 會用這個固定的名稱找到對應的 schematic,就像這樣:
{ "$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "ng-add": { "description": "add NG-ZORRO", "factory": "./ng-add", "schema": "./ng-add/schema.json" } ... }}
在 ng-add
中我們可能要做這些事:
- 將添加依賴到
pagkage.json
- 配置用戶的
app.module.ts
- 配置用戶的
angular.json
在 utils
中已經有幾個現成的方法供我們使用,下面是從 ng-zorro-antd
中截取的幾段代碼。
值得注意的是這裡沒有使用模板系統,底層實現是通過 TypeScript API 對現有 ts 文件進行解析,然後更新現有文件。
/** 把 NG-ZORRO 添加到 package.json */function addZorroToPackageJson() { return (host: Tree) => { addPackageToPackageJson(host, dependencies, ng-zorro-antd, zorroVersion); return host; };}/** 把需要的 module 添加到 app.module */function addModulesToAppModule(options: Schema) { return (host: Tree) => { const workspace = getWorkspace(host); const project = getProjectFromWorkspace(workspace, options.project); addModuleImportToRootModule(host, BrowserAnimationsModule, @angular/platform-browser/animations, project); addModuleImportToRootModule(host, FormsModule, @angular/forms, project); addModuleImportToRootModule(host, HttpClientModule, @angular/common/http, project); addModuleImportToRootModule(host, NgZorroAntdModule.forRoot(), ng-zorro-antd, project); return host; };}
當你在一個 Angular 項目中運行 ng add ng-zorro-antd
時,你會看見下面的文件被更新了。
下面是一些其他庫 ng add
的 schematic:
- ng-alain
- material2
- ng-bootstrap
ng-update
與 ng-add
同樣是個 schematic,不同在於它不在是個固定的名稱,而是一個單獨的 collection 文件,因為會有多版本的情況存在。如果版本之間存在 break change 的話,庫開發者就可以修復這些 break change。
首先我們需要新建一個 collection 文件,命名為 migration.json
(名字不重要)
CLI 會根據本地的版本判斷執行哪個 schematic,如果相差多個版本會依次執行多個 schematic。
migration.json
{ "$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "migration-01": { "version": "0.7.0", "description": "Updates to 0.7.0", "factory": "./update/0_7_0" }, "migration-02": { "version": "1.0", "description": "Updates to 1.0", "factory": "./update/1_0" } }}
然後在 package.json
中指定文件位置。
{ "version": "0.0.0", "schematics": "./src/collection.json", "ng-update": { "migrations": "./src/migration.json" }}
之後在執行 ng update [package name]
時,Angular CLI 會根據當前版本執行對應的 schematic。例如當前版本是 0.7.x
就會執行 migration-02
升級到 1.0.x
;如果當前版本是 0.6.x
則會先執行 migration-01
再執行 migration-02
。
還可以使用 --form
--to
參數來制定升級的版本 ng update [package name] --to=1.0
。
結語
我們討論了 Schematics 的定義方法以及如何在 Angular CLI 中調用,順便也了解 ng-add
和 ng-update
。但只是一些很簡單的用法,你還可以使用 TypeScript API 以及 HTML 解析去實現更高級的 schematic,比如注入服務,在現有 HTML 中插入組件,插入的 Mock 數據等等...
本文沒有介紹 Schematics API 的相關用法,因為截止 v0.6.3
版本,Schematics 還處於未完成狀態,有機會會單獨介紹 Schematics API 以及 TypeScript API。謝謝大家!
參考
@angular-devkit/schematicsSchematics?—?An IntroductionYour first @angular/schematicsmanfredsteyer/schematics-book
推薦閱讀: