気になるもののコードは読むべし、ということで。
ちなみにコードを読み始めた時点でのバージョンは、`v3.23.2`です。
自分のコードリーディング用のメモ記事なので、他の人が読んでわかりやすいかは保証できません!
読みすすめ方
Svelteには、`runtime`と`compiler`のコードがある。
いずれはどっちにも目を通すつもりでいるけど、まずは`compiler`から読んでいく。
ただ`compiler`のコードをそのまま使うことはほぼないと思うので、実際にモジュールとしてどういう使われ方をするのかが知りたい場合は、Rollupなどバンドラーのプラグインのコードも見ることになりそう。
この記事ではコードの中身を1行ずつ(もちろん端折るけど)読んでいく。
svelte/compiler
まずOverviewで、APIとして`export`してるのはこんな感じ。
export { default as compile } from './compile/index'; export { default as parse } from './parse/index'; export { default as preprocess } from './preprocess/index'; export { walk } from 'estree-walker'; export const VERSION = '__VERSION__';
さすが、コンパイラっぽいですね。
ちなみにここまではドキュメントにも記載があって、TL;DRならこれで十分だったりもする・・!
それはさておき、この記事ではこの3つの関数の中身を追っていく。
- `compile()`
- `parse()`
- `preprocess()`
まずは名前から察して`preprocess()`から。
svelte.preprocess()
`preprocess`はその名の通り、事前に何かしらを処理するということ。
Svelteのコンテキストでいう`markup` / `style` / `script`の3つのパーツを、それぞれ事前に処理するということで、つまりはTypeScriptやPostCSSなどを使いたい場合に必要になる。
実際にどうやって導入するかみたいな話は、この記事では公式に丸投げする・・・。
GitHub - sveltejs/integrations: Ways to incorporate Svelte into your stack
というわけで、いざ`preprocess()`のコードへ。
compiler/preprocess/index.ts
https://github.com/sveltejs/svelte/blob/master/src/compiler/preprocess/index.ts
長さにして70行くらいで、関数のシグネチャーはこんな感じ。
function preprocess( source: string, preprocessor: PreprocessorGroup | PreprocessorGroup[], options?: { filename?: string } ): Promise<{ code: string, dependencies: string[], toString(): string, }> {}
- 引数の`source`は、`*.svelte`ファイルの中身そのままの文字列
- `preprocessor`は、バンドラーの設定から渡される`markup` / `script` / `style`それぞれの処理
- だいたいはそういうモジュールが渡ってくるはず
- これらは単一である必要はなく、同じターゲットに対して複数の処理をつなげることもできる
- `source`の中の3部位に対して、それぞれ渡された`preprocessor`の処理を通す
- それぞれの部位は、正規表現で切り出してる
- やってることはただの文字列変換だが、`replace_async()`という非同期に複数の変換を行う最適化が入ってる
- 変換後の`source`を、`Promise`で返す
ということをやってる。
`svelte-preprocess`に渡されるであろう野生のPreprocessorに必要なものを渡すための層という感じで、それ以上の情報はなさそう。
というわけで次。
svelte.parse()
`parse()`は、`*.svelte`ファイルをその名の通りパースして、ASTにして返す処理。
これは実は次に見ていく予定の`compile()`の中で使われてる関数で、バリデーションなどは行わず、単にASTに置換するだけのことをやるらしい。
compiler/parse/index.ts
https://github.com/sveltejs/svelte/blob/master/src/compiler/parse/index.ts
またもシグネチャーはこんな感じ。
function parse( template: string, options: ParserOptions = {} ): { html: TemplateNode, css: Style, instance: Script, module: Script, } {}
- 内部的に`Parser`というクラスを使ってて、処理の本体はそっちにある
- バリデーションは行わないって書いてあったけど、`style`部が複数あったりするとエラーにしたりはする
- 最終的に返すSvelteのASTは、4つのパーツから成る
- html: `markup`部
- css: `style`部
- instance: `script`部
- module: `script(context=module)`部 = Svelteの概念
Parserクラス
- パースした結果は自身のプロパティに溜め込んでいく
- `html`と`css`と`js`というプロパティに積んでいく
- まずはHTMLのフラグメントとして読みはじめる
- `script`と`style`に当たったら、それ用の特別な処理
- それ以外はいっしょくたに扱う
- Svelte独自の構文も当然ここで解析する
- `svelte:`要素とか
- `{#if}`の独自テンプレート構文とか
- 独自のバインディング属性とか
- ASTを歩くためのライブラリは以下
- 独自のHTMLパーサーでは独自の構文をさばく
- `Parser`のコンストラクタ内でパース処理はすべて終わってる
そしてこの4つのパートからなるASTを、次の`compile()`が処理するという流れ。