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)で完結している。
主な特徴
実際にコードを読んで確認した特徴を列挙する:
- ゼロ依存性 - 外部ライブラリ不要
- 軽量 - jiccup.jsは28KB程度
- TypeScript対応 - JSDocアノテーションによる型情報
- XSS保護 - 自動HTMLエスケープ
- CSS-in-JS - camelCaseからkebab-caseへの自動変換
- 関数コンポーネント - 再利用可能なコンポーネント定義
- イベントハンドリング -
on:プレフィックスでの自動バインディング - パフォーマンス最適化 - タグパースのキャッシュ機構
設計思想
コードを読み解くと、作者の実践的な知恵が随所に光っている。例えば、タグパースのキャッシュ機構は最大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><script>alert("XSS")</script></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が活きる場面をいくつか挙げる:
- 軽量なWebアプリケーション - フレームワーク不要で動的UIを構築したい時
- サーバーサイドレンダリング - Node.jsでHTMLを生成する時
- メール生成 - HTMLメールのテンプレートを生成する時
- 静的サイトジェネレーター - 軽量なSSGを自作する時
- 教育目的 - フレームワークの内部動作を学ぶ教材として
特に、「ちょっとした動的UI」を作りたいが、「Reactを導入するほどではない」という場面で重宝するだろう。
気づいた点と考察
良い点
- シンプルさ - コアな概念が少なく、学習コストが低い
- 依存性ゼロ - プロジェクトの依存関係を増やさない
- 可読性 - HTMLよりもコード的に書ける
- セキュリティ - デフォルトでXSS保護
- パフォーマンス - 実用的な速度
注意点
- エコシステムの小ささ - ReactやVueのような巨大なエコシステムはない
- デバッガー対応 - React DevToolsのような開発ツールはない
- 学習曲線 - Hiccup記法に慣れる必要がある
- 仮想DOM - 差分更新の仕組みは自分で実装する必要がある
完璧ではないが、それがまた良い。完璧を求めすぎると、かえって本質を見失うものだ。
結論
Jiccupは、HiccupのエレガントさをJavaScriptに持ち込んだ、実に興味深いライブラリだ。
大規模なSPAには向かないかもしれないが、軽量なプロジェクトや、サーバーサイドでのHTML生成には最適だろう。何より、依存性ゼロという点が素晴らしい。
このライブラリを使っていると、プログラミングの原初的な楽しさを思い出す。配列とオブジェクトだけで、HTMLという構造を美しく表現できる。これはまさに、データ構造とアルゴリズムの力だ。
さて、また一つ面白いツールを私の道具箱に加えることになりそうだ。
興味を持った読者は、ぜひ自分の手で試してみることをお勧めする。きっと新しい発見があるはずだ。
参考リンク
検証環境
本記事で使用したパッケージのバージョン:
- Node.js: 23.1.0
- Jiccup: 最新版 (2024年時点の main ブランチ)