Lume入門(第4回) - ページ部品をコンポーネント化して再利用する

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

前回は、Lumeのタグ管理を使ってページの検索性を高める方法をご紹介しました。
ここでは、SearchプラグインやPaginateプラグインを使ってタグの一覧ページを作成しました。

今回はUIの部品化と再利用がテーマです。
昨今はReactやVue等のフロントエンドフレームワークの普及によってコンポーネント指向でUIを部品化することが当たり前になっています。
LumeはフレームワークではなくSSGの位置付けですが、強力なコンポーネント機能が用意されています。

公式ドキュメントに記載されているように、Lumeのコンポーネントはテンプレートエンジンに依存せずどこからでも利用可能です。

今回はNunjucksとJSXでコンポーネントを作成し、各種テンプレートから使う方法を見ていきます[1]

_includesディレクトリからUI部品を再利用する

_includesディレクトリはページ本体ではないテンプレートを格納する特殊なディレクトリです。
Lumeのドキュメントではあまり触れられていませんが、このディレクトリはレイアウトファイルだけでなく、コンポーネントでも使えます。
以下はNunjucksで作成したページから_includes/component.njkに配置したコンポーネントを利用する例です。

<article>
  <h2>NunjucksのincludeでUI部品を利用する</h2>
  {% include 'component.njk' %}
</article>

Eleventyではこのように使うのが一般的でしたが、Lumeでは本記事で紹介しているテンプレートエンジン非依存のコンポーネント機能がお勧めのようです。

Nunjucksでコンポーネントを作成する

#

まずはビルトインテンプレートのNunjucksを使ってみます。
ここではアラートボックス部品をコンポーネントとして作成します。
以下のNunjucksテンプレート(alertbox.njk)を_components配下に配置します。

<div class="alert alert-{{type}}">
  {{message}}
</div>

タイプとメッセージを受け取って、divタグ内にメッセージを出力するシンプルなものです。

このコンポーネントを使う場合は以下のようになります。

---
title: Lumeコンポーネント入門
url: /components/nunjucks/
layout: layouts/blog.njk
---

{{ comp.alertbox({ type: 'info', message: 'Nunjucksのコンポーネントです'}) | safe }}

comp変数を使っているところがポイントです。このオブジェクト内に、_components配下に作成したコンポーネントが格納されています。
Nunjucksテンプレートでは、このcomp変数に直接アクセスできます。各コンポーネントは引数として可変パラメータをオブジェクト形式で指定できるようになっています。

このテンプレートでページを生成すると、以下のようなHTMLとなります(抜粋)。

<main>
  <article>
    <h2>Lumeコンポーネント入門</h2>
    <div class="alert alert-info">
      Nunjucksのコンポーネントです
    </div>
  </article>
</main>

アラートボックスコンポーネントがページ内に展開されている様子が分かります。

JSX/MDXテンプレートからNunjucksで作成したコンポーネントを使う

JSX/MDXテンプレートでもNunjucksで作成したコンポーネントを使うことはできますが、Nunjucksで作成したコンポーネントは文字列としてレンダリングするためエスケープされてしまいます。
HTMLとしてレンダリングするにはdangerouslySetInnerHTMLを使う必要があります。

export default ({comp}: PageData) => (
  <div dangerouslySetInnerHTML={{
    __html: comp.alertbox({type: "info", message: "Nunjucksのコンポーネントです"})
  }} />
)

とても残念な感じですね。テンプレートとしてJSX/MDXを使うのであれば、特別な理由がない限りコンポーネントはJSXとして作成するのが良さそうです。

JSXでコンポーネントを作成する

#

先ほどはビルトインテンプレートのNunjucksでコンポーネントを作成する方法を見てきました。
次はJSXで作成してみましょう。

先ほどと同じアラートボックスをJSX(TSX)で書き換えてみます。

import { PageData } from 'lume/core.ts';

interface Props extends PageData {
  type: 'info' | 'warning' | 'error';
  message: string;
}

export default ({ type, message }: Props) => (
  <div className={`alert alert-${type}`}>
    { message }
  </div>
)

説明の必要もないシンプルなJSXコンポーネントです[2]

注意点としては、useStateやイベントハンドラ等のリアクティブな実装は機能しません。
Lumeはクライアントサイドの振る舞いには関知しません。あくまでのページ生成時のレンダリングで使われるだけです。

Information

現時点ではJSX/MDXはビルトインのプラグインではありません。テンプレートとして使用する場合は別途有効化する必要があります。
詳細は以下を参考にしてください。

このコンポーネントを各テンプレートから使うと以下のようになります。

  • Nunjucksテンプレート
---
title: Lumeコンポーネント入門
url: /components/jsx/
layout: layouts/blog.njk
---

{{ comp.AlertBox({ type: 'info', message: 'JSXのコンポーネントです'}) | safe }}
  • JSX(TSX)テンプレート
import { PageData } from 'lume/core.ts';

export const title = "Lumeコンポーネント入門";
export const url = "/components/jsx/";
export const layout = "layouts/blog.njk";

export default ({ comp }: PageData) => (
  <comp.AlertBox type="info" message="JSXのコンポーネントです" />
)
  • MDXテンプレート
---
title: Lumeコンポーネント入門
url: /components/jsx/
layout: layouts/blog.njk
---

<comp.AlertBox type="info" message="JSXのコンポーネントです" />

Nunjucksコンポーネントと同様にcomp変数から各コンポーネントにアクセスしています。
特にJSX/MDXについては、カスタムタグがそのままテンプレートで利用できます(パラメータはPropsになります)。React経験者にとってはより直感的になりましたね。

コンポーネント用のCSSを出力する

#

最後にLumeのコンポーネント機能が提供する少し面白い機能を紹介します。
Lumeではコンポーネント自体に加えて、コンポーネント用のCSSやJavaScriptリソースを別ファイルに出力できます。

ここでは、今回作成したJSXのアラートボックスに対してCSSを適用してみます[3]
コンポーネントは以下のようになります。

import { PageData } from 'lume/core.ts';
// コンポーネント向けのCSSを出力
export const css = `
  .alert {
    padding: 10px;
    margin-bottom: 10px;
    border: 1px solid transparent;
    border-radius: 4px;
  }
  .alert-info {
    color: #31708f;
    background-color: #d9edf7;
    border-color: #bce8f1;
  }
  .alert-warning {
    color: #8a6d3b;
    background-color: #fcf8e3;
    border-color: #faebcc;
  }
  .alert-error {
    color: #a94442;
    background-color: #f2dede;
    border-color: #ebccd1;
  }
`

interface Props extends PageData{
  type: 'info' | 'warning' | 'error';
  message: string;
}

export default ({ type, message }: Props) => (
  <div className={`alert alert-${type}`}>
    { message }
  </div>
)

css変数をexportしているところがポイントです。他のコードは変更ありません。
この変数をexportすると、Lumeはビルド時に/components.cssへその内容を出力します。

ちなみに、JavaScriptの場合は、js変数にJavaScriptを記述すると/components.jsに出力されます。

あとはレイアウトファイルでこのCSSをリンクしておきます。

<head>
  <link rel="stylesheet" href="/components.css" />
</head>

このようにしておくと、このアラートボックスコンポーネントは以下のように表示されます。

コンポーネントに対してスタイルが適用されている様子が分かります。
このCSSは対象コンポーネントが利用されている場合のみ出力される点がポイントです。未使用の場合はCSSに含まれませんのでサイズの節約になります。

ただし、出力されるファイルはCSS/JavaScript各1ファイルですので、内容に重複がある場合はいずれかの指定で上書きされてしまいます。
多数のコンポーネントで利用する場合はコンポーネントプレフィックスをつける等工夫した方が良さそうです。

まとめ

#

今回はLumeが提供するコンポーネント機能を紹介しました。
静的サイトでもUIのコンポーネント化がうまくいくと、後々の運用が楽になってきますので是非活用していきたいところです。


  1. 実はLume入門(第2回)でUI部品(JSX)を作成してMDXから使う方法は触れられています。 ↩︎

  2. ここではPropsとしてLumeのPageDataを拡張していますが、Lume固有のデータ(serachやpaginate等)も使用していないため必須ではありません。 ↩︎

  3. Nunjucksのコンポーネントでも、コンポーネントのフロントマターにcssやjsを指定すれば同様のことができます。 ↩︎

豆蔵デベロッパーサイト - 先週のアクセスランキング
  1. 基本から理解するJWTとJWT認証の仕組み(2022-12-08)
  2. 直感が理性に大反抗!「モンティ・ホール問題」(2022-07-04)
  3. Nuxt3入門(第8回) - Nuxt3のuseStateでコンポーネント間で状態を共有する(2022-10-28)
  4. Nuxt3入門(第4回) - Nuxtのルーティングを理解する(2022-10-09)
  5. OpenAIのAssistants API(ベータ版)を試す(2023-11-08)
  6. RAGを利用して国会会議録に基づいて質問に回答するLLMを作る方法(2023-09-27)
  7. GitHub Actions - 構成変数(環境変数)が外部設定できるようになったので用途を整理する(2023-01-16)
  8. Nuxt3入門(第1回) - Nuxtがサポートするレンダリングモードを理解する(2022-09-25)
  9. Pytestを使ってみる(その2:VSCode拡張機能編)(2023-03-05)
  10. WSL2上にUbuntu-22.04LTSを導入し、Dockerをインストールしようとしたら、いろいろとハマった件(2023-09-09)