Astro 5.x への移行はなかなかハマりどころが多い
5系が出たか。
Astroは2.xのときから愛用していて、こまめにメジャーバージョンアップに追従(現状 4.15)してきたし、今回も npx @astrojs/upgrade するだけの簡単なお仕事や・・・と思ったらドハマリ案件でした。
要訳
Content Collectionの仕様が破壊的に変更されました。これがかなりの影響でした。
Content Layer
Astro is the best framework for content-driven sites, and with Astro 5.0 we’re making it even better. The Astro Content Layer is a new flexible and pluggable way to manage content, providing a unified, type-safe API to define, load, and access your content in your Astro project, no matter where it comes from.
Astro 5はVite 6に最も早く対応したフレームワークの一つです・・・これがまたドハマリ案件。 Don't worry...だ...と?
Vite 6
Astro 5 is one of the first frameworks to ship with Vite 6, just released a week ago. (Don’t worry: we’ve been working with beta releases so you likely won’t need to change any code when upgrading to Astro 5.)
ハマりどころ
依存パッケージ
普段であれば npx @astrojs/upgrade 一発で、package.json がいい感じに更新されて終わるのですが、今回はそうは行きませんでした。
run manually the below
npm install @astrojs/mdx@4.0.3 @astrojs/react@4.1.2 @astrojs/tailwind@5.1.4 astro@5.1.1
そして、これを素直に実行しても依存関係が矛盾するみたいなことを言われ、package.jsonを結局手で編集しました。(たしか astro-pagefind と @astrojs/mdx あたりで詰まりました)
これはさほど大きな問題ではありませんでした。
content/config.ts の仕様がガッツリ変わった
まずファイルの場所
従来は src/content/config.ts でしたが、 src/content.config.tsに変更されました。
移動&リネームしましょう。
loader指定が必須になった
従来のcontent collectionでは typeプロパティで *.md か *.yaml かを指定する方式でした。
export const collections = {
checklists: defineCollection({ type: "data", schema: CheckList }),
suppliments: defineCollection({ type: "content", schema: Suppliment }),
};
5.xで導入されたContent Layerにより、リポジトリに含まれるファイルだけではなくAPIリクエストの結果などもContent Collectionの仕組みで扱えるように柔軟性が増しましたが、そのためにloaderというものを指定する必要が出てきました。
const blog = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/data/blog" }),
schema: /* ... */
});
loaderを差し替えることによって多様なデータソースに対応できるようです。
Collection Entry型の定義が変更されている
Collection Entryは getEntryやgetCollectionによって取得されるContentを表す型です。
slug プロパティがなくなった + idプロパティに入る値が変わった
個人的にはこれが非常に影響がありました。
src/content
└── Blog
├── Agile
│ └── BacklogRefinement.md
├── Architecture
│ │ ├── ArchitectureRoadmap.md
Blogコンテンツがこの用に配置されていた場合には、4.xまではslugプロパティに
- agile/backlogrefinement
- architecture/architectureroadmap
といった形で、 toLower+拡張子省略形式の文字列が入っていました。
代替手段としてidプロパティを使うことになるのですが、idプロパティに入る値が破壊的に変わってしまいました。
4.xまでのidプロパティ
パスがそのまま入っていました。
- Agile/BacklogRefinement.md
- Architecture/ArchitectureRoadmap.md
5.xからのidプロパティ
slug相当の「toLower+拡張子省略」に変更されました。
- agile/backlogrefinement
- architecture/architectureroadmap
renderメソッドがrender関数に変わった
4.xまで
const { Content } = await quaterReport.render();
5.xから
const { Content } = await render(quaterReport);
テストが失敗する
さて、プロダクションコードが動くようになったと思ったら npm testが全部失敗します。
VitestがまだVite 6と整合とれてない
テストを実行するとUnhandled Errorsと出まくってそもそもテストが実行できません。
テストケースを expect(1).toBe(1) と言った「なんぼ何でも成功するだろう」というものだけにしても失敗するのでこれはテストケース以前の問題です。
そしてたどり着いたのがこのissueでした。
ワークアラウンドとしては viteのバージョンを固定することでした。
"overrides": {
"vite": "6.0.2"
}
なお、この問題が発生するのは下記のような vitest.config.ts ファイル内で getViteConfigを利用している場合だけです。
GitHub Actions上でだけgetEntryを使っている関数のテストが失敗する
これは頭を悩ませました。GitHub Actionsでテストを実行する場合にだけ失敗するのです。
ローカルで npm testをする限りは成功するのに。
The collection "ramen" does not exist. Please ensure it is defined in your content config.
Content Collectionが空っぽだぞと。
原因は .astro/data-store.json というファイルが作られるタイミングでした。
このファイルは定義されたContent Collectionの内容がJson化されたものです。4.xの時代にこのファイルがあったのかどうかは定かではないのですが、このファイルはnpm run devのタイミングでつくられるようです。ローカルではnpm run devを何回か叩いた後に npm testを実行したので成功していたのですが、GitHub Actionsではまっさらなdata-store.jsonファイルがない状態でnpm testが実行されるために発生していました。
回避方法がわからずissueを起票してAstroコミュニティに相談してみました。
astro:content isn't designed to be used outside of the astro runtime, so if this works then it's accidental (and you won't be doing an accurate test).
そもそも astro:content がそういった状況下でまともに動くものではない、と。
今まで4.x系でこの様なテストが通っていたのは単なる偶然ということですね。
I recommend using a different approach to your tests. Take a look at the tests in packages/astro/test/content-layer.test.js for some examples.
これを参考にしてくれ、ということですが、test-utilsのloadFixture関数がめちゃくちゃ作り込まれていこのままの方法をとるのは難しかったため、結果的にvitestのmocking機能を使って実行時にgetEntry関数をmockに差し替えることにしました。
Discussion