iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 27
1
Modern Web

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

[高效 Coding 術:Angular Schematics 實戰三十天] Day26 - Angular Schematics API List (三)

此篇介紹的 API 依序為:

  • ng-ast-utils
    • findBootstrapModuleCall
    • findBootstrapModulePath
    • getAppModulePath
  • ast-utils
    • insertImport
    • findNodes
    • getSourceNodes
    • findNode
    • insertAfterLastOccurrence
    • getContentOfKeyLiteral
    • getDecoratorMetadata
    • getFirstNgModuleName
    • getMetadataField
    • addSymbolToNgModuleMetadata
    • addDeclarationToModule
    • addImportToModule
    • addProviderToModule
    • addExportToModule
    • addBootstrapToModule
    • addEntryComponentToModule
    • isImported
    • getRouterModuleDeclaration
    • addRouteDeclarationToModule

ng-ast-utils

關於 Angular 專案裡的 main.ts 相關操作的小工具

引入路徑:

import { 
  findBootstrapModuleCall, 
  findBootstrapModulePath,
  getAppModulePath
} from '@schematics/angular/utility/ng-ast-utils';

findBootstrapModuleCall

用途:使用傳入的檔案樹main.ts 的路徑來取得 main.ts 裡, bootstrapModule 那一段的 TypeScript AST 節點的資料。

回傳值: main.ts 裡, bootstrapModule 那一段的 TypeScript AST 資料。

用法:

findBootstrapModuleCall(_tree, '/src/main.ts'); 
/*
platformBrowserDynamic().bootstrapModule(AppModule) 這段程式碼的 AST 節點的資料 ( CallExpression )
*/

原始碼

findBootstrapModulePath

用途:使用傳入的檔案樹main.ts 的路徑來取得初始啟動模組相對於 main.ts 的路徑(含檔名)。

回傳值:初始啟動模組相對於 main.ts 的路徑。

用法:

findBootstrapModulePath(_tree, '/src/main.ts'); // './app/app.module'

原始碼

getAppModulePath

用途:使用傳入的檔案樹main.ts 的路徑來取得初始啟動模組在專案中的路徑(含檔名與附檔名)。

回傳值:初始啟動模組在專案中的路徑(含檔名與附檔名)。

用法:

getAppModulePath(_tree, '/src/main.ts'); // '/src/app/app.module.ts'

原始碼


ast-utils

關於 TypeScript AST 相關操作的小工具(大部分都是 For Angular 用的)。

引入路徑:

import { 
  insertImport, 
  findNodes,
  getSourceNodes,
  findNode,
  insertAfterLastOccurrence,
  getContentOfKeyLiteral,
  getDecoratorMetadata,
  getFirstNgModuleName,
  getMetadataField,
  addSymbolToNgModuleMetadata,
  addDeclarationToModule,
  addImportToModule,
  addProviderToModule,
  addExportToModule,
  addBootstrapToModule,
  addEntryComponentToModule,
  isImported,
  getRouterModuleDeclaration,
  addRouteDeclarationToModule
} from '@schematics/angular/utility/ast-utils';

insertImport

用途:使用傳入的 要修改的檔案的 SourceFile要修改的檔案檔名要引入的識別符名稱要引入的識別符所在的檔案路徑是否為預設來取得插入 import { XXXX } from '/path/to/the/fileName 的資訊的資料( Change ,後續會說明 )。

回傳值:取得插入 import { XXXX } from '/path/to/the/fileName 所需的資訊的資料 ( Change )。

用法:

const path = '/src/app/app.module.ts';
const content = _tree.read(path)!.toString();
const sourceFile = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true)!;
const change = insertImport(sourceFile, path, 'RouterModule', '@angular/router')

console.log(change);
/*
InsertChange {
  path: '/src/app/app.module.ts',
  pos: 148,
  toAdd: ";\nimport { RouterModule } from '@angular/router'",
  description: 'Inserted ;\n' +
    "import { RouterModule } from '@angular/router' into position 148 of /src/app/app.module.ts",
  order: 148
}
*/

原始碼

findNodes

用途:根據傳入的 TypeSCript AST 節點資料要尋找的節點的類型最多尋找個數上限(沒傳入就找完為止)與如果傳入的節點資料就是要尋找的節點類型,是否要再繼續往下找(預設為否)來取得該節點下所有節點類型為要尋找的節點類型的節點資料。

回傳值:該節點下所有節點類型為要尋找的節點類型的節點資料。

用法:

const path = '/src/app/app.module.ts';
const content = _tree.read(path)!.toString();
const sourceFile = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true)!;
const nodes = findNodes(sourceFile, ts.SyntaxKind.ImportDeclaration);

原始碼

getSourceNodes

用途:根據傳入的 TypeSCript AST 根節點資料取得所有的節點資料(包含根節點自己)。

回傳值:所有的節點資料。

用法:

const path = '/src/app/app.module.ts';
const content = _tree.read(path)!.toString();
const sourceFile = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true)!;
const nodes = getSourceNodes(sourceFile);

原始碼

findNode

用途:根據傳入的 TypeSCript AST 節點資料要尋找的節點的類型要尋找的節點的文字來取得該節點的資料。

回傳值:特定節點的資料。

用法:

const path = '/src/app/app.module.ts';
const content = _tree.read(path)!.toString();
const sourceFile = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true)!;
const node = findNode(sourceFile, ts.SyntaxKind.ImportDeclaration, `import { AppComponent } from './app.component';`);

原始碼

insertAfterLastOccurrence

用途:根據傳入的 TypeSCript AST 節點資料(陣列)要插入的字串檔案名稱(含路徑)、插入位置節點類型(非必要)來取得用來插入變更的資訊的資料( Change )。

回傳值:用來插入變更的資訊的資料( Change )。

用法:

const path = './src/foo.ts';
const content = `const arr = ['foo'];`;
const sourceFile = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true)!;
const arrayNode = findNodes(
  sourceFile.getChildren().shift() as ts.Node,
  ts.SyntaxKind.ArrayLiteralExpression,
);
const elements = (arrayNode.pop() as ts.ArrayLiteralExpression).elements;

const change = insertAfterLastOccurrence(
  elements as unknown as ts.Node[],
  `, 'bar'`,
  path,
  elements.pos,
  ts.SyntaxKind.StringLiteral,
);

console.log(change);
/*
InsertChange {
  path: './src/foo.ts',
  pos: 18,
  toAdd: ", 'bar'",
  description: "Inserted , 'bar' into position 18 of ./src/foo.ts",
  order: 18
}
*/

原始碼

測試原始碼

getContentOfKeyLiteral

用途:根據傳入的 TypeSCript AST 根節點資料節點資料來取得該節點的字串

回傳值:某個節點的所代表的字串。

注意,如果傳入的節點資料的類型不是 Indentifier 或是 StringLiteral 的話,則此方法一定會回 null

用法:

const path = './src/foo.ts';
const content = `const arr = ['foo'];`;
const sourceFile = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true)!;
const arrayNode = findNodes(
  sourceFile.getChildren().shift() as ts.Node,
  ts.SyntaxKind.ArrayLiteralExpression,
);
const element = (arrayNode.pop() as ts.ArrayLiteralExpression).elements[0];

getContentOfKeyLiteral(sourceFile, element) // 'foo'

原始碼

getDecoratorMetadata

用途:根據傳入的 TypeSCript AST 根節點資料裝飾器的識別符名稱該裝飾器的引入路徑來取得該裝飾器函式內的所有參數的節點資料。

回傳值:特定裝飾器函式內的所有參數的節點資料(陣列)。

用法:

const path = '/src/app/app.module.ts';
const content = _tree.read(path)!.toString();
const sourceFile = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true)!;

getDecoratorMetadata(sourceFile, 'NgModule', '@angular/core');

原始碼

getFirstNgModuleName

用途:根據傳入的 TypeSCript AST 根節點資料來取得該檔案內第一個使用 NgModule 裝飾器函式的類別名稱。

回傳值:該檔案內第一個使用 NgModule 裝飾器函式的類別名稱。

用法:

const path = '/src/app/app.module.ts';
const content = _tree.read(path)!.toString();
const sourceFile = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true)!;

getFirstNgModuleName(sourceFile); // 'AppModule'

原始碼

getMetadataField

用途:根據傳入的 TypeSCript AST 節點資料欲取得的 Metadata 的 Key 值名稱來取得該 Key 值區塊的節點資料。

回傳值:Metadata 的 Key 值區塊的節點資料(陣列)。

用法:

const path = '/src/app/app.module.ts';
const content = _tree.read(path)!.toString();
const sourceFile = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true)!;
const metadata = getDecoratorMetadata(sourceFile, 'NgModule', '@angular/core')[0] as ts.ObjectLiteralExpression;

getMetadataField(metadata, 'imports');

原始碼

addSymbolToNgModuleMetadata

用途:根據傳入的 TypeSCript AST 根節點資料欲加入識別符的檔案路徑(含檔名)、欲加入識別符的 Metadata 的 Key 值名稱識別符名稱該識別符的引入路徑(非必填)來取得該變動所需資訊之資料( Change )。

回傳值:在 NgModule 的 Metadata 裡加入某個識別符的變動所需之資料( Change )(陣列)。

用法:

const path = '/src/app/app.module.ts';
const content = _tree.read(path)!.toString();
const sourceFile = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true)!;

addSymbolToNgModuleMetadata(sourceFile, path, 'imports', 'RouterModule', '@angular/router');

原始碼

測試原始碼

addDeclarationToModule

用途:根據傳入的 TypeSCript AST 根節點資料欲加入識別符的檔案路徑(含檔名)、欲加入的識別符名稱該識別符的引入路徑來將識別符加入到 NgModule 的 Metadata 的 declarations 裡(其實就是用上面的 addSymbolToNgModuleMetadata 來處理)。

回傳值:在 NgModule 的 Metadata 的 declarations 裡加入某個識別符的變動所需之資料( Change )(陣列)。

用法:

const path = '/src/app/app.module.ts';
const content = _tree.read(path)!.toString();
const sourceFile = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true)!;

addDeclarationToModule(sourceFile, path, 'HomeComponent', './home/home.component');

原始碼

測試原始碼

addImportToModule

用途:跟 addDeclarationToModule 雷同,就只是將原本要加在 declarations 換成加在 imports 而已。

回傳值:在 NgModule 的 Metadata 的 imports 裡加入某個識別符的變動所需之資料( Change )(陣列)。

用法:

const path = '/src/app/app.module.ts';
const content = _tree.read(path)!.toString();
const sourceFile = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true)!;

addImportToModule(sourceFile, path, 'RouterModule', '@angular/router');

原始碼

addProviderToModule

用途:跟 addDeclarationToModule 雷同,就只是將原本要加在 declarations 換成加在 porviders 而已。

回傳值:在 NgModule 的 Metadata 的 imports 裡加入某個識別符的變動所需之資料( Change )(陣列)。

用法:

const path = '/src/app/app.module.ts';
const content = _tree.read(path)!.toString();
const sourceFile = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true)!;

addProviderToModule(sourceFile, path, 'HomeService', './home/home.service');

原始碼

測試原始碼

addExportToModule

用途:跟 addDeclarationToModule 雷同,就只是將原本要加在 declarations 換成加在 exports 而已。

回傳值:在 NgModule 的 Metadata 的 imports 裡加入某個識別符的變動所需之資料( Change )(陣列)。

用法:

const path = '/src/app/app.module.ts';
const content = _tree.read(path)!.toString();
const sourceFile = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true)!;

addExportToModule(sourceFile, path, 'HomeService', './home/home.service');

原始碼

測試原始碼 - Section 1
測試原始碼 - Section 2

addBootstrapToModule

用途:跟 addDeclarationToModule 雷同,就只是將原本要加在 declarations 換成加在 bootstrap 而已。

回傳值:在 NgModule 的 Metadata 的 imports 裡加入某個識別符的變動所需之資料( Change )(陣列)。

用法:

const path = '/src/app/app.module.ts';
const content = _tree.read(path)!.toString();
const sourceFile = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true)!;

addBootstrapToModule(sourceFile, path, 'BppComponent', './bpp.component');

原始碼

addEntryComponentToModule

用途:跟 addDeclarationToModule 雷同,就只是將原本要加在 declarations 換成加在 entryComponents 而已。

回傳值:在 NgModule 的 Metadata 的 imports 裡加入某個識別符的變動所需之資料( Change )(陣列)。

用法:

const path = '/src/app/app.module.ts';
const content = _tree.read(path)!.toString();
const sourceFile = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true)!;

addEntryComponentToModule(sourceFile, path, 'BppComponent', './bpp.component');

原始碼

isImported

用途:根據傳入的 TypeSCript AST 根節點資料欲加入的識別符名稱該識別符的引入路徑來判斷該識別符有沒有被加入過。

回傳值: truefalse

用法:

const path = '/src/app/app.module.ts';
const content = _tree.read(path)!.toString();
const sourceFile = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true)!;

isImported(sourceFile, 'AppComponent', './app.component'); // true

原始碼

getRouterModuleDeclaration

用途:根據傳入的 TypeSCript AST 根節點資料找出引入的 RouterModule.forRoot() 或者是 RouterModule.forChild() 的節點資料。

回傳值:有找到時,回傳 RouterModule.forRoot() 或者是 RouterModule.forChild() 的節點資料;沒有找到則回傳 undefined

用法:

const path = '/src/app/app.module.ts';
const content = _tree.read(path)!.toString();
const sourceFile = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true)!;

getRouterModuleDeclaration(sourceFile)

原始碼

addRouteDeclarationToModule

用途:根據傳入的 TypeSCript AST 根節點資料要新增的檔案路徑要新增的路由配置字串來取得將配置自動加入到路由配置裡的變動所需之資料。

回傳值:取得將配置自動加入到路由配置裡的變動所需之資料。

用法:

const path = '/src/app/app.module.ts';
const content = _tree.read(path)!.toString();
const sourceFile = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true)!;
const route = `{ path: 'foo', component: FooComponent }`;

addRouteDeclarationToModule(sourceFile, path, route);

原始碼

測試原始碼


本日結語

ast-utils 有超多很好用的 API ,不過筆者寫到有點懷疑人生...。

參考資料


上一篇
[高效 Coding 術:Angular Schematics 實戰三十天] Day25 - Angular Schematics API List (二)
下一篇
[高效 Coding 術:Angular Schematics 實戰三十天] Day27 - Angular Schematics API List (四)
系列文
高效 Coding 術:Angular Schematics 實戰三十天32

尚未有邦友留言

立即登入留言