🧑‍🚀

Astro 5.x への移行はなかなかハマりどころが多い

2024/12/27に公開

https://astro.build/blog/astro-5/

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は getEntrygetCollectionによって取得されるContentを表す型です。
https://docs.astro.build/en/reference/modules/astro-content/#collectionentry

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でした。

https://github.com/withastro/astro/issues/12662

ワークアラウンドとしては viteのバージョンを固定することでした。

package.json
"overrides": {
    "vite": "6.0.2"
}

なお、この問題が発生するのは下記のような vitest.config.ts ファイル内で getViteConfigを利用している場合だけです。
https://docs.astro.build/en/guides/testing/

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コミュニティに相談してみました。

https://github.com/withastro/astro/issues/12836

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