Open22

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

kzk4043kzk4043

用語集

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

outputモード

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

遷移で見る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があればクライアントで実行
kzk4043kzk4043

コンポーネント

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を付与でき、クライアントでの実行が可能(カルーセルなど動的コンポや、状態を持ったコンポ)

kzk4043kzk4043

基本的な構造案

  1. src/pages配下=Astroファイル
  2. layout=Astroファイル
  3. それ以外の具体的コンポはすべてReact

1のファイル内においてセクション単位のイメージでReactコンポを配置して、必要であればclient directivesをつける

ほぼ画面全体がForm用のページなど、画面全体として値を保持する必要があるようなケースは最初から全体を1つのReactコンポとしてラップするのが便利そう。

設計として最初からページ全体をReactコンポとして囲うことも可能だが、アイランドアーキテクチャ的な最適化ができなくなるため、基本的にはセクション単位で配置するようにして、必要な場合のみ大きくReactで囲うのがよさそうか?

kzk4043kzk4043

Astroファイル

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

ちょっと困った点

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

Astroファイル中でReactコンポを配置する際の注意

Astroファイル内でReactコンポに対してシリアライズできないものをpropsとして渡せない

  1. 関数が渡せない
  2. ReactNodeが渡せない

基本的にはAstroファイル内でデータ以外を子コンポに渡せないと考えるのが吉。client directivesをつけようがつけまいが関係なく渡せない。たぶんビルドはできるがエラーを出しつつ要素としては無視されるみたいな挙動っぽい?

いろいろ試した結果 @stackblitz

kzk4043kzk4043

propsが無理ならchildrenでReactNodeを渡すことを考える。

AstroファイルでもReactコンポに対してChildrenは渡せるが、そのChildrenを操作せずそのまま表示する場合に限る。

例えば、

astroファイル
<ReactTestChildrenMap>
  <ReactTestStatic />
  <ReactTestStatic />
  <ReactTestStatic />
</ReactTestChildrenMap>

的な形でastro内で配列でChildrenを渡し、

ReactTestChildrenMapコンポ
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がそのまま渡されるだけだからだと思われる。

kzk4043kzk4043

関数を渡したり、ReactNodeを渡したり(何も加工せず表示するだけなら問題なし)する場合は、もう一つReactでラップしてあげる必要がありそう。Astro→Reactへの値の受け渡しはデータのみ、と考えておくのがよさそう。

これはめんどくさい反面、ある意味でJSの責務境界を明確化できるとも思う。特定領域内にJSを閉じ込めることが可能で、アイランドだなあという感じ。

kzk4043kzk4043

方針案

関数

  1. ラップするしかないのでセクション単位くらいでラッパーコンポを作る

ReactNode

  1. propsでは渡せないので諦める
  2. Reactコンポ内でなんの加工もせず表示するだけであれば、childrenで渡す
  3. 加工が必要な場合、ラッパーコンポを作ってその中で渡す
kzk4043kzk4043

詰まったところメモ

eslintがまだいまいちかも

そもそもastroプラグインがexperimental

eslint-plugin-importのルールがエラーを吐く

kzk4043kzk4043

astro checkがout of memoryで落ちていた

→distが対象になってしまっていたので、tsconfigのexcludeにdistを追加

kzk4043kzk4043

これで地味にハマった。

https://github.com/withastro/roadmap/discussions/819

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とかのコードをそのまま出力している→これは別問題かも

kzk4043kzk4043

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