iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 9
2
Modern Web

高效 Coding 術:Angular Schematics 實戰三十天系列 第 9

[高效 Coding 術:Angular Schematics 實戰三十天] Day08 - 增加對 Angular 專案的支援

玩了幾天的 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 檔裡的參數定義一致。

但同樣的東西要維護兩次實在是太麻煩也容易出錯,所以筆者要介紹一個非常好用的工具給大家,那就是 dtsgeneratordtsgenerator 可以根據我們的 schema.json 自動產生對應的 schema.d.ts ,簡直是 Schematics 神器!!

用法非常簡單,只要安裝好 dtsgenerator 之後再輸入以下指令:

dtsgen src/hello/schema.json -o src/hello/schema.d.ts

或者可以像筆者一樣,不用事先安裝,只要將指令記錄在 package.jsonscripts 裡,像是:

{
  "//": "略",
  "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.jsonid 解析出來的,使用時稍微留意一下即可。

調整程式碼

整合的最後一步則是要修改程式碼,以解析出正確的檔案名稱與路徑,並將其產生出來的程式碼與檔案放到正確的位置:

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);
  };
}

在 Angular 專案中使用

存檔並編譯完之後,就可以打開自己現有或新建的專案,輸入以下指令:

ng generate {自己客製的_Schematics_的專案路徑}/src/collection.json:hello-world 路徑/檔案名稱

例如像是:

ng generate ../hello-word/src/collection.json:hello-world feature/leo-chen

結果:

Imgur

本日結語

今天的原始碼在這:https://github.com/leochen0818/angular-schematics-30days-challenge/tree/day08

希望大家看了今天的文章之後會更想要試著自己寫寫看,不過要想寫出更好的 Schematics ,就絕對必須要學會如何寫 Schematics 的測試,因此也希望大家都能開始試著學寫測試並且逐漸習慣去寫測試。

明天又是撰寫測試的一天,敬請期待。

參考資料


上一篇
[高效 Coding 術:Angular Schematics 實戰三十天] Day07 - 如何測試使用範本的 Schematic
下一篇
[高效 Coding 術:Angular Schematics 實戰三十天] Day09 - 撰寫支援 Angular 專案的 Schematics 的測試程式
系列文
高效 Coding 術:Angular Schematics 實戰三十天32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
0
ryan851109
iT邦新手 5 級 ‧ 2022-02-22 18:37:56

不好意思~目前我用的是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')"
不確定這樣我應該要做甚麼修改才能成功呢?

看更多先前的回應...收起先前的回應...
Leo iT邦新手 3 級 ‧ 2022-02-24 16:03:14 檢舉

hi ryan851109,

這錯誤訊息看起來像是他在解析的時候遇到了 undefined exception ,可能是傳入的東西少了些什麼導致的@@

好的~我再試試看,如果我有試出來再來回報一下>﹏<

我也遇到了和你一样的问题,请问最后是怎么修复的呢?

Moose iT邦新手 5 級 ‧ 2023-09-11 19:55:19 檢舉

2023來更新一下, 在操作時也碰到了上述的問題,
後來參考此篇的說明後,
發現utility包的內容算是私有的類.

筆者上述的內容是為了擷取實際要寫入檔案的路徑,
可以透過 @angular-devkit/core包裡的workspaces
來取得sourceRootprojectType.
詳細內容可以參考angular的文章

最後有發現angular官網示例中,使用了applyTemplates去載入optionsstrings但實際操作時,__name@dasherize__的內容沒有被替代, 仍需使用template的方法才能正常運作.

0
xiaote0225
iT邦新手 5 級 ‧ 2022-08-16 16:26:35

为什么在新的Angular专案中执行ng g ../hello-world/src/collection.json:hello-world 'feature/Leo Chen'会报错“Cannot read properties of undefined(reading'sourceRoot')”

看更多先前的回應...收起先前的回應...
Leo iT邦新手 3 級 ‧ 2022-08-16 16:30:48 檢舉

看起來是在讀東西的時候遇到了 undefined exception ,可以用 sourceRoot 這個關鍵字找一下要爬的東西是不是沒有這個設定或是改到別的地方去了

我的代码只有import { buildDefaultPath } from '@schematics/angular/utility/workspace';这个地方和文章中的不一样

https://ithelp.ithome.com.tw/upload/images/20220816/20133844DBOQOlDMB4.png

Leo iT邦新手 3 級 ‧ 2022-08-16 23:58:38 檢舉

這樣起來是他抓不到你的專案名稱,實際可能要看 code debug 喔@@

0
fu05277
iT邦新手 5 級 ‧ 2022-09-01 16:57:20

因為angular.json格式有調整,所以找不到defaultProject的參數
還好Leo大有留下參考文件
按照 Angular 官方文件(英文版) 去寫之後就可以成功了

Leo iT邦新手 3 級 ‧ 2023-09-18 16:22:24 檢舉

不好意思造成大家困擾,畢竟文章有好一段日子了^^"

我要留言

立即登入留言