注目イベント!
アドベントカレンダー2024開催します(12/1~12/25)!
一年を締めくくる特別なイベント、アドベントカレンダーを今年も開催します!
初心者からベテランまで楽しめる内容で、毎日新しい技術トピックをお届けします。
詳細はこちらから!
event banner

Lume入門(第2回) - テンプレートエンジンとしてJSXとMDXを使う

| 6 min read
Author: noboru-kudo noboru-kudoの画像

前回はLumeの基本的な使い方を見てきました。

ここではテンプレート言語として、ビルトインで使えるマークダウンとMozillaのNunjucksを使いました。
ただ、マークダウンとは違い、Nunjucksはあまり世の中に浸透しているとは言えず(たいしたことはないですが)学習コストも発生します。
最近はReactエコシステム普及に伴ってJSXが広く使われています。また、マークダウンでJSXを使えるように拡張したMDXを使いたいと考える人も多いのではないでしょうか。

Lumeは複数テンプレートエンジンをサポートしています。もちろんJSX/MDXもプラグインでサポートしています。プラグインといってもサードパーティ製ではなくLume本体で管理されています(そのうちビルトインプラグインになるかもしれませんが)。
今回は前回のブログサイトをJSX/MDXを使って書き直してみたいと思います。

Information

2023-12-08にLumeがv2にメジャーアップデートしました。これに伴い本記事もv2で動作するよう更新しました。

JSXプラグインを有効にする

#

以下のプラグインをセットアップします。

他のプラグイン同様にJSXプラグイン導入も簡単です。_config.tsに以下を追加します(変更部分のみ掲載)。

import jsx from "lume/plugins/jsx.ts";

const site = lume();
site.use(jsx());

次にdeno.jsonのTypeScriptのcompilerOptionsでReactを追加すれば完了です。

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "npm:react",
    "types": [
      "lume/types.ts",
      "https://unpkg.com/@types/react@18.2.37/index.d.ts"
    ]
  }
  // (省略)
}

レイアウトファイルをJSXで書き換える

#

前回ブログページのレイアウトファイルとしてNunjucksを使っていたものをそのままJSX(TSX)に変換します。
blog.tsxとして以下のJSXを配置しました。

interface BlogPageData extends Lume.Data {
  title: string
}
export default (
  { title, children }: BlogPageData
) => (
  <html lang="ja">
  <head>
    <meta charSet="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>{title}</title>
    <link rel="stylesheet" href="/css/style.css" />
    <link href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css" rel="stylesheet" />
  </head>
  <body>
  <header>
    <h1>サンプルブログサイト</h1>
  </header>

  <main>
    <article>
      <h2>{title}</h2>
      {children}
    </article>
  </main>

  <footer>
    <p>&copy; 2023 豆香ブログ</p>
  </footer>
  </body>
  </html>
)

propsとしてマークダウンのフロントマター情報(title)とコンテンツ(children)を受け取っています。
コンテンツはNunjucksのときはcontent変数を使用していましたが、JSXなのでここはchildren変数として受け取ります。

当たり前ですが、Lumeは静的サイトジェネレーターでクライアント側のJavaScript実行には関与しません。
ここでuseStateやイベントハンドラなどリアクティブなコードを記述しても動作しません。サーバーコンポーネントとして考える必要があります。

これを使うマークダウンファイル(ブログページ)はフロントマターのlayout変数の値をJSX(TSX)を指定するように拡張子を変更します。

---
title: Lumeで始めるブログサイト運営
url: /blogs/lume/
# 以下blog.njkから変更
layout: layouts/blog.tsx
---

これだけです。JSXの変換処理などは一切不要です。
これでサーバーを動かすと(deno task serve)、前回と同じ結果が得られます。

今回はレイアウトファイルとしてJSXを使いましたが、もちろんUIコンポーネントやページ自体でも利用できます。

MDXプラグインを有効にする

#

次にMDXを試してみましょう。MDXはマークダウンでJSXが使えるよう拡張したものです。
こちらもJSX同様に_config.tsに追加するだです。

import jsx from "lume/plugins/jsx.ts";
import mdx from "lume/plugins/mdx.ts";

const site = lume();

site.use(jsx()); // MDXを使う場合は必須
site.use(mdx()); // MDXプラグイン

依存するJSXプラグインに加えてMDXプラグインを追加しています。

MDXでページを作成する

#

ここではJSXでコンポーネントを作成し、MDXのページからそれを利用します。
_componentsディレクトリを作成し、以下のCardコンポーネント(Card.tsx)を配置します。

const styles = {
  card: {
    border: '1px solid #ddd',
    padding: '20px',
    borderRadius: '8px',
    boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
    marginBottom: '20px'
  },
  title: {
    fontSize: '1em',
    marginBottom: '10px'
  },
  description: {
    marginBottom: '10px'
  }
};
interface CardPageData extends Lume.Data {
  title: string;
  content: string;
}
export default ({ title, children }: CardPageData) => {
  return (
    <div style={styles.card}>
      {title && <div style={styles.title}>{title}</div>}
      {children}
    </div>
  );
};

カード部品(Card)としてレンダリングするUI部品です。

ここに配置した_componentsディレクトリはLumeにとって特別なものです。ここに配置したコンポーネントはグローバルにどこからでも使えるようになります。

Information

JSXに限らず、他のテンプレート言語で記述された部品もこのディレクトリに配置することでグローバルに利用可能です。
Lumeのコンポーネント機能の詳細は、本連載の第4回で説明しています。

それでは、このUI部品をMDXで使ってみます。
プロジェクトルートに以下のMDXを配置します(index.mdx)。

---
title: LumeでJSX/MDXを使う
url: /blogs/lume-mdx/
layout: layouts/blog.tsx
---

Lumeで使えるテンプレートエンジンは多数あり、JSX/MDXもサポートしています。

JSX/MDXは公式プラグインとして提供されており、簡単に導入できます。

<comp.Card title="MDXとは?">
  MDXとはマークダウンをJSXを使えるように拡張したものです。

  詳細は以下公式ドキュメントを参照してください。
  - [Markdown for the component era](https://mdxjs.com/)
</comp.Card>

マークダウン内で<comp.Card ...>...</comp.Card>が先程作成したJSXコンポーネントです。
プレフィックスでついているcompは、グローバルコンポーネントが格納されているオブジェクトです。
このようにグローバルコンポーネントはimportなしで使えます[1]

ここでは、titleに加えてchildrenとして渡す内容をタグ内に記述して、Cardコンポーネントをレンダリングしています。
この辺りの記述はReactを使った経験がある方にはお馴染みのものかと思います。

これを実行すると、以下のページになります。

MDX

最後に、今回の記事での変更点を以下にまとめます。

.
├── _components
│   └── Card.tsx <- グローバルで利用可能なCardコンポーネント
├── _includes
│   └── layouts
│       ├── blog.njk <- 前回作成したNunjucksテンプレート
│       └── blog.tsx <- 新規に作成したJSXテンプレート(内容はNunjucks版と同じ)
├── css
│   └── style.scss
├── _config.ts <- JSX/MDXプラグイン導入
├── deno.json
├── deno.lock
├── index.md <- 利用テンプレートをJSXに変更
└── index.mdx <- Cardコンポーネントを使用するMDX

まとめ

#

今回はLumeでJSX/MDXをテンプレート言語として使用しました。
JSX/MDXに限らずLumeは多くのテンプレート言語サポートをプラグインとして提供しています。

もちろんここにない場合は自作できます。入門編ではありませんが、試してみるのもおもしろそうですね。

また、ここでは触れていませんが、1つのファイルで複数のテンプレートエンジンを実行させることも可能です。
例えば、Eleventyのようにマークダウンに対して、Nunjucksとマークダウンパーサーの2つを実行するなんてこともできます。

次回はLumeのページ管理(Searchプラグイン)を見ていきたいと思います。


  1. コンポーネントを_components以外に配置した場合は、マークダウン内でimport文を記述すれば使えます。 ↩︎

豆蔵では共に高め合う仲間を募集しています!

recruit

具体的な採用情報はこちらからご覧いただけます。