🔱

お手軽にヘッドレスCMSの静的サイトを作れる spear 🔱 の紹介

2023/08/25に公開

Spear とは?

Spearly CMS というヘッドレス CMS 向けに作られた SSG です。

https://github.com/unimal-jp/spear/

ソースコードは全て公開されていて、だれでも自由にご利用いただけます。

なぜ作ったのか?

ユニマルという会社で埋め込み JavaScript の開発をしている mantaroh です。
普段は Astro 翻訳など OSS 活動をしています。

以前から、Spearly CMS というサービス向けに以下のような構文で CMS を簡単に埋め込めるようにしています。

sample.html
<div cms-loop cms-content-type="blog">
	<h1>{%= blog_title %} </h1>
</div>

このような HTML を書くと、以下のように CMS からデータを取得して置換してくれるものです。

sample-result.html
<div>
	<h1>記事1</h1>
	<h1>記事2</h1>
	<h1>記事3</h1>
</div>

ただ、この JavaScript ライブラリはレンダリング時に動的に CMS コンテンツを差し込んでいるので実運用で以下のような苦労ポイントがありました。

  • SNS シェアボットはレンダリングを待ってくれないので、OGP を出すためにサーバーレス関数を通してUAで切り分けた応答を返す等インフラ側で頑張る必要がある
  • sitemap を生成できない

クライアントレンダリングなので、特に SEO 関係で弱い部分がありました。

そこで SSG ツールを作りたいと思い spear を作ったのが経緯です。

なぜ自作?

世の中には Astro を代表として多くの 優秀な SSG が沢山あります。けど作っちゃいました。

Spearly CMS という製品は 埋込タグで導入できる世界で一番簡単なヘッドレスCMS と謳っているように、特殊な知識なくとも利用できるヘッドレスCMSを目指しています。

作るに至った背景は色々とあるのですが、私の中では Spearly CMS が使われるであろう制作現場で長く安定して使える SEO に強い SSG ツールを提供したいという思いが一番の決め手だったと思います。

色んな制作会社さんの話を聞いていると、WordPress をまだ使う理由は慣れている事に加え、安定しているという意見を多く聞きました。

  • 使い慣れていること
    • 人間の真意だと思います。一度覚えて仕事ができるようになった環境を捨ててまで魅力がある製品があれば別ですが、そうじゃない、新しそうだからという理由だけでは誰も使ってくれません。
    • Spear は HTML と CSS、JavaScript の知識だけあれば構築可能な簡単なフレームワークを目指しています。
    • また、ファイル構成も固定ページやテーマなどの概念と同じレベルで作れるのも特徴です。
  • 安定していること
    • 一度サイト運用を始めると数年(下手すると5年以上)は運用され続けます。その間メンテンナンス費用が出ればいいですが、出ないことも多々あります。そのような制作環境では長期運用にも耐えられる必要があります。
    • 流行りのライブラリは機能追加が早く、破壊的な更新も多くあります。現場で息の長い作品を作るには不向きな状況もあって、使うに至らないケースもあります。
    • Spear は下位互換を意識して、破壊的な変更をしないように目指しています。

このような特徴を持つ SSG なので、Spear は他の多機能な SSG フレームワークとは少し異なるフレームワークです。

機能

SSG

CMS からコンテンツを取得して静的ページとして生成します。
構文は Spearly CMS の埋め込みJavaScriptで利用している埋め込み用の Django ライクな構文を利用して埋め込みを指定します。

example
<div> {%= blog_title %}

構文は後ほど説明しますが、{%= <コンテンツ名>_<フィールド名> %} となるのが基本構文となっています。

コンポーネント機能

コンポーネントをファイルに分けて記述が出来ます。React / Vue などを使われた人はわかりやすいと思いますが、シンプルなコンポーネント機能を提供しています。

sample.html
<body>
	<header-component></header-component>
	<p>だよ</p>
</body>
header-component.spear
<header>ヘッダー</header>

ビルドすると以下のような HTML が生成されます。

sample-result.html
<body>
	<header>ヘッダー</header>
	<p>だよ</p>
</body>

コンテンツ取得

標準で SpearlyCMS のコンテンツを取得しますが、今後コンテンツコレクションを導入して、Spearly CMS 以外からもコンテンツを取得できるようにする予定です。

オプションとしてコンテンツ取得の関数をプラグインで組むことで、他のCMSから取得したり、ローカルファイルから取得することができるようになります。

SASS 機能

SASS で記述されたファイルがあれば、ビルド時にコンパイルしてくれます。
ファイル名で .scss が付いていると自動でコンパイル&バンドルしてくれます。

サイトマップ生成

サイトマップを自動で生成する簡単な機能です。
生成後の HTML をサイトマップ XML に書き出してくれます。

SEO タグ機能

SEO タグを用いることで、CMS から取得したデータをもとに自動で SEO 関連のタグを生成してくれます。
コンテンツごとに出力されるページは独自構文を利用することで、タイトルなどを可変にすることができます。

sample.spear
<div>
  <spear-seo
    title="{%= #seo_title %}"
    meta-description="meta description"
  ></spear-seo>
</div>

i18n (国際化)機能

i18n ファイルを用意すると、ビルド時に言語別のサイトを構築します。
i18n.yaml という設定ファイルを用意し、ファイル内に言語データを用意します。

i18n.yaml
- default: jp
- falllback: jp
- lang:jp
  - welcome: ようこそ!
- lang:en
  - welcome: Welcome!
- lang:de
  - welcome: Willkommen

このファイルは言語別に、キー・値の組み合わせです。このファイルを元にHTMLに以下の構文があるとファイルから置き換えます。

sample.html
<h1>{%= localize(welcome) %}</h1>
<!-- or シンプルに以下のように省略可能 -->
<h1>{%= l(welcome) %}</h1>
<!-- or HTML 属性として記載可能(この場合、子要素が全て置き換えられます。) -->
<h1 i18n="welcome"></h1>

HookAPI / プラグイン

Spear は追加機能をプラグインという形で提供しています。i18n / SEO タグ も組み込みのプラグインという形で提供しています。

プラグインは Hook API を利用して、Spear のビルド時に処理を注入できます。

API 実行タイミング 補足
configuration 設定を読み込み完了時 戻り値で設定を変更可能
beforeBuild ビルド実行前(パースファイルをロードした状態) 戻り値でファイルを変更可能
afterBuild ビルド実行後(静的ファイルを生成した後) 戻り値でファイルを変更可能
bundle ファイルを書き出し前 戻り値でファイルを変更可能

この API を使ったプラグインを利用するには、spear.config.mjs 内で指定します。

spear.config.mjs
import { confFunc, beforeBuildFunc, afterBuildFunc, bundleFunc } from './sample-plugin.js'

export default {
  plugins: [
    {
      configuration: confFunc,
      beforeBuild: beforeBuildFunc,
      afterBuild: afterBuildFunc,
      bundle: bundleFunc,
    }
  ]
}

プラグイン側で設定を呼び出すための初期化関数を作成すると便利です。i18n / SEO タグ は初期化関数を提供しているため、以下のように設定を簡略化できます。

spear.config.mjs
import { spearI18n } from '@spearly/spear-cli/dist/plugins/i18n.js'
export default {
  plugins: [
    spearI18n('./18n.yaml'),
  ]
}

組み込み構文

組み込みの構文はシンプルです。{%= %} の間に記載された文字が組み込み構文として解釈されます。

CMS コンテンツデータの置換構文

CMS から取得するデータを置換する場合は、そのデータのコンテンツ名と、コンテンツ内のフィールドを指定します。

基本構文
{%= <content>_<field> %}
{%= blog_title %}

CMS コンテンツの表示方法

1. リスト表示

リスト表示は、コンテンツのまとまりを一覧に表示する方法です。例えば、CMS でブログというコンテンツを管理している場合、そのブログの一覧を表示する方法です。

Spear では cms-loop という属性をHTML要素に追加することで、その要素内をコンテンツの数だけループします。

loop-sample.html
<li>
  <ul cms-loop cms-content-type="blog">
    <p>{%= blog_title %}</p>
  </ul>
</li>

上記例では、ulタグにループ指定があるので結果として以下のように表示されます。

loop-result.html
<li>
  <ul>
    <p>記事1</p>
  </ul>
  <ul>
    <p>記事2</p>
  </ul>
  <ul>
    <p>記事n</p>
  </ul>
</li>

2. コンテンツ単体表示

コンテンツを1つだけ指定して表示する方法です。例えば CMS の1つのコンテンツをページ内に固定として表示させたい時に利用します。(例:利用規約や会社沿革など)

Spear では cms-itemcms-content という属性をHTML要素に追加することで、cms-content で指定したコンテンツを埋め込みます。

item-sample.html
<div>
  <article cms-item cms-content-type="policy" cms-content="main-policy">
    <p>{%= policy_content %}</p>
  </article>
</div>

上記例では、CMS

item-sample.html
<div>
  <article cms-item cms-content-type="policy" cms-content="main-policy">
    <p>{%= policy_content %}</p>
  </article>
</div>

使い方

利用は簡単で以下のコマンドでプロジェクトを作成することができます。

$ npm create spear@latest

ビルド方法

ビルドも簡単に以下のコマンドでビルドできます。

$ cd <project>
$ yarn install
$ yarn build

また、開発モードも用意していて、以下のコマンドを利用するとライブサーバーが起動して、編集と同時に再ビルドされます。

$ yarn dev

ディレクトリ構造

spear プロジェクトのディレクトリ構造は以下のようになっています。

├── package.json
├── spear.config.js
├── dist
├── src
│   ├── assets
│   │   ├── fonts
│   │   │   └── OpenSans-Regular.ttf
│   │   └── main.css
│   ├── components
│   │   ├── header-component.spear
│   │   └── main-component.spear
│   ├── images
│   │   └── logo.png
│   ├── index.html
│   └── pages
│       └── index.spear
└── vscodeSettings.json
ファイル/ディレクトリ 説明
spear.config.js spear ビルドに関する設定ファイルです
src/components コンポーネントファイルを記載できます
src/pages 実際のページをここに配備します
src/assts アセットファイルを配備します
src/images 画像ファイルを配備します
dist/ ビルド結果がここに配備されます

src/components だけは固定のパスとなっている特殊なディレクトリです。それ以外のディレクトリは spear ビルド時に dist ディレクトリへ出力されます。

今後の予定

これからも Spear は機能追加をしていく予定です。

  • ページネーション
  • コンポーネント間のプロパティ渡し
  • 条件付きレンダリング

Discussion