title

QwikCityでMDXの記事を書いてみる

2023/12/17

FrontendQwik

QwikCityがMarkdownとMDXに対応しているので、この機能を利用して記事を書いてみたり、調整したり、一覧表示したりします。


MarkdownとMDX

Markdownは言わずも知れたフォーマットですが、どうやらここ1~2年でMDXなるものの勢いが増しているようです。MDXはMarkdownとjsxをかけ合わせたもののようで、Markdownの中にjsxが書けるといった特徴があります。

MDX

Qwik上で使用してみる

QwikCityではデフォルトでMarkdownとMDXに対応しているようなので、早速取り掛かってみましょう。

特に設定をしてなくても読み込めてしまうので、以下の配置にindex.mdxを設置します。

src/
└── routes/
    └── blog/
        └── some-article/
            └── index.mdx

QwikCityはFront matterに対応しているので、下記のようにページタイトルや情報を埋め込むことが出来ます。titleやOpenGraph(og)などは自動で適切にhead要素に展開してくれますし、publishedAtやtagsのように自分で使うプロパティも追加出来ます。

そして、目玉であるjsx/tsxコンポーネントの読み込みも行えます。

---
title: 記事タイトル
description: 記事の概要
opengraph:
  - title: 記事タイトル
    description: true
  - image: https://storage.ephy.dev/path/to/cover/image.webp
    image:alt: 記事のカバー画像
publishedAt: "2023-12-17"
tags:
  - Frontend
  - Qwik
  - MDX
---

import { SomeComponent } from "~/components/some-component.tsx"

<SomeComponent />

以降、Markdownを記述

ちょっとしたサンプルコンポーネントを埋め込んでみたり、良い感じに作った画像・動画用コンポーネントなどを定義しておくと取り回しが良いかも知れませんね。

Qwik - MDX

コードブロックにテーマを当てる

QwikCityはデフォルトで3つのプラグインを利用しているようで、refractorの概要には以下のように書いてあります。

Lightweight, robust, elegant virtual syntax highlighting using Prism.

というように内部的にはPrismを使用しているようなので、Prismを追加してcssだけ利用します。

npm install prism-themes

あとはlayout.tsxや任意のコンポーネントで読み込むだけです。

今回はprism-coldark-coldのテーマを選んでみましたがPrismThemesのリポジトリにいろいろなテーマのサンプルがありますので、お好みのものを探してみてください。

import { Slot, component$, useStyles$ } from "@builder.io/qwik";
import prismStyle from "prism-themes/themes/prism-coldark-cold.min.css?inline";

export default component$(() => {
  useStyles$(prismStyle);
  return <Slot />;
});

記事一覧のためにMDX記事一覧を表示する

Qwik Cityのドキュメントにglob-importのページがあるので、これを参考にしてページ一覧を取得してみます。

Qwik - Glob Import

export const useArticles = routeLoader$(async () => {
  // 記事データの取得
  const mdxComponents: Record<string, any> = import.meta.glob("/src/routes/blog/**/index.mdx");
  // Frontmatterの抽出
  const articles = await Promise.all(
    Object.keys(mdxComponents).map(async (path) => {
      const doc = (await mdxComponents[path]()) as DocumentHeadProps;
      // 記事ディレクトリのパス抽出
      const href = path.match(/\/([^/]+)\/index\.mdx$/);
      const description = doc.head.meta.find((obj) => obj.name === "description");
      return {
        title: doc.head.title,
        description: description?.content,
        href: `${href ? href[1] : ""}`,
        tags: doc.head.frontmatter.tags,
      };
    }),
  );
  return {
    articles: articles,
  };
});
export default component$(() => {
  const { articles } = useArticles().value;

  return <></>; // 省略
});

こんな感じのデータが返ってきますので、これを一覧に使用すれば記事一覧を作成できます。

必要に応じてFrontmatterを追加したり、記事をソートしたり、公開・非公開のフラグを追加して管理してみても良いかも知れませんね。

[
  {
    title: "Cloudflare R2を試してみる",
    description: "Cloudflareが提供するオブジェクトストレージサービスを試してみます。",
    href: "get-start-cloudflare-r2",
    tags: ["Cloudflare"],
  },
  {
    title: "QwikCity + CloudflarePages + Newtでブログを作る",
    description:
      "Newt公式のチュートリアルを参考にブログを作成する過程でハマったところやチュートリアル外での作業などを紹介します。",
    href: "qwik-city-cloudflare-pages",
    tags: ["Frontend", "Qwik", "Cloudflare"],
  },
  {
    title: "QwikCityでMDXの記事を書いてみる",
    description:
      "QwikCityがMarkdownとMDXに対応しているので、この機能を利用して記事を書いてみたり、調整したり、一覧表示したり。",
    href: "qwik-city-mdx-articles",
    tags: ["Frontend", "Qwik", "MDX"],
  },
];

諸々スタイル調整などをして、無事一覧が出来ました。

ブログ記事一覧のイメージ

ビルド時に以下の警告が出ますが一応動作としては問題ないです。ViteがMDXを静的生成しているにもかかわらず、index.tsxのために動的読み込み(import.meta.glob)してしまっているのが原因だと思います。

[plugin:vite:reporter]
(!) /path/to/index.mdx is dynamically imported by /path/to/index.tsx
but also statically imported by /path/to/@qwik-city-plan,
dynamic import will not move module into another chunk.

index.tsx用のjsonファイルを静的生成するか、StaticGenerateHandlerあたりの活用が解決手段になると思います。

Qwik - Static Site Generation Overview

viteのビルド的には好ましくない状態ですが、対応はまた今度にしたいと思います。