Astro備忘録
これは何?
私がAstroを使っていく中で、
- 気になった挙動
- ハマったポイント
- たまに使うが、よく忘れてしまうTips
などのメモを残していくスクラップです。
インデックス
エンコードされたURL(日本語URL)のルーティング
ヘッドレス化したWordPressで記事を管理して、Astroでフロントエンドを構築する際、日本語が混入したURLだとAstroのルーティングがうまくいきませんでした。
URLエンコードされない文字列だけを想定して開発するなら、例えば以下のようなコードでURLのルーティング可能です。
---
// [slug].astro の [slug]に、WPで設定した記事のスラッグが対応します
const { slug } = Astro.params;
export async function getStaticPaths() {
const res = await fetch( WPのURL + "/wp-json/wp/v2/posts");
const posts = await res.json();
return posts.map((post:any) => ({
params: { slug: post.slug },
props: { post: post },
}));
}
...
ですが、URLに日本語が含まれてたりする場合、404
エラーを吐き出してしまい正しくルーティングできません。WordPressのREST APIから返ってくるJSONのslug
の値はURLエンコードされてるのに…。
デコードしてみよう
コード側でJSONのslug
をデコードしたら正しくルーティングできました。
---
// [slug].astro の [slug]に、WPで設定した記事のスラッグが対応します
const { slug } = Astro.params;
export async function getStaticPaths() {
const res = await fetch( WPのURL + "/wp-json/wp/v2/posts");
const posts = await res.json();
return posts.map((post:any) => ({
params: { slug: decodeURI(post.slug) }, // <- ここにdecodeURI()を追加
props: { post: post },
}));
}
...
astro:assets
でも、getImage()
にstring
でファイルパスを渡したい
Astro 3.0でastro:assets
が正式に実装され、@astrojs/image
がdeprecated
となりました。
astro:assets
でも@astrojs/image
のような感覚で画像の最適化をすることが可能です。ただ、今までと違う点もあり、例えばgetImage()
にurlやファイルパスをstring
で雑に渡すような使い方は、(今のところ)できません[1]。
とはいえ、ファイルパスのstring
から画像を最適化したい場合もあると思います。
Astro.glob
の出番
まずはAstro.glob
やimport.meta.glob
を使って、条件に合う画像を全部インポートします。そこからファイルパス(string
)を使って、目的の画像を抽出するような操作をすればOKでした。
コードとしてはこんな感じです。
async function getAvifFromFilePath(filePath: string) {
// public内のimagesディレクトリのサブディレクトリに画像がある場合
const images = import.meta.glob('/public/images/**/*');
// imagesは条件 ('/public/images/**/*') に合致した全ファイルの
// ファイルパスがkeyとなっているオブジェクトなので、
// 目的の画像のファイルパスをkeyとして指定すれば、
// images内にある目的の画像のオブジェクトを抽出できる
const image = await images[filePath]();
// getImage()には、先ほど取得した画像のオブジェクト内にある値、defaultを渡す
return await getImage({
src: image.default,
format: 'avif',
quality: 50,
height: 1024,
width: 1024
});
}
ローカルでビルドする分にはいいんですが、リモートでビルドする場合は、いちいち画像を全部インポートするのは少しモヤモヤします[2][3]。もっとスマートな方法がありそう…。
-
getImage()
のsrc
引数にstring
でファイルパスを渡すと、Expected src to be an image.
というエラーが出ます。ファイルパスが間違ってない場合は、画像自体は表示される(ただ、表示されるのは最適化されていない元々の画像)ので、最適化が上手くいってないことに気付きづらい。
ちなみにコンソールを見た限り、src
にstring
を渡した場合は、width
とheight
のどちらも要求されます。ただし、渡してもやっぱり最適化はしてくれない。 ↩︎ -
import.meta.glob
には、例えば'public/images/${foo}/*'
みたいなかたちで動的な条件を指定することはできず、Invalid glob import syntax: Could only use literals
と返されます。
globの書き方はこちらを参照:globのパターン構文。 ↩︎ -
switch
で切り分けるやり方もあるようです。 ↩︎
dynamic import
を使う方法
もう少し良い方法がないか調べてたら、dynamic import
の存在を思い出しました。
ファイルパスを指定してインポートすることができるのですが、やはりdynamic import
に渡すファイルパスも静的リテラルである必要があります[1]。
例えば、以下のコードでも動きますが、コンソールにはThe above dynamic import cannot be analyzed by Vite.
というWarningが表示され、「コンパイル時にViteで値が検証できなくて困るよ!」と文句を言われます。
async function getAvifFromFilePath(filePath: string) {
const image = await import(filePath); // コンソールでwarning表示される行
// getImage()には、上でインポートした画像のオブジェクト内にある値、defaultを渡す
return await getImage({
src: image.default,
format: 'avif',
quality: 50,
height: 1024,
width: 1024
});
}
-
Viteの公式ドキュメントには
const module = await import('./dir/${file}.js')
のような使い方ができると書いてありますが、実際にそのように書くとwarning
が出ます。また、ファイルパスは相対パスにしなければいけないようです。絶対パスでも動きますが…。ままならない。 ↩︎
Viteに黙ってもらうには、/* @vite-ignore */
キーワードを使うこともできます。コンパイル時に渡すファイルパスが「確実にあっている」ことが自明である場合などに使うと良さそうです。
上述のコードの2行めを以下のように書き換えます。
const image = await import(/* @vite-ignore */ filePath);
参考になりそうなドキュメント
今回参照した、glob
やdynamic import
周りで参考になりそうなドキュメントへのリンクも載せます。
ビルドをしてみたところ、await import
を使ってgetImage()
を呼び出すと 'ERR_MODULE_NOT_FOUND'
エラーが発生してしまいました。公式のドキュメント通りに、Aseto.glob()
を使うのが良さそうです。
Astro 4.xに更新したところ、上に挙げた方法ではうまく動かなかったので、コードを少し書き換えて、記事にしました。
AstroでCSS Scroll-driven AnimationsのPolyfillを使う際の注意
結論
- CSS Scroll-driven Animationsに関わるスタイルは、
<style is:inline>
の中に書くべし - FireFox用に、
animation-duration: 1ms;
を書くべし
CSSだけでページスクロール時のアニメーションが表現できるCSS Scroll-driven Animationsは、2024年6月現在、Chromium系のブラウザでのみ動作します。
ただ、SafariやFireFoxなどのブラウザに対応したい場合でも、以下のPolyfillスクリプトを読み込んであげるだけで動作するようになります。
使い方は本当に簡単で、<script src="js/scroll-timeline.js></script>
の一文を<head>
タグ内などに挿入するだけです。
scroll-timeline.js
を使ってみる
Astroでただ、ローカルでは上記の通りにPolyfillを実装すると動くものの、デプロイしたら動かない…という問題が発生し、1週間ほどハマりました[1][2]。
実装環境:
- ホスティング:Cloufdflare Pages
- フレームワーク:Astro
原因:
<script>
と<style>
のそれぞれの外部ファイルの読み込まれる順番。恐らく[3]。
対策:
- アニメーションに関わる部分をインラインスタイルとして書く。一番大事。
- FireFoxは
animation-duration:auto;
だとアニメーションが動かないため、animation-duration:1ms;
と書く。これで「Safariでは動くけど、FireFoxでは動かない」のような奇怪な現象にも悩まされずに済みます。
ちゃんと動く例:
<head>
<!-- blah blah-->
<style is:inline> /* is:inlineと書くと、HTMLファイルにインライン埋め込みされる */
html{
scroll-timeline: --scroll; /* スクロールタイムラインをhtmlに指定 */
}
.scroll-driven-animation__body {
animation-name: fadeIn;
animation-timeline: --scroll; /* 上で指定したタイムラインを参照 ≒ scroll(root) */
animation-timing-function: ease-in-out;
animation-fill-mode: both;
animation-duration: 1ms; /* autoだとFireFoxの機嫌が悪くなる */
}
@keyframes fadeIn {
from{ opacity:0; }
to { opacity:1; }
}
</style>
</head>
AstroとCloudflare Pagesの組み合わせは非常に強力で、高速で快適な閲覧体験をもたらしてくれる反面、速すぎて「スタイルを当てるのが追いつかない」みたいな挙動が稀に起こる印象なので、そういった場合はis:inline
を駆使するのが良いと思いました。