【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を仕込む記事も上げておりますので、以下の記事もよろしくお願いします。

GoogleアナリティクスのタグをAstroサイトに追加

それでは細かいネタをご紹介いたします。

1. ビルドのログを細かく出す。

ビルド時のエラーメッセージというかエラーの進捗状況をより詳しく出すには --verbose を渡します。

shell
$ npm run build - --verbose

はい、こんな感じで npm runから中のコマンドに引数渡すには- です。

2. Markdown で生成した後のHTMLにTailwind CSSを適応する

MarkdownのドキュメントはあとからHTMLが生成されてくるため、直接 Tailwindのクラスを当てることはできません。 ベースの整形は Tailwind CSS の公式プラグイン @tailwindcss/typography がやってくれます。

まずはこのプラグインのセットアップから開始します。

2-1. @tailwindcss/typography

Tailwind CSSのプラグインをセットアップ。

shell
$ npm install -D @tailwindcss/typography

Tailwindの設定ファイルにプラグインを追加します。

tailwind.config.js
module.exports = {
// ...
  plugins: [
    require('@tailwindcss/typography'),
    // ...
  ],
}

で、Markdown記事のレイアウトを指定するastroファイルでproseクラスを指定します。

src/layouts/Blog.astro
...
<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要素に対してカスタマイズは以下のように記述できます。

src/layouts/Blog.astro
...
<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 程度じゃ太刀打ちできないので以下のようにカスタムのコンポーネントを追加することになります。

src/layouts/Blog.astro
<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 でやってます。

“脚注”のカスタマイズは主にこのプラグインのオプションです。以下のようなオプションがあります。

それぞれの説明は上記のリンクを参照ください。 Astro.js でこのオプションを指定するには以下のようになります。

astro.config.mjs

export default defineConfig({
...
  markdown: {
...
    remarkRehype: {
      footnoteLabel: "脚注"
    },
...
  }
...
});

この例では”Footnote”の表記を”脚注”に変えてます。 デフォルトでタグがh2になってるのもちょっと扱いずらいかもですね~。。。

4. Markdown の目次を表示

そもそもの”Markdownの目次”ですが、これはMarkdownの中で ######### などで指定した”見出し”のコレクションが目次になります。 見出しを抽出してリストにし、見た目を整えたものが目次となります。

Astro.js の CollectionEntryrender() メソッドの戻り値で見出しのリストが返ってきます。

src/pages/blog/[id].astro
  ...
  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型で、これの定義は以下です。

node_modules/@astrojs/markdown-remark/dist/types.d.ts
export interface MarkdownHeading {
    depth: number;
    slug: string;
    text: string;
}

depth が見出しの深さでslugがリンク用のアンカー、textが見出しの文言です。

例えば以下のような感じでサクッと見出しを作成できます。

src/layouts/Blog.astro
<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 ではできなくて、プラグインでタイトルタグを生成して、スタイルシートで見た目を修正してます。 本サイトで使ってるプラグインは以下です。

まずはコイツをインストールします。

shell
$ npm install remark-flexible-code-titles

続いて Astro.js の設定ファイルでプラグインを有効にします。

astro.config.mjs
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です。

Blog.astro
...
<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使ったほうが楽なんじゃない?)

以下のコマンドでサクッと追加します。

shell
$ npx astro add alpinejs

で、このコマンドで astro.config.mjs に設定を追加してくれるので、セットアップは以上です!!

ちなみに…

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の設定ファイルに以下のように追加するだけです。

tailwind.config.cjs
module.exports = {
  ...
	theme: {
    ...
		extend: {
			colors: {
				twitter: '#1DA1F2',
			},
		},
	},
  ...
}

themeの直下に colors を追加すると今のテーマを丸ごと上書きしてしまうので、extends の下に colors を入れています。

ついでに、Github Copilotを有効にしていると、このカラーの#1DA1F2自動で出てきます。 さらにこの下の行で f と打つと、facebook: '#3B5998',が補完候補に挙がってきます。 有名どころのSNSやWebサービスの頭文字を入れるとそのサービスが補完校に挙がってきます…

tailwind.config.cjs
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のアイコンカラーですが本サイトでは以下のように使用してます。

TwitterIcon.astro
    <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>

手順としては、

  1. interface Propsでコンポーネントのプロパティで受け取る設定値の入り口を定義します。
  2. const { ... } = Astro.props; でAstroのコンポーネントとして設定値を受け取ります。
  3. <style define:vars={{ ... }}>変数名 定義してます。これだけで Astroコンポーネントのプロパティ値も渡されてます。
  4. CSSの値には var(--{変数名}) で指定します。
  5. TailWind CSS のクラスには bg-[var(--変数名)] で指定します。

Tailwind のクラス名では拡張値の [] を使って変数で渡した値を有効にしてます。

これでAstoコンポーネントの属性値でCSSの設定を指定することができます。

本日はここまでといたします。

Astro.js、Tailwind CSSの入門にぜひ!