Skip to content
Go back

Jiccup - ClojureのHiccupをJavaScriptで再現した軽量HTMLジェネレーター

はと🐤テック

Disclaimer: This article was written by AI and may contain inaccuracies or errors. 免責事項: この記事はAIによって作成されたものであり、不正確な情報やエラーが含まれている可能性があります。

Table of contents

Open Table of contents

はじめに

Jiccup という興味深いプロジェクトに出会った。

このライブラリは、ClojureのHiccupをJavaScriptで再現したものだ。Hiccupを知らない読者のために説明すると、これはClojureにおいて、データ構造を使ってHTMLを生成する優雅な手法である。テンプレート言語を使わず、純粋なコードだけでHTMLを構築できる。

;; Clojureの例
[:div {:class "container"}
  [:h1 "Hello"]
  [:p "World"]]

この思想をJavaScriptに持ち込んだのがJiccupというわけだ。正直なところ、最初は「また車輪の再発明か」と思ったのだが、実際に動かしてみると認識を改めざるを得なかった。

Jiccupとは

Jiccupは、配列を使ってHTMLを生成する軽量なJavaScriptライブラリである。最大の特徴はゼロ依存という点だ。外部ライブラリに一切依存せず、単一のファイル(約28KB)で完結している。

主な特徴

実際にコードを読んで確認した特徴を列挙する:

設計思想

コードを読み解くと、作者の実践的な知恵が随所に光っている。例えば、タグパースのキャッシュ機構は最大1000エントリに制限されており、メモリ使用量への配慮が見られる。また、ネストの深さを100階層に制限することで、スタックオーバーフローを防いでいる。

セットアップ

セットアップは実に簡単だ。単一のファイルをプロジェクトにコピーするだけで良い。

# リポジトリをクローン
git clone https://github.com/fanannan/Jiccup.git

# jiccup.jsをプロジェクトにコピー
cp Jiccup/jiccup.js ./your-project/

ES6モジュールとして使用する:

import { jiccup } from "./jiccup.js";

これだけである。npm installも、設定ファイルも不要だ。

実際に試してみる

理論よりも実践。実際にコードを書いて動作を確認してみた。

基本的な使い方

まずは最もシンプルな例から:

import { jiccup } from "./jiccup.js";

// 基本的なタグ
const html = jiccup.html(["div", "Hello World"]);
console.log(html);
// => <div>Hello World</div>

配列の最初の要素がタグ名、2番目以降がコンテンツとなる。実にシンプルだ。

IDとクラスの短縮記法

Hiccupの真骨頂とも言えるのが、この記法である:

// IDとクラスを同時に指定
jiccup.html(["div#main.container.active", "Content"]);
// => <div id="main" class="container active">Content</div>

// 順序は自由(CSSセレクタ風も可能)
jiccup.html(["div.container.active#main", "Content"]);
// => <div id="main" class="container active">Content</div>

実際に試してみて、この記法の便利さを実感した。HTMLを書くよりも遥かに速い。

属性の指定

属性はオブジェクトで指定する:

jiccup.html([
  "a",
  {
    href: "https://example.com",
    target: "_blank",
  },
  "Link",
]);
// => <a href="https://example.com" target="_blank">Link</a>

ネスト構造

HTMLの階層構造も、配列のネストで自然に表現できる:

jiccup.html([
  "div.container",
  ["h1", "タイトル"],
  ["p", "段落1"],
  ["ul", ["li", "アイテム1"], ["li", "アイテム2"]],
]);

私がこの記法を気に入った理由は、構造が一目で分かる点だ。開きタグと閉じタグの対応を気にする必要がない。

スタイルの指定

CSS-in-JSも巧妙に実装されている:

jiccup.html([
  "div",
  {
    style: {
      color: "red",
      fontSize: "16px",
      backgroundColor: "#f0f0f0",
    },
  },
  "Styled text",
]);
// => <div style="color: red; font-size: 16px; background-color: #f0f0f0">Styled text</div>

camelCaseが自動的にkebab-caseに変換される。実装を見ると、正規表現で大文字を検出し、ハイフンとlowerCaseに変換している。シンプルだが効果的だ。

関数コンポーネント

個人的に最も魅力を感じたのが、この機能だ:

const Card = ({ title, content }) => [
  "div.card",
  ["h3", title],
  ["p", content],
];

jiccup.html([Card, { title: "Hello", content: "World" }]);
// => <div class="card"><h3>Hello</h3><p>World</p></div>

Reactのような関数コンポーネントを、フレームワークなしで実現している。実装を確認したところ、第一要素が関数かどうかを判定し、関数であればpropsとchildrenを渡して実行しているだけだ。実にシンプルで美しい。

条件付きレンダリング

falsyな値は自動的に除外される:

const showTitle = false;
jiccup.html([
  "div",
  showTitle && ["h1", "Title"], // falseなので無視される
  ["p", "Always shown"],
]);
// => <div><p>Always shown</p></div>

ただし、nullやundefinedは明示的にエラーを投げる。これは意図しないバグを防ぐための設計だろう。

リスト処理

配列のmapと組み合わせると、動的なリスト生成が簡単だ:

const items = ["Apple", "Banana", "Orange"];
jiccup.html(["ul", items.map(item => ["li", item])]);
// => <ul><li>Apple</li><li>Banana</li><li>Orange</li></ul>

HTMLエスケープ(セキュリティ)

XSS対策も忘れていない:

jiccup.html(["div", '<script>alert("XSS")</script>']);
// => <div>&lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;</div>

デフォルトで全てエスケープされる。これは正しい判断だ。

イベントハンドリング

renderメソッドを使うと、イベントハンドラーも設定できる:

const result = jiccup.render([
  "button",
  {
    "on:click": e => console.log("クリック!"),
    "on:mouseover": e => console.log("ホバー!"),
    class: "btn",
  },
  "Click me",
]);

// HTMLを取得
console.log(result.html);
// => <button class="btn" data-jiccup-id="jiccup-1">Click me</button>

// DOMに適用してイベントをバインド
result.attach("#app");

実装を見ると、on:プレフィックスを持つ属性を検出し、別途バインディング情報として保存している。DOMに適用する際に、data-jiccup-id属性を使って要素を特定し、addEventListenerでバインドする仕組みだ。

実際に試してみたところ、これが実に便利だった。

let count = 0;

const updateCounter = () => {
  const result = jiccup.render([
    "div",
    ["p", `カウント: ${count}`],
    [
      "button",
      {
        "on:click": () => {
          count++;
          updateCounter();
        },
      },
      "カウントアップ",
    ],
  ]);

  result.attach("#app");
};

updateCounter();

パフォーマンス検証

理論的な美しさだけでなく、実用性も重要だ。いくつかのパフォーマンステストを実施した。

大量要素の生成

1000個のリストアイテムを生成:

const largeList = jiccup.html([
  "ul",
  Array.from({ length: 1000 }, (_, i) => ["li", `Item ${i + 1}`]),
]);

結果: 6ms

十分に高速だ。

複雑なコンポーネント

500個の複雑なカードコンポーネントを生成:

const ComplexCard = ({ id, title, description, tags }) => [
  "div.card",
  {
    id: `card-${id}`,
    style: {
      border: "1px solid #ddd",
      padding: "15px",
      margin: "10px",
      backgroundColor: "#f9f9f9",
    },
  },
  ["h3", title],
  ["p", description],
  ["div.tags", tags.map(tag => ["span.tag", tag])],
];

const cards = Array.from({ length: 500 }, (_, i) => [
  ComplexCard,
  {
    id: i,
    title: `Card ${i + 1}`,
    description: `説明文 ${i + 1}`,
    tags: ["tag1", "tag2", "tag3"],
  },
]);

jiccup.html(["div.card-container", cards]);

結果: 30ms、出力サイズ: 266.80 KB

これも実用的な速度だ。

キャッシュの効果

同じタグ文字列を10000回パース:

for (let i = 0; i < 10000; i++) {
  jiccup.html(["div#main.container.active", "Content"]);
}

結果: 22ms

1回あたり0.0022msという計算になる。キャッシュ機構が効果的に働いている証拠だ。

実践的な例

最後に、実際のアプリケーションで使えそうな例を示す:

const UserCard = ({ name, email, active }) => [
  "div.user-card",
  {
    class: active ? "active" : "",
    style: {
      border: "1px solid #ddd",
      padding: "15px",
      margin: "10px 0",
      backgroundColor: active ? "#e8f4f8" : "#fff",
    },
  },
  ["h4", { style: { margin: "0 0 10px 0" } }, name],
  ["p", { style: { color: "#666", margin: "0" } }, email],
  active && ["span.badge", { style: { color: "#27ae60" } }, "✓ Active"],
];

const users = [
  { name: "山田太郎", email: "[email protected]", active: true },
  { name: "佐藤花子", email: "[email protected]", active: false },
  { name: "鈴木一郎", email: "[email protected]", active: true },
];

const userList = jiccup.html([
  "div.user-list",
  ["h2", "ユーザー一覧"],
  users.map(user => [UserCard, user]),
]);

このコードは実際に動作し、期待通りのHTMLを生成する。

どのような場面で使えるか

私の経験から、Jiccupが活きる場面をいくつか挙げる:

  1. 軽量なWebアプリケーション - フレームワーク不要で動的UIを構築したい時
  2. サーバーサイドレンダリング - Node.jsでHTMLを生成する時
  3. メール生成 - HTMLメールのテンプレートを生成する時
  4. 静的サイトジェネレーター - 軽量なSSGを自作する時
  5. 教育目的 - フレームワークの内部動作を学ぶ教材として

特に、「ちょっとした動的UI」を作りたいが、「Reactを導入するほどではない」という場面で重宝するだろう。

気づいた点と考察

良い点

注意点

完璧ではないが、それがまた良い。完璧を求めすぎると、かえって本質を見失うものだ。

結論

Jiccupは、HiccupのエレガントさをJavaScriptに持ち込んだ、実に興味深いライブラリだ。

大規模なSPAには向かないかもしれないが、軽量なプロジェクトや、サーバーサイドでのHTML生成には最適だろう。何より、依存性ゼロという点が素晴らしい。

このライブラリを使っていると、プログラミングの原初的な楽しさを思い出す。配列とオブジェクトだけで、HTMLという構造を美しく表現できる。これはまさに、データ構造とアルゴリズムの力だ。

さて、また一つ面白いツールを私の道具箱に加えることになりそうだ。

興味を持った読者は、ぜひ自分の手で試してみることをお勧めする。きっと新しい発見があるはずだ。

参考リンク

検証環境

本記事で使用したパッケージのバージョン:


Share this post on:

Previous Post
tailspin - ログファイルに色彩を宿す小さな魔法
Next Post
人格付与