pluginIsland

ページの一部を React App 化するプラグイン。Astro の Island を再現しています。

Table of Contents

How To Use

動作させるには以下パッケージのいずれが必要です。

  • @vitejs/plugin-react
  • @vitejs/plugin-react-swc

これによって Island の JavaScript に React の import が自動挿入され、同時に開発モードで Island 内の HMR が効くようになります。

$ npm install --save-dev @vitejs/plugin-react
./minista.config.js
import { pluginIsland } from "minista"
import react from "@vitejs/plugin-react"

export default {
  plugins: [pluginIsland(), react()],
}
./src/pages/index.jsx
import { Counter } from "../components/counter"

export default function () {
  return <Counter client:load />
}

Options

Default
pluginIsland({
  useSplitPages: true,
  outName: "island-[index]",
  rootAttrName: "island",
  rootDOMElement: "div",
  rootStyle: { display: "contents" },
})

useSplitPages

  • 型: boolean
  • デフォルト: true

ページ毎に必要な JavaScript を分割して読み込みます。

outName

  • 型: string
  • デフォルト: "island-[index]"

出力ファイル名。拡張子は含みません。以下の動的出力タグを使用できます。

  • [index]: 生成された順番(開始: 1)

rootAttrName

  • 型: string
  • デフォルト: "island"

ハイドレーションを行うルート要素のデータ属性名に使われる名前。

  • 例: data-island-client-directive

rootDOMElement

  • 型: "div" | "span"
  • デフォルト: "div"

ハイドレーションを行うルート要素の HTML タグ。

rootStyle

  • 型: React.CSSProperties
  • デフォルト: { display: "contents" }

ハイドレーションを行うルート要素に付与するスタイル。

Client Island

minista は React の JSX テンプレートを 静的な HTML に変換し、クライアントサイドの JavaScript を取り除きます。

import { Counter } from "../components/counter"

export default function () {
  return <Counter />
}

しかし、UI コンポーネントをインタラクティブにしたいシーンもあります。その場合は client:* ディレクティブを付与してください。minista はその部分だけの JavaScript を生成してハイドレーションします。client:* ディレクティブの種類と動作は次項を参照ください。

import { Counter } from "../components/counter"

export default function () {
  return <Counter client:load />
}

※ハイドレーションを行うコンポーネン配下で読み込んだ画像パスはルートパス・または絶対パスとなり、埋め込み用の相対パスは反映されません

Client Directive

静的な HTML に変換される JSX の一部を React App として動作させる効果があります。client:* ディレクティブ配下では React の useState useEffect などインタラクティブな実装がすべて使えます。client:* ディレクティブは React Components または HTML タグに付与でき、同時に props を渡すことも可能です。

client:load

ページの読み込みと同時に、コンポーネントをハイドレーションします。

import { Counter } from "../components/counter"

export default function () {
  return <Counter client:load />
}

client:idle

ページの初期読み込みが終わり、ブラウザが待機状態になったときにコンポーネントをハイドレーションします。

import { Counter } from "../components/counter"

export default function () {
  return <Counter client:idle />
}
  • timeout: 初期読み込みがこの時間に到達した場合にハイドレーションを優先して実行
    • 実装: requestIdleCallbacktimeout
import { Counter } from "../components/counter"

export default function () {
  return <Counter client:idle={{ timeout: 5000 }} />
}

client:visible

コンポーネントが画面内に表示されたときにハイドレーションします。

import { Counter } from "../components/counter"

export default function () {
  return <Counter client:visible />
}
  • rootMargin: コンポーネントの認識範囲を拡大
import { Counter } from "../components/counter"

export default function () {
  return <Counter client:visible={{ rootMargin: "200px" }} />
}

client:media

CSS メディアクエリの条件が満たされたときに、コンポーネントをハイドレーションします。

import { Counter } from "../components/counter"

export default function () {
  return <Counter client:media="(max-width: 500px)" />
}

client:only

静的 HTML を出力せずに、ReactDOM.createRoot でコンポーネントをレンダリングします。タイミングは client:load と同じくページの読み込みと同時。静的な HTML を初期表示させたくない場合に有効ですが、レンダリング前の高さを確保できないため Cumulative Layout Shift(CLS)による画面のガタつきに注意が必要です。

import { Counter } from "../components/counter"

export default function () {
  return <Counter client:only />
}
  • slot="fallback": 直下要素に付与することでレンダリング前に表示
import { Counter } from "../components/counter"

export default function () {
  return (
    <div client:only>
      <div slot="fallback">Loading...</div>
      <Counter />
    </div>
  )
}