Astro.js入門:Next.js経験者が意識すべき点/実装してみての注意点メモ

Next.js経験者にとって意識した方が良さそうな点を羅列。軽い社内勉強会とかに使えるとよさそう。
その他Astro.jsについてのまとめ
- はじめてのAstro:最初に個人で触ったときのメモ
- Astro公式ドキュメント調査:導入時ドキュメント読み込みメモ
- Astro.js詰まったところ

用語集
- レンダリング方式(遷移を基準とする)
- SPA
- MPA→Astroはこっち
- output モード
- static
- hybrid
- server
- astroコンポーネント
- フロントマター(=コンポーネントスクリプト。ちょっと単語として一般的過ぎるので、フロントマタで統一したい)
- コードフェンス(
---
) - コンポーネントテンプレート(HTML部分。テンプレートとかHTMLテンプレとかでもいいかも)

Core Concepts

outputモード
- static
- SSG、事前ビルド
- デフォルトのモードで追加設定不要
- hybrid/server
- オンデマンドレンダリング、SSR
- SSRアダプタの追加が必要
- たぶん挙動は同じで
export const prerender
がデフォルトでtrueなのかfalseなのかの違い

遷移で見るSPA/MPA
- SPA
- history APIでの遷移
- GTM的に不利
- MPA→Astroはこっち
- aタグでの遷移
初期レンダリングで見るSPA/MPA
- SPA(CSR/SSR)
- サーバサイドではレンダリングなし(SSRの場合はHTMLのみ生成)
- Appと空のHTMLをクライアントに送信
- クライアントでレンダリング(SSRの場合は差分チェックをして差分がなければHydrationのみ)
- MPA(クラシックSSR)→Astro
- サーバサイドでレンダリング
- HTMLを送信
- jsがあればクライアントで実行

コンポーネント
astroコンポとそれ以外の違いの把握が大事そう。
.astro
ファイル
---
// フロントマター
// ここの処理はクラアントにそもそも送信されず、実行できない(client:loadつけても関係ない)
type Props {
name: string;
greeting?: string;
}
---(コードフェンス)
<!-- コンポーネントテンプレート (HTML + JS Expressions) -->
<!-- スクリプトタグを埋め込めばクライアントで実行できる(普通のHTMLと一緒) -->
// jsxじゃないので、classNameはclassなど
- <div className="box" dataValue="3" />
+ <div class="box" data-value="3" />
<!-- .astroファイルでは、HTMLのコメント構文が使えます -->
<!-- が、ブラウザDOMに含まれるので使わない -->
{/* JSのコメント構文も有効です */}
{/* こちらはブラウザDOMに含まれないのでこちらを使う */}
children
slotを使う
- 名前つきslotもある
- デフォルトのslot(slotが渡されなかったときのslot)を設定できる
client directives
astroファイルでしか使えないので注意
その他コンポファイル
有名UIライブラリは軒並み入れられる(以降reactの.tsx
ファイル前提で話を進める)
基本それぞれのUIライブラリが普通にそのまま使える。
当然だが、Next.jsの機能は使えないし、Astroコンポ用のAPIなども使えない。
Astroコンポ内でimportしてきたReactコンポにはclient directiveを付与でき、クライアントでの実行が可能(カルーセルなど動的コンポや、状態を持ったコンポ)

現状astroコンポはstorybookが使えないので、storybook使うならReactコンポにする必要がある。
containerAPIが入れば使える…?

基本的な構造案
- src/pages配下=Astroファイル
- layout=Astroファイル
- それ以外の具体的コンポはすべてReact
1のファイル内においてセクション単位のイメージでReactコンポを配置して、必要であればclient directivesをつける
ほぼ画面全体がForm用のページなど、画面全体として値を保持する必要があるようなケースは最初から全体を1つのReactコンポとしてラップするのが便利そう。
設計として最初からページ全体をReactコンポとして囲うことも可能だが、アイランドアーキテクチャ的な最適化ができなくなるため、基本的にはセクション単位で配置するようにして、必要な場合のみ大きくReactで囲うのがよさそうか?

Astroファイル
- CSS
-
<style is:global>
/:global()
は使用せず、グローバルスタイルは別ファイルに切り出す -
<div class:list={['box', { red: isRed }]}>
でclsxのように使える
-
- <Debug /> コンポーネントは必要であれば使う
-
<script>
/<style>
は基本つかわない- astroファイルはルーティング用コンポのみ
- scriptはフロントマターで充足しそう
- styleで必要そうな内容はBaseContainerで充足するはず
- フォーム系どうするか
- 全部Reactで囲う
- Nano stores?

ちょっと困った点
- もともと client:load 必要なかったが、あとから必要になった、みたいなケースの修正範囲が大きい。勝手には動くようにならない。
- 最も基本的なコンポでclient directivesが必要になった場合、最上流のAstroファイルで、最下流?の挙動を意識しないといけない
- 見た目静的っぽいが、細かいところで client:load 必要みたいなときに忘れそう

Astroへの移行手順
公式のNext.jsからの移行を一読。

eslint
npm install --save-dev eslint eslint-plugin-astro

Astroファイル中でReactコンポを配置する際の注意
Astroファイル内でReactコンポに対してシリアライズできないものをpropsとして渡せない。
- 関数が渡せない
- ReactNodeが渡せない
基本的にはAstroファイル内でデータ以外を子コンポに渡せないと考えるのが吉。client directivesをつけようがつけまいが関係なく渡せない。たぶんビルドはできるがエラーを出しつつ要素としては無視されるみたいな挙動っぽい?

propsが無理ならchildrenでReactNodeを渡すことを考える。
AstroファイルでもReactコンポに対してChildrenは渡せるが、そのChildrenを操作せずそのまま表示する場合に限る。
例えば、
<ReactTestChildrenMap>
<ReactTestStatic />
<ReactTestStatic />
<ReactTestStatic />
</ReactTestChildrenMap>
的な形でastro内で配列でChildrenを渡し、
import { Children } from 'react';
export const ReactTestChildrenMap = ({children}) => {
return Children.map(children, (child) => <div className="hoge">{child}</div>);
的なことをしようとした場合、通常のReactコンポであれば3つの要素としてmapできるが、Astroではchildrenは1つのHTMLとして扱われてしまい、想定通りにならない。Astro側でレンダリングされたHTMLがそのまま渡されるだけだからだと思われる。

関数を渡したり、ReactNodeを渡したり(何も加工せず表示するだけなら問題なし)する場合は、もう一つReactでラップしてあげる必要がありそう。Astro→Reactへの値の受け渡しはデータのみ、と考えておくのがよさそう。
これはめんどくさい反面、ある意味でJSの責務境界を明確化できるとも思う。特定領域内にJSを閉じ込めることが可能で、アイランドだなあという感じ。

方針案
関数
- ラップするしかないのでセクション単位くらいでラッパーコンポを作る
ReactNode
- propsでは渡せないので諦める
- Reactコンポ内でなんの加工もせず表示するだけであれば、childrenで渡す
- 加工が必要な場合、ラッパーコンポを作ってその中で渡す


csp>'unsafe-inline'いれなきゃいけない?

astro checkがout of memoryで落ちていた
→distが対象になってしまっていたので、tsconfigのexcludeにdistを追加

これで地味にハマった。
frontmatter内のtsがいまいちらしい。AIによると、
Astroコンパイラの実装
現在:
- Go言語で書かれたカスタムパーサー
- Go エコシステムには成熟したモダンJavaScript/TypeScriptパーサーがないため、独自実装
課題:
- Astroチームも現在のTypeScript解析に限界があることを認識
- 特にフロントマターのTypeScriptコード解析で問題
検討されている解決策
Astroチームは以下のアプローチを検討中:
1. ESBuildのTypeScriptパーサー: ESBuildのCSSパーサーをフォークしたのと同様に、TypeScrip
tパーサーもフォークすることを検討
2. Microsoft TypeScript-Go: github.com/microsoft/typescript-go
を試験的に使用し、良好な結果を得ている
3. Go エコシステムの限界: Go
には成熟したモダンJS/TSパーサーがほとんどない(多くがES2015レベル)
tsのtargetへのトランスパイルもうまくいってない気がする…
target: es5なのにes2023とかのコードをそのまま出力している→これは別問題かも

frontmatter内で return
するとコンポ非表示だが、 return null
だとエラー
