はじめに
Constelaは、UIをJavaScriptではなくJSONで記述するUI言語です。
見出し、テキスト、ボタン、状態、イベントといったUI要素を、すべて構造化されたJSONとして定義し、コンパイルによってWebページを生成します。開発者がUIを書くためにJavaScriptを書く必要はありません。
Constelaは、UIを「コード」ではなく検証可能なデータとして扱います。
そのため、UIの構造不整合、未定義の状態参照、不正な更新操作などを実行前に検出できます。
JSONという制約された形式により、UI表現の表面積は有限で静的に解析可能です。
この設計は、人間だけでなく生成AIがUIを生成・修正するワークフローにも適しています。
なぜJSONでUIを書くのか?
1. コンパイル時にエラーを検出できる
React/TypeScriptでは、型エラーはTypeScriptが検出しますが、UIの論理的なエラー(存在しない状態への参照、無効なイベントハンドラ等)は実行時まで発見できません。
Constelaでは、すべてのエラーがコンパイル時に検出されます:
{
code: 'UNDEFINED_STATE',
message: 'State "count" is not defined',
path: '/view/children/0/props/onClick' // エラー箇所の正確な位置
}
2. 状態管理が宣言的で予測可能
React の場合:
const [count, setCount] = useState(0);
const [loading, setLoading] = useState(false);
const handleClick = async () => {
setLoading(true);
// どこでsetCountが呼ばれるかわからない
// 複数のuseEffectが絡み合う可能性
setCount(prev => prev + 1);
setLoading(false);
};
Constela の場合:
{
"state": {
"count": { "type": "number", "initial": 0 },
"loading": { "type": "boolean", "initial": false }
},
"actions": [
{
"name": "increment",
"steps": [
{ "do": "set", "target": "loading", "value": { "expr": "lit", "value": true } },
{ "do": "update", "target": "count", "operation": "increment" },
{ "do": "set", "target": "loading", "value": { "expr": "lit", "value": false } }
]
}
]
}
Constelaでは、状態の型と初期値が明示的に宣言され、アクションは「何をするか」のステップが配列として並びます。実行パスが一目瞭然です。
3. AI生成に適した制約付きDSL
ReactのJSXは、任意のJavaScriptを埋め込めます。これはAIがUIを生成する際にセキュリティリスクとなります。
Constelaは制約されたDSLです:
- 式は13種類(
lit,state,var,bin,not,param,cond,get,route,import,data,ref,index) - 演算子は安全なセットのみ(
+,-,*,/,==,!=,<,<=,>,>=,&&,||) - 任意のJavaScript実行は不可能
この制約により、AIは構造的に正しいUIを安全に生成できます。
Constelaはどんな場面で意味を持つのか?
Constelaは、従来の「人間が手書きするUI開発」を主な対象としていません。その価値が最もはっきり現れるのは、UIが人間だけでなく、機械によって生成・検証・修正されるワークフローです。
このモデルが特に適している具体的な場面は以下です。
AIによるUI生成ワークフロー
AI(あるいは各種ツール)によってUIが生成される場合、JavaScriptやJSXは自由度が高すぎ、わずかなミスでも問題が実行時にしか表面化しないことが多くあります。
Constelaは、制約された有限のDSLを採用しているため、生成されたUIをコンパイル時に検証することができます。
これにより、UIの自動生成・再生成・反復的な修正を、より安全に行うことが可能になります。
UIをデータとして扱うシステム(CMS / フォームビルダー / 管理画面)
UIを保存・差分管理・バージョン管理・レビュー対象として扱うシステムでは、UIを構造化されたJSONとして表現することに明確な利点があります。
Constelaでは、UI定義を次のように扱うことができます。
- 差分が取りやすい
- スキーマによる検証が可能
- 静的解析ができる
命令的なコードの中にUIが埋もれてしまうことがありません。
安全性や予測可能性が重要な環境
Constelaは、任意のJavaScript実行を意図的に避けています。状態更新や副作用は、限定された操作セットとして表現されます。
そのため、UIの振る舞いが予測しやすく、柔軟性よりも正しさや安全性が重視される環境に適しています。
Constelaは、次の一つの考え方を軸に設計されています。
UIを「とりあえず動くコード」ではなく、機械が理解し、検証できる対象として扱うこと。
計測結果:Constela と Next.js の比較
Constelaの公式サイトを、Constela版とNext.js版の両方で実装し、各種指標を比較しました。
測定は複数回行い、いずれも一貫した傾向が確認できています。
結果
| 指標 | Constela | Next.js | 差分 |
|---|---|---|---|
| ビルド時間 | 2.2秒 | 12.3秒 | 約5.6倍高速 |
| node_modules サイズ | 297MB | 794MB | 約2.7倍軽量 |
| 出力サイズ | 14MB | 72MB | 約5.1倍軽量 |
| デプロイ時間 | 10秒 | 50秒 | 約5.0倍高速 |
なぜこの差が生まれるのか
この結果は偶然の最適化ではなく、設計上の違いによるものです。
-
Next.js はフルスタックなアプリケーションフレームワークであり、ビルド時にルーティング解析、バンドル最適化、各種ランタイム設定などを行います。
-
Constela はコンパイラファーストなUI言語であり、UIをJSONとして検証・解析し、最小限のランタイム出力に直接コンパイルします。
その結果として、
- ビルド工程が本質的に単純になる
- 依存関係が小さく予測可能になる
- 出力される成果物が最小限になる
といった特性が自然に得られます。
これらは、CI/CDパイプラインやVercelのようなホスティング環境において、ビルド時間やデプロイ時間の短縮として現れます。
注意:パフォーマンスはConstelaの主目的ではありません。中核となる価値は、コンパイル時検証と安全なUI生成です。軽量なビルド・デプロイ特性は、その設計の結果として得られるものです。
コード例
カウンターアプリ
{
"version": "1.0",
"state": {
"count": { "type": "number", "initial": 0 }
},
"actions": [
{
"name": "increment",
"steps": [
{ "do": "update", "target": "count", "operation": "increment" }
]
},
{
"name": "decrement",
"steps": [
{ "do": "update", "target": "count", "operation": "decrement" }
]
}
],
"view": {
"kind": "element",
"tag": "div",
"children": [
{
"kind": "element",
"tag": "h1",
"children": [
{ "kind": "text", "value": { "expr": "lit", "value": "Constela Counter" } }
]
},
{
"kind": "element",
"tag": "div",
"children": [
{ "kind": "text", "value": { "expr": "state", "name": "count" } }
]
},
{
"kind": "element",
"tag": "div",
"children": [
{
"kind": "element",
"tag": "button",
"props": { "onClick": { "event": "click", "action": "decrement" } },
"children": [
{ "kind": "text", "value": { "expr": "lit", "value": "-" } }
]
},
{
"kind": "element",
"tag": "button",
"props": { "onClick": { "event": "click", "action": "increment" } },
"children": [
{ "kind": "text", "value": { "expr": "lit", "value": "+" } }
]
}
]
}
]
}
}
Todoリストでのループ
{
"version": "1.0",
"state": {
"todos": { "type": "list", "initial": [] },
"newTodo": { "type": "string", "initial": "" }
},
"actions": [
{
"name": "addTodo",
"steps": [
{
"do": "update",
"target": "todos",
"operation": "push",
"value": { "expr": "state", "name": "newTodo" }
},
{ "do": "set", "target": "newTodo", "value": { "expr": "lit", "value": "" } }
]
}
],
"view": {
"kind": "element",
"tag": "ul",
"children": [
{
"kind": "each",
"items": { "expr": "state", "name": "todos" },
"as": "item",
"body": {
"kind": "element",
"tag": "li",
"children": [
{ "kind": "text", "value": { "expr": "var", "name": "item" } }
]
}
}
]
}
}
非同期データ取得
{
"version": "1.0",
"state": {
"users": { "type": "list", "initial": [] },
"loading": { "type": "string", "initial": "idle" },
"error": { "type": "string", "initial": "" }
},
"actions": [
{
"name": "fetchUsers",
"steps": [
{ "do": "set", "target": "loading", "value": { "expr": "lit", "value": "loading" } },
{
"do": "fetch",
"url": { "expr": "lit", "value": "https://api.example.com/users" },
"method": "GET",
"result": "data",
"onSuccess": [
{ "do": "set", "target": "users", "value": { "expr": "var", "name": "data" } },
{ "do": "set", "target": "loading", "value": { "expr": "lit", "value": "done" } }
],
"onError": [
{ "do": "set", "target": "error", "value": { "expr": "var", "name": "error" } },
{ "do": "set", "target": "loading", "value": { "expr": "lit", "value": "error" } }
]
}
]
}
],
"view": {
"kind": "element",
"tag": "div",
"children": []
}
}
React/Next.js との比較
| 観点 | React/TypeScript | Constela |
|---|---|---|
| 言語 | JavaScript/JSX | JSON DSL |
| 実行モデル | ランタイム駆動 | コンパイラ駆動 |
| 型安全性 | TypeScript(実行時に消える) | JSONスキーマ(コンパイル時検証) |
| 状態更新 | 任意の関数 | 宣言的アクション |
| エラー | ランタイム例外 | 構造化エラー(位置情報付き) |
| AI生成 | 安全でない(任意JS可能) | 安全(制約付きDSL) |
| デバッグ | クロージャの検査 | アクションステップを読む |
| Markdown/Code | 依存関係追加が必要 | ビルトイン(Shiki対応) |
| SSR | 複雑(Next.jsセットアップ) | ファーストクラス対応 |
| 決定性 | 複雑(再レンダリング問題) | 保証(コンパイラ検証) |
ビルトイン機能
Constelaには以下の機能が標準で含まれています:
-
Markdown レンダリング:
{ "kind": "markdown", "content": ... } - シンタックスハイライト: Shikiによるデュアルテーマ対応
-
ファイルベースルーティング:
index.json,users/[id].json,docs/[...slug].json - SSR/SSG: サーバーサイドレンダリングとハイドレーション
- データローディング: ビルド時にファイル、API、MDXからデータ取得
始め方
# プロジェクト作成
mkdir my-app && cd my-app
npm init -y
npm install @constela/start
mkdir -p src/routes
# src/routes/index.json にページを作成後...
# 開発サーバー起動
npx constela dev
# プロダクションビルド
npx constela build
まとめ
Constelaは、UIを「プログラム」ではなく「データ」として扱う新しいアプローチです。
- コンパイル時検証: 実行前にエラーを発見
- 宣言的状態管理: 予測可能で追跡しやすい
- AI親和性: 制約付きDSLで安全なUI生成
- シンプルなメンタルモデル: JSONを書く、コンパイルする、動く
まだスタートしたばかりのプロジェクトで、ようやくConstela公式サイトがReact/Next.jsを使わず、Constelaで作ることができるようになった状態です。改善のため皆様のフィードバックがいただけると嬉しいです。
公式サイト: https://constela-dev.vercel.app/
リポジトリ: https://github.com/yuuichieguchi/constela