玩了幾天的 Schematics ,不曉得大家有沒有發現一個問題:為什麼當我們在 Angular 專案裡使用 ng generate
的指令時,Angular CLI
會知道我們要將產生出來的檔案放在哪裡?而且在一個 Angular 專案中,可能會有多個 project
,所以 ng generate
才有一個 --project
參數,用來指定要在哪個 project
裡新增檔案。
所以如果我們要在現有的 Angular 專案中使用自己客製的 Schematics ,就勢必要增加關於這個部分的支援。
事不宜遲,趕緊打開 schema.json
來新增 project
屬性:
{
"//": "略",
"properties": {
"//": "略",
"project": {
"type": "string",
"description": "Generate in specific Angular CLI workspace project"
}
},
"//": "略"
}
而隨著程式複雜度的提升與參數的增加,為了讓我們自己開發時可以更輕鬆且降低出錯的機率,我們可以在 /hello
資料夾裡新增一個 schema.d.ts
的檔案,把該 Schematic 會有哪些參數與各參數的資料型別都定義清楚:
export interface HelloSchema {
name: string;
project?: string;
}
既然都已經將參數的介面定義清楚了,後續只要我們有新增參數,就必須要維護這個介面檔,令它與 schema.json
檔裡的參數定義一致。
但同樣的東西要維護兩次實在是太麻煩也容易出錯,所以筆者要介紹一個非常好用的工具給大家,那就是 dtsgenerator , dtsgenerator
可以根據我們的 schema.json
自動產生對應的 schema.d.ts
,簡直是 Schematics 神器!!
用法非常簡單,只要安裝好 dtsgenerator
之後再輸入以下指令:
dtsgen src/hello/schema.json -o src/hello/schema.d.ts
或者可以像筆者一樣,不用事先安裝,只要將指令記錄在 package.json
的 scripts
裡,像是:
{
"//": "略",
"scripts": {
"//": "略",
"gen:schema": "npx -p dtsgenerator dtsgen src/hello-world/schema.json -o src/hello-world/schema.d.ts"
},
"//": "略"
}
然後要用的時候直接使用以下指令即可:
npm run gen:schema
小提醒:
dtsgenerator
所產出的schema.d.ts
,裡面介面名稱是根據schema.json
的id
解析出來的,使用時稍微留意一下即可。
整合的最後一步則是要修改程式碼,以解析出正確的檔案名稱與路徑,並將其產生出來的程式碼與檔案放到正確的位置:
import {
Rule,
SchematicContext,
Tree,
url,
apply,
template,
mergeWith,
SchematicsException,
move
} from '@angular-devkit/schematics';
import { strings } from '@angular-devkit/core';
// 需要先在終端機中輸入 'npm install @schematics/angular -S'
import { parseName } from '@schematics/angular/utility/parse-name';
import { buildDefaultPath } from '@schematics/angular/utility/project';
export function helloWorld(_options: HelloWorldSchema): Rule {
return (_tree: Tree, _context: SchematicContext) => {
// 讀取 angular.json ,如果沒有這個檔案表示該專案不是 Angular 專案
const workspaceConfigBuffer = _tree.read('angular.json');
if ( !workspaceConfigBuffer ) {
throw new SchematicsException('Not an Angular CLI workspace');
}
// 解析出專案的正確路徑與檔名
const workspaceConfig = JSON.parse(workspaceConfigBuffer.toString());
const projectName = _options.project || workspaceConfig.defaultProject;
const project = workspaceConfig.projects[projectName];
const defaultProjectPath = buildDefaultPath(project);
const parsePath = parseName(defaultProjectPath, _options.name);
const { name, path } = parsePath;
const sourceTemplates = url('./files');
const sourceParametrizedTemplates = apply(sourceTemplates, [
template({
..._options,
...strings,
name // 蓋過原本的 _options.name,避免使用錯誤的檔名
}),
move(path) // 將產生出來的檔案移到正確的目錄下
]);
return mergeWith(sourceParametrizedTemplates);
};
}
存檔並編譯完之後,就可以打開自己現有或新建的專案,輸入以下指令:
ng generate {自己客製的_Schematics_的專案路徑}/src/collection.json:hello-world 路徑/檔案名稱
例如像是:
ng generate ../hello-word/src/collection.json:hello-world feature/leo-chen
結果:
今天的原始碼在這:https://github.com/leochen0818/angular-schematics-30days-challenge/tree/day08
希望大家看了今天的文章之後會更想要試著自己寫寫看,不過要想寫出更好的 Schematics ,就絕對必須要學會如何寫 Schematics 的測試,因此也希望大家都能開始試著學寫測試並且逐漸習慣去寫測試。
明天又是撰寫測試的一天,敬請期待。
不好意思~目前我用的是angular13,其程式碼
import { buildDefaultPath } from '@schematics/angular/utility/project';
這行的buildDefaultPath好像已經移除了,要改用
import { buildDefaultPath } from '@schematics/angular/utility/workspace';
參考 : https://github.com/angular/angular-cli/issues/19619#issuecomment-744400354
程式碼只有那行不同,但在最後測試階段一直報錯,顯示
"Cannot read properties of undefined (reading 'projectType')"
不確定這樣我應該要做甚麼修改才能成功呢?
hi ryan851109,
這錯誤訊息看起來像是他在解析的時候遇到了 undefined exception ,可能是傳入的東西少了些什麼導致的@@
好的~我再試試看,如果我有試出來再來回報一下>﹏<
我也遇到了和你一样的问题,请问最后是怎么修复的呢?
为什么在新的Angular专案中执行ng g ../hello-world/src/collection.json:hello-world 'feature/Leo Chen'会报错“Cannot read properties of undefined(reading'sourceRoot')”
看起來是在讀東西的時候遇到了 undefined exception ,可以用 sourceRoot
這個關鍵字找一下要爬的東西是不是沒有這個設定或是改到別的地方去了
我的代码只有import { buildDefaultPath } from '@schematics/angular/utility/workspace';这个地方和文章中的不一样
這樣起來是他抓不到你的專案名稱,實際可能要看 code debug 喔@@
因為angular.json格式有調整,所以找不到defaultProject的參數
還好Leo大有留下參考文件
按照 Angular 官方文件(英文版) 去寫之後就可以成功了
不好意思造成大家困擾,畢竟文章有好一段日子了^^"