Schematics 跟檔案操作的元素有三種,分別是 Tree
、Rule
、Source
,但這三個元素到底是什麼? 又各自有哪些方法可以使用呢?
基本簡介
檔案系統的操作可以算是 Schematics 內核心的功能之一,也是大部分我們希望 schematics 幫我們處理的事情。所以熟悉 schematics 的檔案操作方式,在寫自己的 schematics 會輕鬆很多。
檔案系統操作
型別
- Tree: 是檔案系統的結構描述,包含檔案的狀態與改變檔案的規則 (
Rule
) - Source: 是一個建立空的新
Tree
,常見的方法有Url(path)
- Rule: 是描述要如何改變
Tree
,所以Rule
會回傳一個包含改變規則的Tree
方法
Tree
當第一次建立空的 schematics 時,會看到這一段程式碼
1 | export function blogdemo(options: any): Rule { |
這裡取得的 Tree
會是當下的檔案系統,所以會包含目前目錄下的所有檔案與資料夾,當然也可以針對那些檔案去做操作,可使用的方法有
branch(): Tree
:複製一份目前Tree
狀態的 Tree 物件merge(other: Tree, strategy?: MergeStrategy): void
:合併兩個Tree
物件,可決定合併衝突時的解決方式MergeStrategy
有以下選項AllowOverwriteConflict
AllowCreationConflict
AllowDeleteConflict
Default
Error
:如果2個檔案存在於相同的位置就會丟出錯誤訊息ContentOnly
:只有內容衝突時才可以被覆寫Overwrite
:包含AllowOverwriteConflict
、AllowCreationConflict
、AllowDeleteConflict
,根據最後的異動做覆蓋
root: DirEntry
(唯讀):取得目前的資料夾資訊 (FileSystemDirEntry
型別)read(path: string): Buffer | null
:讀取檔案並用 binary 的方式呈現exists(path: string): boolean
:檢查檔案是否存在get(path: string): FileEntry | null
:取得檔案 (FileEntry
型別)getDir(path: string): DirEntry
:取得某資料夾的資訊 (FileSystemDirEntry
型別)visit(visitor: FileVisitor): void
:拜訪目前 Tree 下的所有檔案 (list through folders)overwrite(path: string, content: Buffer | string): void
:複寫特定位置的檔案內容beginUpdate(path: string): UpdateRecorder
:開始修改某個檔案commitUpdate(record: UpdateRecorder): void
:確認修改內容,需與beginUpate
配合使用create(path: string, content: Buffer | string): void
:建立檔案並給予檔案內容delete(path: string): void
:刪除檔案rename(from: string, to: string): void
:重新命名檔案apply(action: Action, strategy?: MergeStrategy): void
:套用規則,但無法在HostTree
使用actions: Action[] (readonly)
:列出該Tree
目前所有的 actions,每一個 action 會有以下資訊kind
:動作種類c
:建立d
:刪除o
:複寫r
:重新命名
path
:路徑(from)to
:路徑 (to)content
:異動內容
基本上,屬於 Tree
型別的資料,都有上述的方法可以使用。在操作檔案系統時,Tree
是一個很重要的觀念,可以想像程他跟 Git 的 commit history 有雷同的運作方式。而所有的異動與規則,最終都得回到 Tree 上
Source
Source 如上頭所介紹的,是用來建立一個全新空的檔案系統,有以下的方法可以產生 Source
-
url(path: String)
1
2
3
4
5
6
7
8
9
10// src/blogdemo/index.ts
export function blogdemo(_options: any): Rule {
return (tree: Tree, context: SchematicContext) => {
// 根據 index.ts 的位置為出發點
const files = url('./')(context) as Tree;
// 列出該 Tree 下的所有檔案
files.visit(v => console.log(v));
return tree;
};
}執行結果
-
apply(source: Source, rules: Rule[])
:套用規則到Source
上,並回傳經處理後的Source
1
2
3
4
5
6
7
8
9
10export function blogdemo(_options: any): Rule {
return (tree: Tree, context: SchematicContext) => {
const filterRule = filter(x => x.endsWith('ts'));
const files = apply(url('./'), [filterRule])(context) as Observable<Tree>;
files.subscribe(tree => {
tree.visit(f => console.log(f));
});
return tree;
};
} -
source(tree: Tree)
:將Tree
轉換成Source
型別1
2
3
4
5
6
7
8
9
10
11
12export function blogdemo(_options: any): Rule {
return (tree: Tree, context: SchematicContext) => {
const filterRule = filter(x => x.endsWith('ts'));
const files = apply(source(tree), [filterRule])(context) as Observable<
Tree
>;
files.subscribe(tree => {
tree.visit(f => console.log(f));
});
return tree;
};
} -
empty()
: 回傳一個空的Tree
-
asSource (rule: Rule)
將規則轉換成source
這邊會發現我在 source
物件後面加上 (context)
,這個動作是將 source
型別進行處理並會回傳 Tree | Observable<Tree>
型別的資料,之後的操作就跟操作 Tree
是一模一樣的
1 | export type Source = (context: SchematicContext) => Tree | Observable<Tree>; |
Rule
chain(rules: Rule[]): Rule
: 將 Rule 串接在一起mergeWith(source: Source, strategy: MergeStrategy = MergeStrategy.Default): Rule
將source
與Tree
做合併 (直接修改)noop() : Rule
:回傳沒有任何動作的Rule
filter(predicate: FilePredicate<boolean>): Rule
:過濾規則branchAndMerge(rule: Rule, strategy = MergeStrategy.Default): Rule
:與目前的 Tree (複製) 合併並回傳一份新的 TreepartitionApplyMerge(predicate: FilePredicate<boolean>, ruleYes: Rule , ruleNo?: Rule): Rule
:根據條件執行對應的Rule
forEach(operator: FileOperator): Rule
:批次直型傳進的FileOperator
move(from: string, to?: string): Rule
:移動檔案至資料夾rename(match: FilePredicate<boolean>, to: FilePredicate<string>): Rule
:將符合條件的檔案更換名稱externalSchematic<OptionT extends object>(collectionName: string, schematicName: string, options: OptionT): Rule
:執行第三方 schematics 的命令schematic<OptionT extends object>(schematicName: string, options: OptionT): Rule
:執行其他的 schematics 命令template<T>(options: T): Rule
:樣板套用,包含檔案內容與檔名路徑的部分轉換pathTemplate<T extends PathTemplateData>(options: T): Rule
:轉換檔名路徑至對應的內容contentTemplate<T>(options: T): Rule
:轉換檔名內容的變數至對應的內容
1 | export type Rule = (tree: Tree, context: SchematicContext) => Tree | Observable<Tree> | Rule | void; |
總結
Tree
、source
與 Rule
間的關係其實很密切,將這三者的控制弄熟之後,就可以寫出功能很強大的 schematics,而不是單純的從別人的 schematics 複製貼上,卻不懂每一個動作的意義。