claude/skills/moonbit-luna-ui/SKILL.md
MoonBit + Luna UI (Sol Framework) でのWebアプリ開発。MoonBitコード、Solルーティング、Island Components、Server Actions、D1データベース、Cloudflare Workersデプロイに使用
npx skillsauth add kazuph/dotfiles moonbit-luna-uiInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
MoonBitとLuna UI (Sol Framework) を使用したCloudflare Workers向けWebアプリケーション開発のノウハウ。
→ ECOSYSTEM.mdを参照
Luna UI (Sol Framework) 以外にも選択肢があります:
FFIを自前で書く前に、必ず既存ライブラリを確認してください。
このスキルは Luna UI + Sol Framework を使う場合のガイドです。 シンプルなアプリや既存Viteプロジェクトには vite-plugin-moonbit を推奨します。
| 技術 | 役割 | |-----|------| | MoonBit | メイン言語(WASMターゲット、JSターゲット両対応) | | Luna UI | UIフレームワーク(Island Architecture) | | Sol Framework | ルーティング・SSR・Server Actions | | Cloudflare Workers | ランタイム | | D1 | SQLiteベースのデータベース | | Hono | HTTPミドルウェア(認証等) |
project/
├── app/
│ ├── server/
│ │ ├── routes.mbt # ルーティング・ページ・API定義
│ │ ├── db.mbt # D1 FFIバインディング
│ │ └── _using.mbt # 共通インポート
│ ├── client/
│ │ ├── *.mbt # Island Components
│ │ └── _using.mbt
│ └── __gen__/ # 自動生成(.gitignore推奨)
├── src/
│ └── worker.ts # Cloudflare Workerエントリーポイント
├── static/
│ └── loader.js # Luna UIハイドレーションローダー
├── scripts/
│ ├── patch-for-cloudflare.js # CF Workers用パッチ
│ └── bundle-client.js # クライアントバンドル
├── moon.mod.json # MoonBit設定
├── wrangler.json # Cloudflare設定
└── .sol/ # Sol生成物(.gitignore推奨)
プロジェクトでは just コマンドを使用して開発タスクを実行します。
# 主要コマンド
just dev # 開発サーバー起動(wrangler dev)
just build # 完全ビルド
just deploy # Cloudflare Workersにデプロイ
# ビルド関連
just generate # sol generate 実行
just moon-build # MoonBitビルド
just bundle # クライアントバンドル
just clean # ビルド成果物削除
# テスト関連
just test # MoonBit単体テスト実行
just test-e2e # E2Eテスト実行
just test-all # 全テスト実行
# SSG関連
just ssg # SSGビルド
just ssg-preview # SSGビルド + プレビュー
# 型チェック・リント
just check # moon check 実行
just fmt # moon fmt 実行
# 完全ビルド
pnpm build
# 内部で実行される処理:
# 1. sol generate - __gen__と.solを生成
# 2. moon build --target js - MoonBitをJSにコンパイル
# 3. patch-for-cloudflare.js - CF Workers用にパッチ
# 4. bundle-client.js - Island Componentsをバンドル
{
"build": {
"command": "pnpm build",
"watch_dir": ["src", "app"]
}
}
Sol Frameworkは静的サイト生成(SSG)と増分静的再生成(ISR)をサポート。
{
"ssg": {
"enabled": true,
"outDir": ".sol/static",
"routes": ["/", "/about", "/posts/*"]
},
"isr": {
"enabled": true,
"revalidate": 60
},
"metaFiles": {
"sitemap": true,
"rss": true,
"llmsTxt": true
}
}
# SSGビルド実行
sol build --ssg
# プレビュー
wrangler pages dev .sol/static
pub fn routes() -> Array[@router.SolRoutes] {
[
@router.SolRoutes::Page(
path="/posts/:slug",
[email protected](post_page),
title="Post",
meta=[],
revalidate=Some(60), // 60秒ごとに再生成
cache=None,
),
]
}
自動生成されるメタファイル。
// sol.config.json
{
"metaFiles": {
"sitemap": {
"enabled": true,
"hostname": "https://example.com",
"exclude": ["/admin/*", "/api/*"]
}
}
}
// sol.config.json
{
"metaFiles": {
"rss": {
"enabled": true,
"title": "My Blog",
"description": "技術ブログ",
"feedPath": "/feed.xml"
}
}
}
// sol.config.json
{
"metaFiles": {
"llmsTxt": {
"enabled": true,
"include": ["/docs/*", "/blog/*"],
"exclude": ["/admin/*"]
}
}
}
生成例:
# My Site
> サイトの説明
## Docs
- /docs/getting-started: Getting Started Guide
- /docs/api: API Reference
## Blog
- /blog/post-1: 記事タイトル1
- /blog/post-2: 記事タイトル2
Sol FrameworkのCSS最適化機能。
// sol.config.json
{
"css": {
"extract": true,
"minify": true,
"purge": true
}
}
// sol.config.json
{
"css": {
"splitting": true,
"chunks": {
"/": ["base", "home"],
"/posts/*": ["base", "posts", "markdown"]
}
}
}
// sol.config.json
{
"css": {
"inlineThreshold": 4096, // 4KB未満はインライン化
"criticalCss": true // Above-the-fold CSSを抽出
}
}
生成されるHTML:
<head>
<!-- クリティカルCSSはインライン -->
<style>/* critical styles */</style>
<!-- 非クリティカルは非同期ロード -->
<link rel="preload" href="/styles/chunk-posts.css" as="style">
</head>
テストファイルは *_test.mbt の命名規則。
// app/server/routes_test.mbt
test "parse_slug extracts correct value" {
let result = parse_slug("/posts/hello-world")
assert_eq!(result, Some("hello-world"))
}
test "home_page returns valid html" {
let ctx = mock_page_context("/")
let html = home_page(ctx)
assert_true!(html.contains("<h1>"))
}
実行:
moon test
# または
just test
// app/server/integration_test.mbt
test "api_create_post with valid data" {
let ctx = mock_api_context(
method="POST",
body="{\"title\": \"Test\", \"content\": \"Hello\"}",
)
let result = api_create_post(ctx)
assert_eq!(result.status, 201)
}
Playwrightを使用。
// e2e/posts.spec.ts
import { test, expect } from '@playwright/test';
test('create and view post', async ({ page }) => {
await page.goto('/posts/new');
await page.fill('[name="title"]', 'Test Post');
await page.fill('[name="content"]', 'Hello World');
await page.click('button[type="submit"]');
await expect(page).toHaveURL(/\/posts\/test-post/);
await expect(page.locator('h1')).toContainText('Test Post');
});
実行:
pnpm test:e2e
# または
just test-e2e
pub fn routes() -> Array[@router.SolRoutes] {
[
@router.SolRoutes::Page(
path="/",
[email protected](home_page),
title="Home",
meta=[], revalidate=None, cache=None,
),
@router.SolRoutes::Post(
path="/api/posts",
[email protected](api_create_post),
),
]
}
pub fn my_component(props : MyProps) -> DomNode {
let count = @signal.signal(0)
div(class="container", [
button(
on=events().click(fn(_) { count.set(count.get() + 1) }),
[text_of(count)]
)
])
}
let create_action : @action.ActionHandler = @action.ActionHandler(async fn(ctx) {
let body = ctx.body
let data = parse_json(body)
// 処理...
@action.ActionResult::ok({ message: "Success" })
})
pub fn action_registry() -> @action.ActionRegistry {
@action.ActionRegistry::new(allowed_origins=[
"http://localhost:8787",
"https://your-app.workers.dev",
]).register(
@action.ActionDef::new("create", create_action)
)
}
extern "js" fn db_query(sql : String) -> @core.Promise[@core.Any] =
#| async (sql) => {
#| const db = globalThis.__D1_DB;
#| return await db.prepare(sql).all();
#| }
→ action_registry()のallowed_originsに本番ドメインを追加
→ CSSクラス名がroutes.mbtのスタイル定義と一致しているか確認
→ scripts/patch-for-cloudflare.jsでCF Workers非互換コードをパッチ
→ wrangler.jsonにbuild.commandを設定
→ 重要: 日本語や絵文字を含むデータをIsland Componentに渡す場合、json_stringifyでASCII-safeなJSON生成が必須。Luna UIのluna:state属性エスケープ処理がUTF-16サロゲートペアを正しく処理できないため。詳細はPERFORMANCE.md参照
→ スケルトン(フォールバック)の高さを実際のコンテンツと近似させる。計算式: エントリー数 * 21px + 30px。詳細はPERFORMANCE.md参照
→ sol.config.jsonのssg.routesにワイルドカード(/posts/*)を使用している場合、動的ルートのスラッグ一覧を返す関数が必要
pub fn get_static_paths() -> Array[String] {
// DBまたはファイルシステムから全スラッグを取得
["post-1", "post-2", "post-3"]
}
→ SSGはビルド時のデータを使用。頻繁に更新されるデータにはISRを使用するか、クライアントサイドフェッチを組み合わせる
→ 確認事項:
revalidateがルート定義で設定されているかwrangler.jsonで設定されているかsol.config.jsonでisr.enabled: trueか→ 手動クリア方法:
# 特定ルートのキャッシュを削除
wrangler kv:key delete --binding=SOL_CACHE "/posts/slug-name"
# 全キャッシュをクリア
wrangler kv:bulk delete --binding=SOL_CACHE keys.json
→ sol.config.jsonでmetaFiles.sitemap.hostnameが設定されているか確認。ホスト名がないとsitemapは生成されない
→ css.splittingが有効な場合、チャンク定義が重複していないか確認。共通スタイルはbaseチャンクにまとめる
tools
X (Twitter) API read-only CLI. Bookmarks retrieval, tweet search, engagement analytics (likes/RT aggregation), mentions, user lookup. Use when: reading X bookmarks, searching tweets, aggregating likes/retweets, checking mentions, looking up users. Triggers: bookmark, bookmarks, X search, Twitter search, likes count, RT count, engagement, tweet analytics.
testing
単体テスト方針の要約。Kiro流で使うときは本文を必ず参照・展開する。
tools
Send prompts to other AI CLIs (Codex, Claude Code) running in sibling tmux panes and receive results back. Use this skill when the user asks to send a question or task to Codex or another Claude Code instance in a tmux pane. Handles pane discovery, CLI startup if needed, prompt delivery with proper Enter timing, delivery verification, and result return via tmux send-keys.
data-ai
TAKT ピースエンジン。Agent Team を使ったマルチエージェントオーケストレーション。ピースYAMLワークフローに従ってマルチエージェントを実行する。