之前筆者在 Day12 的時候有說過, Angular 其實很貼心地幫我們準備了很多 API ,讓我們在開發 Schematics 的時候可以更輕鬆、簡單。
不過那些 API 其實比較適合在 Angular 的專案中使用,這時如果要開發 Schematics 給非 Angular 專案使用的話,就必須得好好地熟悉一下 TypeScript Compiler API ,而它又臭又長的程式碼也會給開發者帶來不少的麻煩與困擾。
這時,TSQurey 如英雄般地出現在筆者的面前!
聽到 TSQuery 這個名字有沒有種似曾相識的感覺?好想在哪邊聽過? jQuery?
沒錯!就像曾經的 jQuery 讓操作 DOM 變得像吃飯、喝水般地簡單一樣, TSQuery 也讓操作 AST 變得像吃飯、喝水般地一樣簡單。
究竟有沒有這麼神?讓我們繼續看下去。
如上圖所示,TSQuery 是基於 ESQuery 的 API 所開發出來的函式庫,而 ESQuery 又是基於 Esprima 所開發出來的函式庫,所以故事就要從 Esprima 是什麼開始講起。
Esprima 是一個高性能且符合標準的 ECMAScript 解析器,並使用 JavaScript 所撰寫。
關於 ECMAScript 與 JavaScript 的差別,可以參考以下文章:
ESQuery 是一個函式庫,讓開發者可以用類似 CSS Selector 的方式來取得 Esprima 所回傳的語法樹。
它們有一個 Demo 網頁可以玩,只要在這裡貼上任何一段 JavaScript :
在這裡輸入 selector 的語法:
這裡會顯示 selector 的資料結構:
並在這裡顯示 ESTree 格式的結果:
關於 ESQuery 的部份,筆者就大概介紹到這邊,想知道更多的邦友可以參考它們的 Github 。
從上面看到這邊之後,相信大家應該會知道 TSQuery 的用途跟目的。
簡單來說就是:
它能夠讓我們用類似 CSS Selector 的方式,找出我們所需要的 TypeScript AST 。
它也有個 TSQuery Playground 可以玩,作者是以色列的 GDE - Uri Shaked 大神。
介面長這樣:
基本上筆者覺得它跟 TypeScript AST Viewer 稍微有點類似,都是貼上程式碼之後直接顯示 AST 的結構,而且一樣可以在點擊程式碼或 AST 之後 Highlight 相對的 AST 與 程式碼隻位置。
不過它的 AST 只會顯示類別名稱,不會顯示更 Detail 的資料,而且多了一個可以輸入 Selector 的輸入框:
被 Query 到的區塊會被 Highlight 出來:
較特別的是,把 AST 展開之後,會顯示對應的 Selector 語法:
所以假設當我們想要取得 function f
的整個區塊時,只要把 Selector 改成 FunctionDeclaration[name.name=f]
,就可以選中它:
是不是超好用超方便的?!這簡直是神器阿!!!(跪)
使用方式非常簡單,只要輸入以下指令安裝:
npm install @phenomnomnominal/tsquery --save-dev
然後在程式碼裡引入 tsquery
:
import { tsquery } from '@phenomnomnominal/tsquery';
接著就可以像這樣子使用:
const typescript = `
class Animal {
constructor(public name: string) { }
move(distanceInMeters: number = 0) {
console.log(\`\${this.name} moved \${distanceInMeters}m.\`);
}
}
class Snake extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}
`;
const ast = tsquery.ast(typescript);
const nodes = tsquery(ast, 'Identifier[name="Animal"]');
console.log(nodes.length); // 2
常言道:「沒有比較,沒有傷害。」,所以我們來互相傷害比較看看,使用 TSQuery 之前與使用 TSQuery 之後的差異吧!
筆者擷取一段 Day11 的程式碼來讓大家近距離感受一下 TSQuery 的威力。
找到 AppComponent
的區塊:
使用 TypeScript Compiler API 時:
const sourceFile = ts.createSourceFile(
'test.ts',
text.toString(),
ts.ScriptTarget.Latest,
true
);
const classDeclaration = sourceFile.statements.find( node => ts.isClassDeclaration(node) )! as ts.ClassDeclaration;
const decorator = classDeclaration.decorators![0] as ts.Decorator;
const callExpression = decorator.expression as ts.CallExpression;
const objectLiteralExpression = callExpression.arguments[0] as ts.ObjectLiteralExpression;
const propertyAssignment = objectLiteralExpression.properties.find((property: ts.PropertyAssignment) => {
return (property.name as ts.Identifier).text === 'declarations'
})! as ts.PropertyAssignment;
const arrayLiteralExpression = propertyAssignment.initializer as ts.ArrayLiteralExpression;
const identifier = arrayLiteralExpression.elements[0] as ts.Identifier;
使用 TSQuery 時:
const sourceFile = tsquery.ast(text.toString());
const identifier = tsquery(sourceFile, 'PropertyAssignment[name.name="declarations"] Identifier:last-child')[0] as ts.Identifier;
TSQuery is the Winer!!
在開發給 Angular 專案所使用的 Schematics 時,有 Angular Schematics API 讓我們使用,但如果是開發給 React 、 Vue 甚至是其他專案所使用的 Schematics 時,有沒有使用 TSQuery 就會差很多了!所以務必要將它學起來!
不過雖然 TSQuery 非常好用,但筆者希望各位也別因此跳過 TypeScript Compiler API 的學習,正所謂:「萬丈高樓平地起。」,根基打得穩,在使用工具時才會更加得心應手。
此外,在使用 TSQuery 時,寫 CSS Selector 的功力也會影響到操作效能跟準確度,平常可以多加練習。