自転車ブログをGatsbyからAstroに書き換えた。
新興フレームワークだけあり、Gatsbyのように豊富なプラグインによる便利な設定というものはないので、殆どの機能を自分で実装する必要がある。
Head APIがなく、レイアウトコンポーネントから必要な要素を全てPropsで渡してhead
タグに記述したり、クライアント処理をAstroでやるのはつらいのでReactを使ったりと、Gatsbyに慣れているといろいろ面倒なところが多く感じた。
その中でも、ナレッジが少なく(というかundocumented)でデフォルト処理してくれない、MDX内に相対パスで記述した画像をレスポンシブ化する部分だけ抜粋してメモ。
背景
AstroにはContent Collectionという機能があり、src
ディレクトリ内に配置された画像を自動的にフォーマット変換・レスポンシブ対応で自動的に複数サイズの画像をsharpでレンダリングする機能がある。
.astro
コンポーネントの場合は、組み込みの<Image>
, <Picture>
タグを通じてこの最適化をコントロールできる。
一方で、このContent Collectionは.md
, .mdx
ファイル内の画像リンクも最適化してくれるのだが、この最適化は最適化と名ばかりのオリジナルサイズのwebpを吐き出すだけである(しかもフォールバックもない)。
Content Collectionには(ドキュメントを読む限り)グローバル設定が存在しないうえ、gatsby-plugin-image
のようなキャプション設定機能も存在していないため、自前でこれらを実装する必要がある。
Astroの書き味はNext.jsに似ていて、シンプルなコア機能やコンポーネントを提供する方針のようだ。
前提
- Astro.3.x
- Content Collection
@astro/mdx
integration >1.1.0
実装
実は、@astro/mdx
インテグレーションにはCustom Componentのマッピングでimg
を指定した場合、ContentCollectionによって処理された画像に限り AstroのImageMetadata
タイプがPropsとして渡される仕様になっている。
これを知っていると非常にシンプルに記述できる。この情報にたどり着くまでは自分でrehype, remarkプラグインを書かねばならないのかと絶望していたところ。
--- //MdxPicture.astro import type { ImageMetadata } from "astro" import { Picture } from "astro:assets" type Props = { src: string | ImageMetadata alt: string } const { src, alt } = Astro.props --- { typeof src === "string" ? ( <figure> <img class="mx-auto" src={src} alt={alt} /> <figcaption class="not-prose text-center text-xs text-secondary md:text-sm"> {alt} </figcaption> </figure> ) : ( <figure> <Picture formats={["webp"]} fallbackFormat="jpg" widths={[360, 752]} class="mx-auto" {src} {alt} /> <figcaption class="not-prose text-center text-xs text-secondary md:text-sm"> {alt} </figcaption> </figure> ) }
//[...slug].astro --- ~ ~ import { MdxPicture } from "~~~" ~ ~ const { Content } = await post.render() --- <Content components={{ ~ ~ img: MdxPicture }} />
componentsとして渡すときに特段Propsとして明示的に渡す必要がないところがポイント。
ドキュメントに書く価値のある情報のはずなのだが…