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

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

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

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

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

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

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

Information

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

_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}: Lume.Data) => (
  <div dangerouslySetInnerHTML={{
    __html: comp.alertbox({type: "info", message: "Nunjucksのコンポーネントです"})
  }} />
)

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

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

#

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

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

interface Props extends Lume.Data {
  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)テンプレート
export const title = "Lumeコンポーネント入門";
export const url = "/components/jsx/";
export const layout = "layouts/blog.njk";

export default ({ comp }: Lume.Data) => (
  <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]
コンポーネントは以下のようになります。

// コンポーネント向けの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 Lume.Data{
  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のLume.Dataを拡張していますが、Lume固有のデータ(serachやpaginate等)も使用していないため必須ではありません。 ↩︎

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

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

recruit

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