VOICEVOX のエディタは Electron・TypeScript・Vue・Vuex などが活用されており、全体構成がわかりにくくなっています。 ここではどういう構成になっているのかを紹介します。コードを読む上で参考になれば幸いです。
とにかくコードを読みたい方向けに、コードが実行される順番を簡潔に説明します。わかりやすさを優先したため正確な表現でない箇所もあることにご注意ください。
- electron がアプリケーションを起動します。このとき実行されるコードが
src/background.ts
です。 src/background.ts
から UI 表示のためのウィンドウを起動します。このとき実行されるコードがsrc/main.ts
です。main.ts
が Vue を使って UI がレンダリングされます。UI のルートコンポーネントがsrc/App.vue
です。src/App.vue
が様々な UI を呼び出します。src/views
やsrc/components
にそれぞれの UI があります。- UI のレンダリングと同時に、アプリの状態やロジックを管理する Vuex のストアが初期化されます。
src/store
ディレクトリに Vuex 用のコードがあります。 - これらのコードは全て TypeScript で書かれています。Vue や TypeScript のコードは実行・ビルド時に自動的に JavaScript へトランスパイルされます。
また electron は、最初に起動するプロセス(メインプロセス)と UI ウィンドウ用のプロセス(レンダラープロセス)で通信するために、プリロードスクリプトsrc/electron/preload.ts
を両方から読み込みます。
flowchart TB
subgraph Electron
direction TB
background.ts -.- preload.ts
end
background.ts --> main.ts
main.ts -.- preload.ts
main.ts --> Vue & Vuex
subgraph Vuex
direction TB
store/
end
subgraph Vue
direction TB
App.vue --> views/ & components/
end
ここでは Electron 周り・Vuex 周り・UI 周りの設計方針を簡単に紹介します。
TODO
VOICEVOX では動的に変更される状態と、状態を変更する関数を、Vuex のStore
を介して管理しています。
Store
は音声・エンジン・UI などそれぞれの目的に応じて分割し、src/store/index.ts
で統合しています。
Vuex の仕様を少し説明します。
状態はState
に保存し、状態の変更はMutation
で行います。
Mutation
は同期的に実行される必要がありますが、非同期処理を行いたい場合はAction
を使います。
VOICEVOX 内では、UI から実行するものはAction
を介するようにして、Action
内からMutation
を実行するようにしています。
これは後述する Undo/Redo 機構のための設計です。
関数は全て文字列を渡す形になっていますが、これも Vuex の仕様です。コードを追う際は文字列で全体検索を行うと便利です。
Vuex は型サポートが不十分なので、VOICEVOX では独自に型周りの機構を作っています(src/store/type.ts
)。
関数を定義したい場合は、まずtype.ts
に型を宣言し、対応するStore
内に実装します。
UI から Electron やエンジンにアクセスしたい場合は、必ず Vuex を介するようにしています。 これは Vuex の元の思想である Flux の考え方を引き継いでいて、外部にアクセスすることで生じる予想外な状態の変更などをなるべく一元管理するためです。
VOICEVOX では Vuex のMutation
をラップする形で Undo/Redo 機構を実現しています。
状態 A→B の変更を行った時、自動的にその逆方向 B→A の変更が Undo スタックに積まれます。
詳細はUndo/Redo を実装したい #116をご参照ください。
TODO
- src
- background.ts ・・・ Electron 版で最初に実行されるコード。ウィンドウを表示したり、エンジンを起動したりする。Electron のメインプロセス。
- main.ts ・・・ ウィンドウを表示するために最初に実行されるコード。ここで Vue や Vuex を組み込む。
- App.vue ・・・ Vue のルートになるコンポーネント。他の全てのコンポーネントの親。
- views ディレクトリ ・・・ 画面全体を覆うような Vue コンポーネントのディレクトリ。
- components ディレクトリ ・・・ UI のパーツになる Vue コンポーネントディレクトリ。
- 具体的にどの UI と対応しているかについては、UI 名称と Vue ファイル名の対応早見表 もご参照ください。
- store ディレクトリ ・・・ Vuex のストアのディレクトリ。アプリのロジックの大半はここに書かれる。
- electron ディレクトリ ・・・ Electron の ipc 通信などのコードが置かれるディレクトリ。
- browser ディレクトリ ・・・ WIP
- type ディレクトリ ・・・ TypeScript 用の型定義などが入るディレクトリ。
- styles ディレクトリ ・・・ CSS や SCSS などのディレクトリ。
- infrastructures ディレクトリ ・・・ UI 用のコードと UI 以外のコードを跨ぐときに一枚かませたいときのためのコードのディレクトリ。
- openapi ディレクトリ ・・・ エンジンの API を叩くためのコードのディレクトリ。OpenAPI で自動生成される。
- router ディレクトリ ・・・ Vue Router 用のディレクトリ。
- helpers ディレクトリ ・・・ 便利な関数を置くディレクトリ。
- shared ディレクトリ ・・・ UI と Electron 両方から参照されるコードを置くディレクトリ。
- public
- アプリのリソースファイルが置かれるディレクトリ。
- build
- ビルドに必要なファイルが置かれるディレクトリ。
Mermaid Flowを用いて書いています。mermaid のコードをサイトに貼り付ければ再編集も可能です。記述ルールは以下参照。
flowchart LR
817736>"シーケンスの開始"] --> 584587["関数"]
style 817736 fill:#ffbbbb,stroke:#ff0000
584587 --> 965553(["状態変数の変更"])
965553 --> 945949{{"分岐"}}
945949 -->|"条件1"| 366994["能動的な処理は単線\n(関数の実行など)"]
945949 -->|"条件2"| 871294["受動的な処理は点線\n(イベント発生など)"]
366994 --> 617606["大きい関数(subgraph)"]
871294 -.->|"発生条件"| 617606
572391[["外に定義した関数"]] --> 314405>"なんらかの状態"]
572391 --> 230853>"シーケンスの終了"]
style 230853 fill:#bbbbff,stroke:#0000ff
617606 --> 572391
572391 ~~~|"透明な線で結んでもよし"| 513532["外に定義した関数"]
subgraph 617606["大きい関数(subgraph)"]
776462["処理開始"] --> 992786["処理終了"]
end
subgraph 513532["外に定義した関数"]
327013["処理"]
end
処理の prefix 一覧
- back: background.ts の関数
- app: app のイベント
- win: win のイベント
- engine: EngineManager のメソッド
- store: electron-store のメソッド
- Vuex: Vuex の関数