HonoX、便利ですよね。 Next.js ほど巨大なフレームワークを使いたくない場面などで、セットアップも簡単、さっと初めてさっとデプロイできるという点で大活躍です。
そんな HonoX ですが、 vite.config.ts
に設定を加えることで MDX もルーティングに使用することが出来ます。
例えばこのように設定することで、 localhost:5173/foo
にアクセスするとコンテンツが表示されます。
// vite.config.ts import build from "@hono/vite-build/cloudflare-pages"; import adapter from "@hono/vite-dev-server/cloudflare"; import honox from "honox/vite"; import { defineConfig } from "vite"; // この辺を追加 import mdx from "@mdx-js/rollup'"; import remarkFrontmatter from "remark-frontmatter"; import remarkMdxFrontmatter from "remark-mdx-frontmatter"; const jsxImportSource = "hono/jsx"; export default defineConfig({ plugins: [ honox({ devServer: { adapter } }), mdx({ jsxImportSource, remarkPlugins: [remarkFrontmatter, remarkMdxFrontmatter], }), build(), ], });
<!-- app/routes/foo.mdx --> # Hello from MDX It works!
こんな感じ。
便利ですね。ところで、せっかくの MDX なので React コンポーネントを動かしたいですよね。さらに言えば Island コンポーネントとして作成して必要な時だけ JS がダウンロードされて欲しいですよね。 しかしながら、上記設定では、例えば次のような Island コンポーネントを作成、インポートしたとしても、デプロイすると動かなくなります。
// app/islands/counter.tsx import { useState } from 'hono/jsx' export default function Counter() { const [count, setCount] = useState(0) return ( <div> <p>{count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ) }
<!-- app/routes/counter.mdx --> import Counter from "../../islands/counter"; # Hello from MDX <Counter />
これでは困りますね。ということでなんとかしましょう!というのが今回の記事です。
まず HonoX では、 islands
ディレクトリや、 *.island.tsx
などのファイルが参照に含まれていると Island コンポーネントとして動作する、という仕組みがあります。
そして、参照に含まれていた場合はコンパイル時に export const __importing_islands = true;
が差し込まれることによって、ルーターミドルウェアで Context に Island コンポーネントがあることが設定され、最終的にはクライアントへ JS が配信される、という仕組みになっています。
※ちなみにこの仕組みはプロダクションビルド時にのみ動作する (開発ビルド時には常に配信される) ので、デプロイ後に動かなくなってしまうわけですね。
なので、同じ事を MDX ファイルに対してもやってあげれば、自動的にクライアントへ JS が配信されるはず、ということでやっていきましょう。
現状一番簡単なのはわたしが昨日作ったパッケージを使うことですが、それでは面白くないので自作していきましょう。
やることとしては、 MDX の参照に Island コンポーネントが含まれていれば export const __importing_islands = true;
を差し込むこと、なので、下記のような Vite (Rollup) プラグインを書くことで動作します。
// src/plugins/mdx-island.ts import { compile, type CompileOptions } from "@mdx-js/mdx"; import precinct from "precinct"; import { type Plugin } from "vite"; const mdx = (opts: Readonly<CompileOptions>): Plugin => { return { name: "mdx-island", async transform(source, id) { if (id.endsWith(".mdx")) { const code = await compile(source, opts); const deps = precinct(code.value, { type: "tsx" }) as string[]; const hasIslands = deps.some((w) => /\/islands\/.*$/.test(w)); if (hasIslands) { return { code: `${code.value}\nexport const __importing_islands = true;`, }; } return { code: code.value }; } }, }; };
precinct
は JS/TS コードから依存だけを取得してくれるパッケージです。これに JSX へコンパイル済み MDX を渡すことで、 MDX の参照が取得できます。
その取得できた依存に対して、 islands/*
があればとりあえず export const __importing_islands = true;
を差し込むだけのコードです。
これをデプロイすると動きます。簡単ですね。
これで完成です。あとは依存の依存に対応だとか、 .island.tsx
のときなどの対応を入れれば使うことが出来るハズです。
ということで、使いたい系記事でした。ではでは。