【2023年6月版】
Astro.js 小ネタ集 その①
公開日: 2023/06/04
読了時間: 約 21分
初めに
本サイトは Astro.js + Vercel でサイトを構築&公開しております。
立ち上げの時点での顛末については以下の Zenn の記事をご覧いただければ幸いです。
Markdown形式で記事を書きつつ、Astro.js そのものの構造について理解を深めてまいりましたが、サイトを立ち上げていろいろと見た目や機能などぼちぼち整備を進めているところでございます。
主な記事はMarkdown形式なのでメインのコンテンツである記事を作成することについてはまったく問題はないです。というかそのあたりの機能がそろっていたので Astro.js はかなり強力なSSGであります。 ただし、やはり見た目の部分や機能の部分は当然のことながら、自前で実装するといか手を入れていく必要がございます。
ところが、この Astro.js はとにかくかなりの機能をプラグインで揃えておりまして、VueやReact, Svelteにも対応できるしTailwindは積んでるしMarkdownやMDXもできるってわけで各種フレームワーク、ライブラリの組み合わせが爆発的に多いわけでございまして。
なんせ本格的なHTML+CSSはもう何十年振りなもんでさらにはハイカラなTailwind CSSを本格的に触るってなことでいろいろと四苦八苦しながら汗かき汗かきサイトをなんとか仕上げているところでございます。
正直、細かすぎてどう記事に切り出したもんか困っているのではございますが、そこそこノウハウがたまってきたのでこれまでの振り返りを含めてまとめて見たいと思った次第でございます。
それでは本日もよろしくお願いいたします。
技術スタック
本サイトのざっくりな技術スタックは以下のような感じです。
- Astro.js
- Tailwind CSS
- Alpine.js
- Vercel
Astro.jsが様々な技術スタックのハブでありプラグイン集であり統合ビルダーであるのでどこまで技術スタックにカウントするか微妙ではありまして… 特に MarkdownからのHTMLの生成については、Markdown → HTML にremark、HTML→HTMLの整形にrehype、コードブロックのシンタックスハイライトにShiki、HTMLの色付けは Tailwind プラグイン@tailwindcss/typography のように多重のフレームワークが折り重なっており、remark,rehype,shikiそれぞれに設定やらプラグインでの追加機能などが盛りだくさんでございます。
さらに今回は超シンプルなUIライブラリとしてAlpine.jsを採用してみてます。ブログ記事メインのサイトでがっつりの機能はいらないので鬼シンプルにAlpine.jsを初採用してみましたのでそのあたりもご確認いただければと。
また、すでに Google アナリティクスのJSを仕込む記事も上げておりますので、以下の記事もよろしくお願いします。
それでは細かいネタをご紹介いたします。
1. ビルドのログを細かく出す。
ビルド時のエラーメッセージというかエラーの進捗状況をより詳しく出すには --verbose
を渡します。
$ npm run build - --verbose
はい、こんな感じで npm run
から中のコマンドに引数渡すには-
です。
2. Markdown で生成した後のHTMLにTailwind CSSを適応する
MarkdownのドキュメントはあとからHTMLが生成されてくるため、直接 Tailwindのクラスを当てることはできません。
ベースの整形は Tailwind CSS の公式プラグイン @tailwindcss/typography
がやってくれます。
まずはこのプラグインのセットアップから開始します。
2-1. @tailwindcss/typography
Tailwind CSSのプラグインをセットアップ。
$ npm install -D @tailwindcss/typography
Tailwindの設定ファイルにプラグインを追加します。
module.exports = {
// ...
plugins: [
require('@tailwindcss/typography'),
// ...
],
}
で、Markdown記事のレイアウトを指定するastro
ファイルでprose
クラスを指定します。
...
<article class="prose">
<slot/>
</article>
...
prose
クラス指定のみでその下のMarkdownから生成されるHTMLにはがばっとスタイルが当たります。
だいたい、これでOKです。
で、proseに対してさらにカスタマイズを加えたい場合は次の手順となります。
2-2. prose でざっくり指定
prose-xx
で様々な カスタマイズが可能です。
- カラーテーマ:
prose-gray
(default),prose-slate
,prose-zinc
,prose-neutral
,prose-stone
- 文字サイズ:
prose-sm
,prose-base
(default),prose-lg
,prose-xl
,prose-2xl
- ダークモード:
dark:prose-invert
でさらにMarkdownで生成されるHTML要素に対してカスタマイズは以下のように記述できます。
...
<article class="prose prose-img:rounded-xl prose-headings:underline prose-a:text-blue-600">
<slot/>
</article>
...
このように prose-
の後ろに要素名を指定することでそのタグに対して一括で装飾が可能です。
使用可能な要素のリストは以下の公式ドキュメントをご覧ください。
要素名ではなく、いつものクラス名でスタイルを当てたい場合は、以下の @layer component
定義でどうにかします。
2-3. @layer component でがっつり
remark,rehypeのプラグインでしっかり組まれたHTMLが生成されることも多々あります。
そのような場合には prose-h1
程度じゃ太刀打ちできないので以下のようにカスタムのコンポーネントを追加することになります。
<style>
@tailwind components;
@layer components {
.remark-code-container > .remark-code-title+pre {
@apply mt-0 rounded-t-none;
}
.remark-code-title {
@apply text-base text-gray-200 dark:text-gray-800 bg-slate-900 dark:bg-slate-100 pl-3 rounded-t-lg font-bold;
}
}
</style>
...
<article class="prose">
<slot/>
</article>
...
こんな感じで@layer components {}
内にクラスを定義することでいつも通りのCSSが利用可能です。
2-4. Markdown にHTML直貼り
そして、さらにガッツリCSSというかデザイン当てたい場合には、HTMLをMarkdownに直書きしたいです…
が、@tailwindcss/typography
が邪魔なのでコイツを無効にする必要があります。
<div class="not-prose">
...
</div>
このように not-prose
クラスをつけるとその中身は@tailwindcss/typography
が効かなくなります。これでOKです。
このサイトの広告枠の箇所もMarkdownにアフィリエイトタグを直貼りしてます。
3. Markdown の”脚注”
MarkdownからHTML生成のメインのプラグイン?は remark-rehype
でやってます。
“脚注”のカスタマイズは主にこのプラグインのオプションです。以下のようなオプションがあります。
- options.footnoteLabel
- options.footnoteLabelTagName
- options.footnoteLabelProperties
- options.footnoteBackLabel
それぞれの説明は上記のリンクを参照ください。 Astro.js でこのオプションを指定するには以下のようになります。
export default defineConfig({
...
markdown: {
...
remarkRehype: {
footnoteLabel: "脚注"
},
...
}
...
});
この例では”Footnote”の表記を”脚注”に変えてます。
デフォルトでタグがh2
になってるのもちょっと扱いずらいかもですね~。。。
4. Markdown の目次を表示
そもそもの”Markdownの目次”ですが、これはMarkdownの中で #
~ ########
などで指定した”見出し”のコレクションが目次になります。
見出しを抽出してリストにし、見た目を整えたものが目次となります。
Astro.js の CollectionEntry
は render()
メソッドの戻り値で見出しのリストが返ってきます。
...
export async function getStaticPaths() {
return (await getCollection('blog')).map(entry => ({
params: { id: entry.slug },
props: { entry },
}))
}
interface Props {
entry: CollectionEntry<'blog'>;
}
const { entry } = Astro.props;
const { Content } = await entry.render();
const { Content, headings } = await entry.render();
...
このような感じで const { Content, headings } = await entry.render();
でheadings
に見出しのリストが返ってきます。
このクラスはMarkdownHeading
型で、これの定義は以下です。
export interface MarkdownHeading {
depth: number;
slug: string;
text: string;
}
depth
が見出しの深さでslug
がリンク用のアンカー、text
が見出しの文言です。
例えば以下のような感じでサクッと見出しを作成できます。
<article ...>
<aside class="toc">
<ol class="toc-level-0">
{headings.filter(h => h.depth < 3).map((h,i) => {
return (
<li class={`toc-level-${h.depth} toc-item`}><a href={`#${h.slug}`}>{h.text}</a></li>
)})}
</ol>
</aside>
<slot/>
</article>
ここの例では .filter(h => h.depth < 3)
で深さが1,2だけの2段の見出しに絞っています。
あとは、スタイルシートで toc-level-1
toc-level-2
toc-level-3
などやっていけばいろいろ凝った段組みも可能ですね。
別出しにせずに本文内に入れ込んで簡単に目次を表示したい場合はremarkのプラグイン remark-toc を使うのもありかと思います。
5. Markdown のコードブロックにタイトルをつける
Markdown のコードブロックでよくある、
```ts:ファイル名
みたいなファイル形式とファイル名を指定できるの、よくあるじゃないですか? あれは Remark ではできなくて、プラグインでタイトルタグを生成して、スタイルシートで見た目を修正してます。 本サイトで使ってるプラグインは以下です。
まずはコイツをインストールします。
$ npm install remark-flexible-code-titles
続いて Astro.js の設定ファイルでプラグインを有効にします。
import remarkCodeTitles from "remark-flexible-code-titles";
export default defineConfig({
...
markdown: {
...
remarkPlugins: [remarkCodeTitles],
...
}
...
});
で、準備は完了です。 これで、
```ts:filename
というMarkdwon だと以下のようなHTMLを生成してくれるようになります。
<div class="remark-code-container">
<div class="remark-code-title">filename</div>
<pre class="astro-code" tabindex="0">
<code>
...
このようにタイトル付きのコードタグ、みたいなブロックを生成してくれます。
で、これを上記であげたように、カスタムのタグを当てればOKです。
...
<style is:global>
@tailwind components;
@layer components {
.remark-code-title {
@apply text-base pl-3 rounded-t-lg font-bold;
}
.remark-code-title+pre {
@apply mt-0 rounded-t-none;
}
}
</style>
のような感じで、このサイトのコードブロック部分は作成してます。 その他にも remark-code-title などがあります。 生成されるタグは異なると思いますが、だいたいこんな流れでOKでしょう。(?)
6. Alpine.js でコンポーネント作成
本サイトはブログ記事がメインのためあんまりガッツリと”機能”を作りこむ予定はありません。
そのためできるだけ薄くてイージーな、モリモリのフレームワークよりは軽めのライブラリのようなUI用の軽量フレームワークを探していました。
そこで、Astro.jsでサポートされているUIフレームワークで一番軽めな Alpine.js
を使ってみました。
ちなみに Astro.js のプラグイン集?から連携可能なフレームワークが検索可能です。
で、alpine.js 連携のガイドは以下です。
6-1. Alpine.js 連携のセットアップ
セットアップは非常に簡単です。(Astro.jsは他のフレームワークもセットアップが劇的に簡単ですが…というかReactなんか普通にサイトのセットアップするよりAstro.jsでサイト設定してコンポーネントでReact使ったほうが楽なんじゃない?)
以下のコマンドでサクッと追加します。
$ npx astro add alpinejs
で、このコマンドで astro.config.mjs
に設定を追加してくれるので、セットアップは以上です!!
ちなみに…
...
export default defineConfig({
...
integrations: [
tailwind(),
sitemap(),
alpinejs()
],
...
});
みたいな感じで設定を追加してくれます。いや、これで設定は終了で、あとは使うだけです。
6-2. コンポーネントの実装
本サイトではスマホ用の記事の目次の表示切替にAlpine.jsを使ってます。
いかのような感じです。
<aside x-data="{ open: false }">
<div class="w-full flex flex-row justify-end my-auto px-6 py-3 gap-2">
<div class="text-sm my-auto font-bold text-gray-400">
<button @click="open = true">目次</button>
<span class="open_area cursor-pointer" x-show="!open" @click="open = true">▽</span>
<span class="close_area cursor-pointer" x-show="open" @click="open = false">△</span>
</div>
<TwShare entry={entry} url={url} />
</div>
<div x-show="open" class="float-menu" @click.outside="open = false">
<Toc headings={headings} />
</div>
</aside>
とくに import
などせずともいきなり Alpine.js の属性が利用可能です。
x-data
で表示切替用のフラグを初期化します。@click="open = true"
でクリックしたらフラグをtrue
にする、の設定。@click="open = false"
はその逆。x-show="open"
でフラグに従って表示・非表示の切り替え。@click.outside="open = false"
でその領域外をクリックしたら、フラグをfalse
にする、の設定。
これだけですがやりたいことは120%実現できているので大満足です。
とくに、@click.outside
ってすごくない?○○である
の設定は普通ですが、○○じゃない
の設定ってなかなか大変なんすよ。
領域外
のイベントを設定できるのスゴイです。
ミニマルなセットアップでミニマルに実装できる。。。 今のところほしかったのは Alpine.js です。 (jQueryは…方向性がどうのこうのとか肥大化しすぎて…とかあって使いたくない。っていうかnode.jsの開発環境で jQuery 使ったことないな…)
7. TailWind CSSにTwitter色を追加
サイトの”Twitterにシェア”機能の実装でSVGのアイコンなど設置してみましたが、ついでに Tailwind にTiwtterのアイコンカラーを追加して、いろいろ使えるようにしてみたいと思います。
tailwindの設定ファイルに以下のように追加するだけです。
module.exports = {
...
theme: {
...
extend: {
colors: {
twitter: '#1DA1F2',
},
},
},
...
}
theme
の直下に colors
を追加すると今のテーマを丸ごと上書きしてしまうので、extends
の下に colors
を入れています。
ついでに、Github Copilotを有効にしていると、このカラーの#1DA1F2
、自動で出てきます。
さらにこの下の行で f
と打つと、facebook: '#3B5998',
が補完候補に挙がってきます。
有名どころのSNSやWebサービスの頭文字を入れるとそのサービスが補完校に挙がってきます…
module.exports = {
...
theme: {
...
extend: {
colors: {
airbnb: '#FF5A5F',
google: '#4285F4',
twitter: '#1DA1F2',
facebook: '#3B5998',
instagram: '#E1306C',
linkedin: '#2867B2',
github: '#333',
whatsapp: '#25D366',
telegram: '#0088cc',
medium: '#00ab6c',
youtube: '#FF0000',
},
},
},
...
}
こんな感じでガンガンにサービスとアイコンカラーが補完されていきます。 めっちゃ便利です。 アイコンカラー追加しおくと、Tailwindのイベントと組み合わせアイコンカラーが使えるのでいろいろ便利です。
っていうか Github Copilot スゴイ。
8. SVGの色をTailwind CSSで指定
上記で追加した Twitterのアイコンカラーですが本サイトでは以下のように使用してます。
<svg
xmlns="http://www.w3.org/2000/svg"
class=" fill-white hover:fill-twitter"
viewBox="0 0 30 30">
<path ... />
</svg>
これでSVGでアイコンを描画するだけど、基本は白塗り、マウスオーバーでツイッターカラーに塗る、をやってます。
このように、SVGではfill
属性を使えばSVGの塗りがTailwindで指定できます。
9. Style タグに変数を渡して Tailwind CSSをバリアブル(可変)にする
いろいろコンポーネントを作っていくと、ピンポイントで前景色と背景色を変えたいとか入れ替えたいとか出てきます。
そんな時は、Astro.js の props
と CSS の変数を組み合わせてコンポーネントの属性としてCSSに値を渡すことができます。
本サイトでは記事の目次エリアをPC版とモバイル版で色を切り替えてます。 たとえば、以下のようにしてます。
---
import type { MarkdownHeading } from 'astro';
export interface Props {
headings: MarkdownHeading[];
fgColor : string;
bgColor : string;
}
const { headings, fgColor, bgColor } = Astro.props;
---
<style define:vars={{ fgColor, bgColor }}>
@tailwind components;
@layer components {
...
.toc-level-0::before {
@apply bg-[var(--fgColor)] rounded-full content-[''];
}
.toc-level-0 > li::before {
@apply bg-[var(--fgColor)];
border: 2px solid var(--bgColor);
}
.toc-level-0 > li.toc-level-3::before {
@apply bg-[var(--fgColor)];
border: 2px solid var(--bgColor);
}
...
}
</style>
手順としては、
interface Props
でコンポーネントのプロパティで受け取る設定値の入り口を定義します。const { ... } = Astro.props;
でAstroのコンポーネントとして設定値を受け取ります。<style define:vars={{ ... }}>
で変数名
定義してます。これだけでAstroコンポーネントのプロパティ値
も渡されてます。- CSSの値には
var(--{変数名})
で指定します。 - TailWind CSS のクラスには
bg-[var(--変数名)]
で指定します。
Tailwind のクラス名では拡張値の []
を使って変数で渡した値を有効にしてます。
これでAstoコンポーネントの属性値でCSSの設定を指定することができます。
本日はここまでといたします。