Angular 原理图 Schematics 学习 - 动手开发一个实际的例子
當(dāng) ng add 命令向項(xiàng)目中添加某個(gè)庫(kù)時(shí),就會(huì)運(yùn)行原理圖。ng generate 命令則會(huì)運(yùn)行原理圖,來(lái)創(chuàng)建應(yīng)用、庫(kù)和 Angular 代碼塊。
一些術(shù)語(yǔ):
規(guī)則
在原理圖 中,是指一個(gè)在文件樹(shù)上運(yùn)行的函數(shù),用于以指定方式創(chuàng)建、刪除或修改文件,并返回一個(gè)新的 Tree 對(duì)象。
文件樹(shù)
在 schematics 中,一個(gè)用 Tree 類表示的虛擬文件系統(tǒng)。 Schematic 規(guī)則以一個(gè) tree 對(duì)象作為輸入,對(duì)它們進(jìn)行操作,并且返回一個(gè)新的 tree 對(duì)象。
開(kāi)發(fā)人員可以創(chuàng)建下列三種原理圖:
- 安裝原理圖,以便 ng add 可以把你的庫(kù)添加到項(xiàng)目中。
- 生成原理圖,以便 ng generate 可以為項(xiàng)目中的已定義工件(組件,服務(wù),測(cè)試等)提供支持。
- 更新原理圖,以便 ng update 可以更新你的庫(kù)的依賴,并提供一些遷移來(lái)破壞新版本中的更改。
下面我們動(dòng)手做一個(gè)例子。
在庫(kù)的根文件夾中,創(chuàng)建一個(gè) schematics/ 文件夾。
在 schematics/ 文件夾中,為你的第一個(gè)原理圖創(chuàng)建一個(gè) ng-add/ 文件夾。
在 schematics/ 文件夾的根級(jí),創(chuàng)建一個(gè) collection.json 文件。
編輯 collection.json 文件來(lái)定義你的集合的初始模式定義。
如下圖所示:
collection.json 文件內(nèi)容如下:
{"$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json","schematics": {"ng-add": {"description": "Add my library to the project.","factory": "./ng-add/index#ngAdd"},"my-service": {"description": "Generate a service in the project.","factory": "./my-service/index#myService","schema": "./my-service/schema.json"}} }下圖高亮行的意思是:執(zhí)行 ng add 時(shí),調(diào)用文件夾 ng-add 下面的 index.ts 文件。
即這個(gè)文件:
我們需要在 my-lib 庫(kù)的根目錄下的 package.json 里,申明對(duì)上圖 collection.json 文件的引用:
ng add 命令的原理圖可以增強(qiáng)用戶的初始安裝過(guò)程。可以按如下步驟定義這種原理圖。
(1) 進(jìn)入 /schematics/ng-add/ 目錄。
(2) 創(chuàng)建主文件 index.ts。
(3) 打開(kāi) index.ts 并添加原理圖工廠函數(shù)的源代碼:
提供初始 ng add 支持所需的唯一步驟是使用 SchematicContext 來(lái)觸發(fā)安裝任務(wù)。該任務(wù)會(huì)借助用戶首選的包管理器將該庫(kù)添加到宿主項(xiàng)目的 package.json 配置文件中,并將其安裝到該項(xiàng)目的 node_modules 目錄下。
在這個(gè)例子中,該函數(shù)會(huì)接收當(dāng)前的 Tree 并返回它而不作任何修改。如果需要,你也可以在安裝軟件包時(shí)進(jìn)行額外的設(shè)置,例如生成文件、更新配置、或者庫(kù)所需的任何其它初始設(shè)置。
定義依賴類型
如果該庫(kù)應(yīng)該添加到 dependencies 中、devDepedencies 中,或者不用保存到項(xiàng)目的 package.json 配置文件中,請(qǐng)使用 ng-add 的 save 選項(xiàng)進(jìn)行配置
"ng-add": {"save": "devDependencies"}可能的值有:
- false - 不把此包添加到 package.json
- true - 把此包添加到 dependencies
- “dependencies” - 把此包添加到 dependencies
- “devDependencies” - 把此包添加到 devDependencies
構(gòu)建你的原理圖
必須首先構(gòu)建庫(kù)本身,然后再構(gòu)建 Schematics.
你的庫(kù)需要一個(gè)自定義的 Typescript 配置文件,里面帶有如何把原理圖編譯進(jìn)庫(kù)的發(fā)布版的一些指令。
要把這些原理圖添加到庫(kù)的發(fā)布包中,就要把這些腳本添加到該庫(kù)的 package.json 文件中。
假設(shè)你在 Angular 工作區(qū)中有一個(gè)庫(kù)項(xiàng)目 my-lib。要想告訴庫(kù)如何構(gòu)建原理圖,就要在生成的 tsconfig.lib.json 庫(kù)配置文件旁添加一個(gè) tsconfig.schematics.json 文件。
新建一個(gè) tsconfig.schematics.json 文件,維護(hù)如下的源代碼:
{"compilerOptions": {"baseUrl": ".","lib": ["es2018","dom"],"declaration": true,"module": "commonjs","moduleResolution": "node","noEmitOnError": true,"noFallthroughCasesInSwitch": true,"noImplicitAny": true,"noImplicitThis": true,"noUnusedParameters": true,"noUnusedLocals": true,"rootDir": "schematics","outDir": "../../dist/my-lib/schematics","skipDefaultLibCheck": true,"skipLibCheck": true,"sourceMap": true,"strictNullChecks": true,"target": "es6","types": ["jasmine","node"]},"include": ["schematics/**/*"],"exclude": ["schematics/*/files/**/*"] }rootDir 指出在你的 schematics/ 文件夾中包含要編譯的輸入文件,即下圖高亮的文件:
outDir 映射到了庫(kù)的輸出目錄下。默認(rèn)情況下,這是工作區(qū)根目錄下的 dist/my-lib 文件夾,即下圖這些文件:
要確保你的原理圖源文件會(huì)被編譯進(jìn)庫(kù)包中,請(qǐng)把下列腳本添加到庫(kù)項(xiàng)目的根文件夾(projects/my-lib)下的 package.json 文件中。
{"name": "my-lib","version": "0.0.1","scripts": {"build": "../../node_modules/.bin/tsc -p tsconfig.schematics.json","copy:schemas": "cp --parents schematics/*/schema.json ../../dist/my-lib/","copy:files": "cp --parents -p schematics/*/files/** ../../dist/my-lib/","copy:collection": "cp schematics/collection.json ../../dist/my-lib/schematics/collection.json","postbuild": "npm run copy:schemas && npm run copy:files && npm run copy:collection"},"peerDependencies": {"@angular/common": "^7.2.0","@angular/core": "^7.2.0"},"schematics": "./schematics/collection.json","ng-add": {"save": "devDependencies"} }build 腳本使用自定義的 tsconfig.schematics.json 文件來(lái)編譯你的原理圖。
copy:* 語(yǔ)句將已編譯的原理圖文件復(fù)制到庫(kù)的輸出目錄下的正確位置,以保持目錄的結(jié)構(gòu)。
postbuild 腳本會(huì)在 build 腳本完成后復(fù)制原理圖文件。
提供生成器支持
你可以把一個(gè)命名原理圖添加到集合中,讓你的用戶可以使用 ng generate 命令來(lái)創(chuàng)建你在庫(kù)中定義的工件。
我們假設(shè)你的庫(kù)定義了一項(xiàng)需要進(jìn)行某些設(shè)置的服務(wù) my-service。你希望用戶能夠用下面的 CLI 命令來(lái)生成它。
ng generate my-lib:my-service
首先,在 schematics 文件夾中新建一個(gè)子文件夾 my-service.
編輯一下 schematics/collection.json 文件,指向新的原理圖子文件夾,并附上一個(gè)指向模式文件的指針,該文件將會(huì)指定新原理圖的輸入。
進(jìn)入 /schematics/my-service/ 目錄。
創(chuàng)建一個(gè) schema.json 文件并定義該原理圖的可用選項(xiàng)。
每個(gè)選項(xiàng)都會(huì)把 key 與類型、描述和一個(gè)可選的別名關(guān)聯(lián)起來(lái)。該類型定義了你所期望的值的形態(tài),并在用戶請(qǐng)求你的原理圖給出用法幫助時(shí)顯示這份描述。
創(chuàng)建一個(gè) schema.ts 文件,并定義一個(gè)接口,用于存放 schema.json 文件中定義的各個(gè)選項(xiàng)的值。
export interface Schema {// The name of the service.name: string;// The path to create the service.path?: string;// The name of the project.project?: string; }name:你要為創(chuàng)建的這個(gè)服務(wù)指定的名稱。
path:覆蓋為原理圖提供的路徑。默認(rèn)情況下,路徑是基于當(dāng)前工作目錄的。
project:提供一個(gè)具體項(xiàng)目來(lái)運(yùn)行原理圖。在原理圖中,如果用戶沒(méi)有給出該選項(xiàng),你可以提供一個(gè)默認(rèn)值。
要把工件添加到項(xiàng)目中,你的原理圖就需要自己的模板文件。原理圖模板支持特殊的語(yǔ)法來(lái)執(zhí)行代碼和變量替換。
在 schematics/my-service/ 目錄下創(chuàng)建一個(gè) files/ 文件夾。
創(chuàng)建一個(gè)名叫 name@dasherize.service.ts.template 的文件,它定義了一個(gè)可以用來(lái)生成文件的模板。這里的模板會(huì)生成一個(gè)已把 Angular 的 HttpClient 注入到其構(gòu)造函數(shù)中的服務(wù)。
文件內(nèi)容如下:
// #docregion template import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http';@Injectable({providedIn: 'root' }) export class <%= classify(name) %>Service {constructor(private http: HttpClient) { } }classify 和 dasherize 方法是實(shí)用函數(shù),你的原理圖會(huì)用它們來(lái)轉(zhuǎn)換你的模板源碼和文件名。
name 是工廠函數(shù)提供的一個(gè)屬性。它與你在模式中定義的 name 是一樣的。
添加工廠函數(shù)
現(xiàn)在,你已經(jīng)有了基礎(chǔ)設(shè)施,可以開(kāi)始定義一個(gè) main 函數(shù)來(lái)執(zhí)行要對(duì)用戶項(xiàng)目做的各種修改了。
Schematics 框架提供了一個(gè)文件模板系統(tǒng),它支持路徑和內(nèi)容模板。系統(tǒng)會(huì)操作在這個(gè)輸入文件樹(shù)(Tree)中加載的文件內(nèi)或路徑中定義的占位符,用傳給 Rule 的值來(lái)填充它們。
關(guān)于這些數(shù)據(jù)結(jié)構(gòu)和語(yǔ)法的詳細(xì)信息,請(qǐng)參閱 Schematics 的 README。
創(chuàng)建主文件 index.ts 并為你的原理圖工廠函數(shù)添加源代碼。
首先,導(dǎo)入你需要的原理圖定義。Schematics 框架提供了許多實(shí)用函數(shù)來(lái)創(chuàng)建規(guī)則或在執(zhí)行原理圖時(shí)和使用規(guī)則。
代碼如下:
import {Rule, Tree, SchematicsException,apply, url, applyTemplates, move,chain, mergeWith } from '@angular-devkit/schematics';import { strings, normalize, virtualFs, workspaces } from '@angular-devkit/core';導(dǎo)入已定義的模式接口,使用別名重定義為 MyServiceSchema,它會(huì)為你的原理圖選項(xiàng)提供類型信息。
要想構(gòu)建 “生成器原理圖”,我們從一個(gè)空白的規(guī)則工廠開(kāi)始。
index.js 文件里:
export function myService(options: MyServiceSchema): Rule {return (tree: Tree) => {return tree;}; }這個(gè)規(guī)則工廠返回樹(shù)而不做任何修改。這些選項(xiàng)都是從 ng generate 命令傳過(guò)來(lái)的選項(xiàng)值。
定義一個(gè)生成器規(guī)則
我們現(xiàn)在有了一個(gè)框架,可用來(lái)創(chuàng)建一些真正修改用戶程序的代碼,以便對(duì)庫(kù)中定義的服務(wù)進(jìn)行設(shè)置。
用戶安裝過(guò)此庫(kù)的 Angular 工作區(qū)中會(huì)包含多個(gè)項(xiàng)目(應(yīng)用和庫(kù))。用戶可以在命令行中指定一個(gè)項(xiàng)目,也可以使用它的默認(rèn)值。在任何一種情況下,你的代碼都需要知道應(yīng)該在哪個(gè)項(xiàng)目上應(yīng)用此原理圖,這樣才能從該項(xiàng)目的配置中檢索信息。
你可以使用傳給工廠函數(shù)的 Tree 對(duì)象來(lái)做到這一點(diǎn)。通過(guò) Tree 的一些方法,你可以訪問(wèn)此工作區(qū)的完整文件樹(shù),以便在運(yùn)行原理圖時(shí)讀寫文件。
獲取項(xiàng)目配置
要確定目標(biāo)項(xiàng)目,可以使用 workspaces.readWorkspace 方法在工作區(qū)的根目錄下讀取工作區(qū)配置文件 angular.json 的內(nèi)容。要想使用 workspaces.readWorkspace,你要先從這個(gè) Tree 創(chuàng)建出一個(gè) workspaces.WorkspaceHost。 將以下代碼添加到工廠函數(shù)中。
function createHost(tree: Tree): workspaces.WorkspaceHost {return {async readFile(path: string): Promise<string> {const data = tree.read(path);if (!data) {throw new SchematicsException('File not found.');}return virtualFs.fileBufferToString(data);},async writeFile(path: string, data: string): Promise<void> {return tree.overwrite(path, data);},async isDirectory(path: string): Promise<boolean> {return !tree.exists(path) && tree.getDir(path).subfiles.length > 0;},async isFile(path: string): Promise<boolean> {return tree.exists(path);},}; }export function myService(options: MyServiceSchema): Rule {return async (tree: Tree) => {const host = createHost(tree);const { workspace } = await workspaces.readWorkspace('/', host);}; }workspaces 是從 @angular-devkit/core 導(dǎo)出的,readWorkspace 是其標(biāo)準(zhǔn)方法。該方法需要的第二個(gè)輸入?yún)?shù) host,是從另一個(gè)自定義函數(shù) createHost 返回的。
下面這行 default 邏輯處理:
if (!options.project) {options.project = workspace.extensions.defaultProject; }此 workspace.extensions 屬性中包含一個(gè) defaultProject 值,用來(lái)確定如果沒(méi)有提供該參數(shù),要使用哪個(gè)項(xiàng)目。如果 ng generate 命令中沒(méi)有明確指定任何項(xiàng)目,我們就會(huì)把它作為后備值。
有了項(xiàng)目名稱之后,用它來(lái)檢索指定項(xiàng)目的配置信息。
const project = workspace.projects.get(options.project); if (!project) {throw new SchematicsException(`Invalid project name: ${options.project}`); }const projectType = project.extensions.projectType === 'application' ? 'app' : 'lib';options.path 決定了應(yīng)用原理圖之后,要把原理圖模板文件移動(dòng)到的位置。
原理圖模式中的 path 選項(xiàng)默認(rèn)會(huì)替換為當(dāng)前工作目錄。如果未定義 path,就使用項(xiàng)目配置中的 sourceRoot 和 projectType 來(lái)確定。
邏輯體現(xiàn)在下面的代碼里:
if (options.path === undefined) {options.path = `${project.sourceRoot}/${projectType}`; }sourceRoot 在 angular.json 里定義:
定義規(guī)則
Rule 可以使用外部模板文件,對(duì)它們進(jìn)行轉(zhuǎn)換,并使用轉(zhuǎn)換后的模板返回另一個(gè) Rule 對(duì)象。你可以使用模板來(lái)生成原理圖所需的任意自定義文件。
將以下代碼添加到工廠函數(shù)中。
const templateSource = apply(url('./files'), [applyTemplates({classify: strings.classify,dasherize: strings.dasherize,name: options.name}),move(normalize(options.path as string)) ]);apply() 方法會(huì)把多個(gè)規(guī)則應(yīng)用到源碼中,并返回轉(zhuǎn)換后的源代碼。它需要兩個(gè)參數(shù),一個(gè)源代碼和一個(gè)規(guī)則數(shù)組。
url() 方法會(huì)從文件系統(tǒng)中相對(duì)于原理圖的路徑下讀取源文件。
applyTemplates() 方法會(huì)接收一個(gè)參數(shù),它的方法和屬性可用在原理圖模板和原理圖文件名上。它返回一條 Rule。你可以在這里定義 classify() 和 dasherize() 方法,以及 name 屬性。
classify() 方法接受一個(gè)值,并返回標(biāo)題格式(title case)的值。比如,如果提供的名字是 my service,它就會(huì)返回 MyService。Title case 和駝峰命名法類似,是一種變量拼寫規(guī)則。
dasherize() 方法接受一個(gè)值,并以中線分隔并小寫的形式返回值。比如,如果提供的名字是 MyService,它就會(huì)返回 “my-service” 的形式。
當(dāng)應(yīng)用了此原理圖之后,move 方法會(huì)把所提供的源文件移動(dòng)到目的地。所以,my service 被轉(zhuǎn)換為 MyService,進(jìn)而為 my-service.
規(guī)則工廠必須返回一條規(guī)則。
return chain([mergeWith(templateSource) ]);該 chain() 方法允許你把多個(gè)規(guī)則組合到一個(gè)規(guī)則中,這樣就可以在一個(gè)原理圖中執(zhí)行多個(gè)操作。這里你只是把模板規(guī)則和原理圖要執(zhí)行的代碼合并在一起。
至此這個(gè) Angular 庫(kù)的 Schematics 就開(kāi)發(fā)完畢了,請(qǐng)持續(xù)關(guān)注 Jerry 后續(xù)文章,我會(huì)介紹如何消費(fèi)這個(gè) Schematics.
更多Jerry的原創(chuàng)文章,盡在:“汪子熙”:
總結(jié)
以上是生活随笔為你收集整理的Angular 原理图 Schematics 学习 - 动手开发一个实际的例子的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 明日之后那些npc送护甲材料
- 下一篇: 计算机思维or人的思维