Skill

moonbit-luna-ui

Install
1
Install the plugin
$
npx claudepluginhub kazuph/dotfiles --plugin kazuph-dotfiles

Want just this skill?

Add to a custom plugin, then install with one command.

Description

MoonBit + Luna UI (Sol Framework) でのWebアプリ開発。MoonBitコード、Solルーティング、Island Components、Server Actions、D1データベース、Cloudflare Workersデプロイに使用

Tool Access

This skill uses the workspace's default tool permissions.

Supporting Assets
View in Repository
CLOUDFLARE-DEPLOY.md
ECOSYSTEM.md
ISLAND-COMPONENTS.md
MOONBIT-FFI.md
PERFORMANCE.md
SERVER-ACTIONS.md
SOL-ROUTING.md
Skill Content

MoonBit + Luna UI 開発ガイド

MoonBitとLuna UI (Sol Framework) を使用したCloudflare Workers向けWebアプリケーション開発のノウハウ。

開発を始める前に必読

アプローチ選択

ECOSYSTEM.mdを参照

Luna UI (Sol Framework) 以外にも選択肢があります:

  • vite-plugin-moonbit: HMR対応、既存Viteプロジェクトへの統合向け
  • mizchi/js: JS FFIバインディング(必須ライブラリ
  • mizchi/npm_typed: Hono, Playwright等50+パッケージのバインディング

FFIを自前で書く前に、必ず既存ライブラリを確認してください。

本ドキュメントの対象

このスキルは Luna UI + Sol Framework を使う場合のガイドです。 シンプルなアプリや既存Viteプロジェクトには vite-plugin-moonbit を推奨します。

技術スタック概要

技術役割
MoonBitメイン言語(WASMターゲット、JSターゲット両対応)
Luna UIUIフレームワーク(Island Architecture)
Sol Frameworkルーティング・SSR・Server Actions
Cloudflare Workersランタイム
D1SQLiteベースのデータベース
HonoHTTPミドルウェア(認証等)

プロジェクト構成

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 コマンドを使用して開発タスクを実行します。

# 主要コマンド
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をバンドル

wrangler.jsonでの自動ビルド設定

{
  "build": {
    "command": "pnpm build",
    "watch_dir": ["src", "app"]
  }
}

SSG/ISR機能

Sol Frameworkは静的サイト生成(SSG)と増分静的再生成(ISR)をサポート。

sol.config.json設定

{
  "ssg": {
    "enabled": true,
    "outDir": ".sol/static",
    "routes": ["/", "/about", "/posts/*"]
  },
  "isr": {
    "enabled": true,
    "revalidate": 60
  },
  "metaFiles": {
    "sitemap": true,
    "rss": true,
    "llmsTxt": true
  }
}

SSGビルド

# SSGビルド実行
sol build --ssg

# プレビュー
wrangler pages dev .sol/static

ルート別ISR設定

pub fn routes() -> Array[@router.SolRoutes] {
  [
    @router.SolRoutes::Page(
      path="/posts/:slug",
      handler=@router.PageHandler(post_page),
      title="Post",
      meta=[],
      revalidate=Some(60),  // 60秒ごとに再生成
      cache=None,
    ),
  ]
}

ISRの動作

  1. 初回リクエスト: ページを生成しKVにキャッシュ
  2. revalidate期間内: キャッシュから即時応答
  3. revalidate期間後: バックグラウンドで再生成、古いキャッシュを返却
  4. 再生成完了後: 新しいコンテンツをキャッシュ

metaFiles機能

自動生成されるメタファイル。

sitemap.xml

// sol.config.json
{
  "metaFiles": {
    "sitemap": {
      "enabled": true,
      "hostname": "https://example.com",
      "exclude": ["/admin/*", "/api/*"]
    }
  }
}

RSS feed (feed.xml)

// sol.config.json
{
  "metaFiles": {
    "rss": {
      "enabled": true,
      "title": "My Blog",
      "description": "技術ブログ",
      "feedPath": "/feed.xml"
    }
  }
}

llms.txt

// 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

CSS Utilities機能

Sol FrameworkのCSS最適化機能。

自動CSS抽出

// sol.config.json
{
  "css": {
    "extract": true,
    "minify": true,
    "purge": true
  }
}

ページ別CSS分割

// 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>

テスト

MoonBit Unit テスト

テストファイルは *_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

Integration テスト

// 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)
}

E2E テスト

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

詳細リファレンス

クイックリファレンス

ルート定義(routes.mbt)

pub fn routes() -> Array[@router.SolRoutes] {
  [
    @router.SolRoutes::Page(
      path="/",
      handler=@router.PageHandler(home_page),
      title="Home",
      meta=[], revalidate=None, cache=None,
    ),
    @router.SolRoutes::Post(
      path="/api/posts",
      handler=@router.ApiHandler(api_create_post),
    ),
  ]
}

Island Component(クライアント)

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)]
    )
  ])
}

Server Action

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)
  )
}

D1 FFI

extern "js" fn db_query(sql : String) -> @core.Promise[@core.Any] =
  #| async (sql) => {
  #|   const db = globalThis.__D1_DB;
  #|   return await db.prepare(sql).all();
  #| }

よくある問題と解決策

403エラー(Server Actions)

action_registry()allowed_originsに本番ドメインを追加

CSSが適用されない(Island Component)

→ CSSクラス名がroutes.mbtのスタイル定義と一致しているか確認

ビルド後にモジュールエラー

scripts/patch-for-cloudflare.jsでCF Workers非互換コードをパッチ

デプロイ時にビルドされない

wrangler.jsonbuild.commandを設定

ハイドレーションエラー(entries_json undefined等)

重要: 日本語や絵文字を含むデータをIsland Componentに渡す場合、json_stringifyでASCII-safeなJSON生成が必須。Luna UIのluna:state属性エスケープ処理がUTF-16サロゲートペアを正しく処理できないため。詳細はPERFORMANCE.md参照

CLS(レイアウトシフト)が高い

→ スケルトン(フォールバック)の高さを実際のコンテンツと近似させる。計算式: エントリー数 * 21px + 30px。詳細はPERFORMANCE.md参照

SSGビルドでルートが見つからない

sol.config.jsonssg.routesにワイルドカード(/posts/*)を使用している場合、動的ルートのスラッグ一覧を返す関数が必要

pub fn get_static_paths() -> Array[String] {
  // DBまたはファイルシステムから全スラッグを取得
  ["post-1", "post-2", "post-3"]
}

SSGで動的データが古い

→ SSGはビルド時のデータを使用。頻繁に更新されるデータにはISRを使用するか、クライアントサイドフェッチを組み合わせる

ISRが再生成されない

→ 確認事項:

  1. revalidateがルート定義で設定されているか
  2. KVバインディングがwrangler.jsonで設定されているか
  3. sol.config.jsonisr.enabled: true

ISRキャッシュをクリアしたい

→ 手動クリア方法:

# 特定ルートのキャッシュを削除
wrangler kv:key delete --binding=SOL_CACHE "/posts/slug-name"

# 全キャッシュをクリア
wrangler kv:bulk delete --binding=SOL_CACHE keys.json

sitemap.xmlが生成されない

sol.config.jsonmetaFiles.sitemap.hostnameが設定されているか確認。ホスト名がないとsitemapは生成されない

CSSが重複してロードされる

css.splittingが有効な場合、チャンク定義が重複していないか確認。共通スタイルはbaseチャンクにまとめる

Stats
Stars15
Forks2
Last CommitJan 29, 2026
Actions

Similar Skills

cache-components

Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.

138.4k