📘

Deno製の静的サイトジェネレータ "Lume" の紹介

2021/12/06に公開

この記事は、TSG Advent Calendar 2021Deno Advent Calendar 2021の6日目の記事です。

前日の記事はTSG Advent Calendarがhideo54さんの「なんかかく」、Deno Advent Calendarがaccess3151fqさんの「DenoからFirebaseを使う」でした。

はじめに

先日から、LumeというDeno製の静的サイトジェネレータで自分のホームページを作っています。ちなみにホスティングはCloudflare Pagesです。

私のホームページ

ある程度このLumeなるソフトウェアのことが分かってきたので、この記事では「Lume、けっこう悪くないよ」という布教をしたいと思います。

Lumeとは

Deno製のシンプルな静的サイトジェネレータです。ガリシア語の「炎」を意味するlumeに由来します。(ラテン語のlūmenのdescendantですね)

静的サイトジェネレータとは

その名の通り、静的なHTML/CSS/JavaScriptを生成するツールです。
古典的なサーバサイドのwebアプリのように、テンプレートエンジンを使ってページを動的に生成します。しかし、生成はあくまでビルド時に行われるだけで、最終的には静的なウェブサイトが出力されます。

静的なウェブサイトは様々なホスティングサービスで容易に配信できますし、高速化もしやすく、まさに一石二鳥です。

ちなみに、静的サイトジェネレータといちいち打つのは大変なので、SSG(Static Site Generator)という略語がよく使われています。

Lumeの特徴

とにかくシンプルで、複雑なことはしてくれません。たとえば、画像の最適化や遅延ローディングをやりたいのなら、画像形式の変換とかクライアント側の実装とかを自分でやらないといけません。

しかし、逆に言えば、100%自分が書いた通りのHTMLを生成してくれます。余計なJSが読み込まれることも、謎のタグが挿入されることもありません。

そして、古典的な静的webサイトの生成というメインターゲットに絞れば、Lumeの機能はシンプルながら十分だといえると思います。データ、タグ、HTML生成用のヘルパー関数、1つのテンプレートから複数ページを動的に生成する機能[1]、などなど。やろうと思えば割となんでもできます。

テンプレートエンジン

デフォルトでNunjucksとMarkdownが使えます。設定で有効化すればPug(旧jade)やEtaも使えます。

さらに、TSX/JSX(React)でページを書くこともできます。TypeScript過激派なので型がつけられるHTMLテンプレートエンジンとしてのTSXを愛好していて、この機能のためにLumeを使う決心をしました。
他の選択肢がNext.jsとかGatsbyみたいな大掛かりなフレームワークしかなかったんですよね……

ちなみに、自分でプラグインを書けば他のエンジンも使えます。

Deno製

そして、LumeはDeno製です。

私はDenoを触り始めたばかりですが、開発体験が良いと感じています。補完を含むツールチェインも揃っていますし、何よりもTypeScriptを書くまでに「package.jsonをinitして、tsconfig.jsonを書いて……」といった面倒なプロセスを踏まなくていいというのが素晴らしいです。

LumeもDenoの利点を受け継いでいます。プロジェクトを始めるのに複雑なディレクトリ構成を作る必要も、設定ファイルをたくさん書く必要もありません。

他の静的サイトジェネレータとの比較

最近人気の静的サイトジェネレータとして、Next.jsと、Next.jsをベースにしたGatsbyが挙げられます[2]

この2つはどちらかというとオールインワン志向で、多機能ではありますが、重量級で複雑です。よく言われる「simpleとeasy」の軸だと圧倒的にeasy寄りです。

一方、古典的な静的サイトジェネレータであるmiddlemanやjekyll、hugoなどは、どちらかというと「データやコンテンツを元に意図通りのHTMLを出力する」という役割を果たします。内部の実装はよく知りませんが、少なくとも果たす責務は極めてシンプルだと言えるでしょう。Lumeはこちら側に属します。

そして、そういった静的サイトジェネレータの中でも、LumeはJavaScript(Node.js)製の11ty(Eleventy)に近いです。

速度

計測していないので正確なところは分かりませんが、少なくともmiddlemanやjekyllに比べると速いです。hugoにはさすがに勝てない気もしますが、体感では十分高速です。

いくら本番サーバでは実行されないとはいえ、もっさりしてると開発時に不便ですし、ビルドに時間がかかるとストレスですからね。

ちょっとしたチュートリアル

簡単なウェブサイトを作る手順を解説していきます。残念ながら、公式サイトにドキュメントはありますが、チュートリアルは存在しません。ドキュメントの1つ1つのページを見ても「何から始めればいいのか」がいまいち分かりづらいので、公式ドキュメントの補助としてご利用ください。

設定ファイルの生成

まず、作業ディレクトリでlume initを実行して、Lumeの設定ファイルを生成します。「設定ファイルをTypeScriptで書くか」「Lumeのインポートはどういうスタイルでやるか」「プラグインをどうするか」「VS Codeの設定をセットアップするか」などを聞かれます(2021年11月現在)。後からいくらでも設定し直せるので別にどっちでもいいです。最初の質問はお好みで、他は迷ったらデフォルトでいいでしょう。ただし、vscodeを使っているなら設定のセットアップだけは先にやっておいた方がいいです。

生成した_config.js(あるいは_config.ts)は、こんな感じになっていると思います。他に.vscodeディレクトリに多少の設定ファイルが生成されているかもしれません。

_config.ts
import lume from "lume/mod.ts";

const site = lume();

export default site;

(vscode設定ファイルを除いて)生成されるファイルが1つ、しかもこれだけの短さ。シンプルなのはいいことだと思います。

HTMLのレイアウト用テンプレートを作る

基本的に各ページ用のファイルは中身だけを記述して、それ以外のhead要素とかはレイアウトと呼ばれるテンプレートファイルを利用します。Railsなどのサーバサイドフレームワークにも存在する概念なので、そのあたりを知っている人なら分かりやすいと思います。

プロジェクトのディレクトリの下に_includesというディレクトリを作ります。ここにはレイアウトだけでなくincludeするファイルを入れる場所なので、_includesの下にlayoutsのようなディレクトリを作り、そこに置くのが一般的です。別に作らなくてもいいんですけど。

Lume公式がNunjucks推しなので、Nunjucksで書いてみます。

_includes/layouts/main.njk
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
  </head>
  <body>
    <main>
      {{ content | safe }}
    </main>
  </body>
</html>

この{{title}}の部分にページのタイトルが、main要素の中にページの内容が入ります。

ページを作る

index.md
---
title: タイトル
layout: layouts/main.njk
---

# ホーム

文章 [リンク](https://example.com)

この---で囲まれた部分はfrontmatter(front matter)と呼ばれるもので、YAML形式でページ固有の変数を記述することができます。今回の場合はタイトルとレイアウト用ファイルですね。

このままlume -sを実行し、http://localhost:3000/ にアクセスすると、ページが表示されるはずです。

データファイル

ページ内でデータを保持するならfrontmatterでいいですが、全てのページでデータを共有したい場合、データファイルが使えます。具体的には、_data.yml(YAML以外にもJSONなどが使える)というファイルをディレクトリに置くと、そのディレクトリ(とそのサブディレクトリ)の内部でそのデータにアクセスできるようになります。

次に、データファイルを利用しつつ/page/を作ってみます。

_data.yml
data1: str
layout: layouts/main.njk
page.njk
---
data2: str2
---
<p>{{ data1 }}</p>
<p>{{ data2 }}</p>

http://localhost:3000/page/にアクセスすると、変数data1とdata2が指定された内容に置き換わっているのが分かります。

また、layoutのような変数もデータファイル内で指定できることが分かりました。これは、「このディレクトリ内ではこのレイアウトを使う」と決められる場合に便利です。もし例外があれば、それはfrontmatterで指定すればいいのです。

プラグインを使う

では、ページをJSXで書いてみましょう。JSXプラグインが必要なので、_config.js(ts)を編集してプラグインを有効化します。lumeのプロセスを一度終了させるのを忘れないようにしてください。

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

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

export default site;

そして、プラグインの説明通りにページの内容を書きます。frontmatterに相当するのは変数のnamed exportで、ページの中身はdefault exportで書きます。

page2.jsx
export const title = "ページ2";
export default function(data) {
  return <p>ページ2</p>;
}

静的なファイルのコピー

デフォルトの設定では、たとえばimgのようなディレクトリに画像を追加しても、ビルドしたウェブサイトには含まれません。単純に無視されてしまいます。
そこで、コピーすべきファイル・ディレクトリを設定ファイルに記述します。

_config.ts
// 1行追記
site.copy("img");
index.md
![画像](/img/pic.png)

これで画像が表示できるようになりました。画像だけでなく、CSSやJavaScriptも同じです。

その他の設定

lume関数の引数にオプションを渡すことで、様々な設定を変更することができます。たとえば、ウェブサイトのソースをsrcなどのディレクトリに格納したいならsrcオプションを指定するといいです。

詳しく知りたい人は、公式ドキュメントの設定解説ページをご覧ください。

まとめ

LumeはDeno製の軽くてシンプルな静的サイトジェネレータです。要件がシンプルなら問題なくウェブサイトを構築できる、そういう必要十分な機能を備えています。

ドキュメントが不十分なのが玉に瑕ですが、おいおい改善されると思います。実際、ひさしぶりにドキュメントを読み返したらけっこう改善されていた部分がありました。

現状だと趣味を超えて仕事に投入するのは少しリスキーかもしれませんが、趣味で戯れるにはちょうどいいのではないでしょうか。小さい規模の趣味のサイトなら、万が一途中で「これはLumeの手に負えない」となったとしても、別のソフトウェアに乗り換えるのはそう大変ではないでしょうし。

リンク集

  • 公式サイト
  • Lumeを使用しているサイトの例
  • Base Blog - Lume公式のブログテンプレート。サイトマップやページネーションなど、ドキュメントに書いてない要素を実際にどうやって実装すればいいか把握できる貴重な情報源です。公式ドキュメントは正直説明不足

公式以外はあまり情報が充実していないので、ぶっちゃけ公式ドキュメントと実際の使用例と本体のソースコードを眺めるのが一番早かったりします。

Deno Advent Calendarの明日(7日目)の記事はaccess3151fqさんの「stdを使ってテスタブルなサーバーを書く」です。
TSG Advent Calendarの次の記事は10日の「たぶん TSG CTF 開催記」です。

脚注
  1. Create multiple pages ↩︎

  2. Next.jsでもgetStaticPropsを使うことで静的なHTMLを出力することができます ↩︎

Discussion