これの続きであり、最後の記事です。
2週間ちょいでバージョンが`1.0.0-beta.27`から、`1.0.0-beta.33`になり、この時点でのハッシュは`51db2b9b4efd899bdd7efc481a5f226b3b040614`でした。
まもなくメジャーバージョンがリリースされることもあり、これが最後のコードリーディング。
残してきたビルドコマンドまわりと、Partial Hydrationの仕組みまわりを読む。
astro build
CLIの`build`コマンドを見ていく。
このコマンドは、SSGとして成果物をディスクに書き出す処理、もしくは、SSRするためのランタイムを生成する処理になる。
AstroBuilder
- ビルドコマンドの実体
- 以前にも見たポリフィルをロードしたあとは、これに丸投げ
- コンストラクタ
- 以前にみた`RouteManifest`の生成
- `run()`
- `setup()`からの`build()`
- `setup()`
- 以前にみた`createVite()`から、Viteの設定オブジェクトを用意
- それを使ってViteのサーバーを立てる
- `build()`
- `collectPagesData()`: ページとそのアセットのリストを返す
- `staticBuild()`: ページを生成する
- アセットの書き出し
各所でインテグレーションのためにフックを呼んでたり、メトリクスをログに出したりはしてるけど、基本的にはこれだけ。ビルドでもViteを酷使していくスタイル。
collectPagesData()
- `RouteManifest`を頼りに、すべてのページ(コンポーネント)を探す
- コンポーネントは、以前に見た`preload()`によってSSRする
- `dev`ではリクエストに応じてやってたこと
- `build`では一括でやってる
- `getStaticPaths()`が存在してれば、その個別ページも同様にやる
- `paginate()`の設定の単位でよしなに
ちなみに返り値は、`allPages`と`assets`という2つのオブジェクトで、どっちもファイル名がキーになってた。
staticBuild()
- ページの情報を回収
- `.astro`で定義されたやつ
- `client:`のディレクティブの有無の確認とか
- hoistedされるものを含め、JSが必要なものの確認も
- `clientBuild()`
- `ssrBuild()`
- 本命のプロジェクト自体のビルド
- ただし、まだ静的なファイルを生成するわけではない
- Rollupのビルドアウトプットのチャンク集ができるまで
- こっちもビルド専用のViteの設定オブジェクトを用意して、`vite.build()`を呼ぶ
- `@astro/plugin-build-pages`がここで登場
- SSRされたモジュールのエントリーポイント用のテンプレ
- `generatePages()`
- `generatePath()`
- `dev`コマンド時にもやってた`render()`がここで呼ばれてる
- ただし結果はディスクに書き出されるという違いがある
- これでやっと最終的なHTMLができる
- あとはビルドに使った一時ファイルを削除して終わり
長かった。
Partial Hydrationまわり
Astroの存在意義でもあるこの部分を読まずには終われまい。
- `.astro`内部で`import`してる別のコンポーネントを評価するのは、`preload()`でViteにSSRされるとき
- `$$metadata`にそういうデータが詰まってる
- `client:idle`などの`hydrationDirectives`も、この時点で判別される
- そして`client:only`には特別なマーキング
- `client:*`のディレクティブがあった = クライアントサイドでJSが必要ということ
- なので`clientBuild()`時に、それ用のコードが出力される
- `ssrBuild()`の各ページごとに`topLevelImports`を見つけるところで、挿入される
- `client:idle`は、`astro/client/idle.js`のような命名ルールでランタイムのファイルが対応する
- これらは別のディレクトリにある
- ハイドレーションのスクリプト
- 基本的にはよしななタイミングで`innerHTML`を更新したり関数を呼ぶだけ
- `requestIdleCallback`や`IntersectionObserver`などにあわせて
- `.svelte`などのUIフレームワークのコードももちろんViteでビルドする
- インテグレーションを追加すると、Viteのプラグインリストに入ってくる
- そして`clientBuild()`の時に使われる
なんとな〜く、わかったかな。
まとめ
- URLに対応する`pages`をまとめたマニフェストをまず作る
- それらルートのエントリーとなるコンポーネントをロードしていく
- Viteで`ssrLoadModule()`する`preload()`が最初の関門
- そのためのViteプラグインたち
- 数多のフォーマットを、いったんJSにまとめるのがポイント
- `.astro`をJS表現にして by WASM
- `.svelte|vue|jsx`も全部JS表現にして
- 依存関係やらCSSなどのアセットの相関をまとめあげ
- `render()`でそのコンポーネント関数を実行して、HTMLを吐かせる
- `dev`コマンドではそれをリクエスト時に実行して返す
- `build`コマンドでは一括でやって書き出す
ViteでビルドしたものをViteでビルドしてViteでビルドする!って感じ。
SSRモードでビルドする実行パスは追えず終いだった。(つかれた)
ただしSSRモード、コードとしても後付した感がすごいので、v2に向けてコードベースをリライトしたりしそうやなって思った。
それなりに大変だったけど、Nodeのツールなおかげで実行時にもだいたい難読化されてたりしないし、デバッガー使ってコードリーディングができて楽でいいですね。
(実際にAstroでサイトを作ってみてる感想としては、動いてるし方向性としても間違いないけど、DXとしては正直まだまだと思ってるので、あと数日でほんとにメジャーバージョン出すの?って思ってたら、7月後半に延期するって記事が出てた!)