Sol Framework
実験的: Solは開発中です。APIは変更される可能性があります。
SolはLuna UIとMoonBitで構築されたフルスタックSSRフレームワークです。Island Architectureによるサーバーサイドレンダリングと部分的ハイドレーションを提供します。
特徴
Hono統合 - 高速で軽量なHTTPサーバー
Island Architecture - スマートなトリガーで最小限のJavaScriptを配信
ファイルベースルーティング - ディレクトリ構造からページとAPIルートを生成
型安全 - MoonBitの型がサーバーからブラウザまで流れる
ストリーミングSSR - 非同期コンテンツのストリーミング対応
CSRナビゲーション -
data-sol-linkによるSPAライクなページ遷移ミドルウェア - Railway Oriented Programmingベースのミドルウェア
Server Actions - CSRF保護付きのサーバーサイド関数
ネストされたレイアウト - 階層的なレイアウト構造
ドキュメント/SSG - Sol SSG単体サイト、または
staticDirsによるハイブリッド
プロジェクト構造
myapp/
├── moon.mod.json # MoonBitモジュール
├── package.json # npmパッケージ
├── sol.config.json # Sol設定
├── app/
│ ├── server/ # サーバーコンポーネント
│ │ ├── moon.pkg
│ │ └── routes.mbt # routes() + config() + ページ
│ ├── client/ # クライアントコンポーネント (Islands)
│ │ ├── moon.pkg
│ │ └── counter.mbt
│ └── __gen__/ # 自動生成 (sol generate)
└── static/
└── loader.js # Islandローダー
CLIリファレンス
sol new <name>
新規プロジェクトを作成。
sol new myapp --user mizchi # mizchi/myapp パッケージを作成
sol new myapp --user mizchi --dev # ローカル luna パスを使用
sol dev
開発サーバーを起動。以下を自動実行:
sol generate --mode dev- コード生成moon build- MoonBitビルドrolldown- クライアントバンドルサーバー起動
sol dev # デフォルトポート 7777
sol dev --port 8080 # ポート指定
sol dev --clean # キャッシュクリアしてビルド
sol build
本番用ビルド。.sol/prod/に出力。
sol build # JSターゲット (デフォルト)
sol build --target wasm # WASMターゲット
sol build --clean # キャッシュクリアしてビルド
sol serve
本番ビルドを配信。sol buildが必要。
sol serve # デフォルトポート 7777
sol serve --port 8080 # ポート指定
SolRoutes定義
@solヘルパー関数による宣言的なルート定義:
pub fn routes() -> Array[@sol.SolRoutes] {
[
// ページルート
@sol.route("/", home_page, title="Home"),
// GET APIルート
@sol.api_get("/api/health", api_health),
// ネストされたレイアウト
// segment="/admin" + path="/" => /admin
@sol.wrap("/admin", admin_layout, [
@sol.route("/", admin_dashboard, title="Admin"),
]),
// ミドルウェア適用
@sol.with_mw([@middleware.cors(), @middleware.logger()], [
@sol.api_get("/api/data", api_data),
]),
]
}
ルートヘルパー関数
| 関数 | 説明 |
|---|---|
@sol.route(path, handler, title=...) | ページルート(HTMLレスポンス) |
@sol.api_get(path, handler) | GET APIルート(JSONレスポンス) |
@sol.api_post(path, handler) | POST APIルート(JSONレスポンス) |
@sol.api_put(path, handler) | PUT APIルート(JSONレスポンス) |
@sol.api_delete(path, handler) | DELETE APIルート(JSONレスポンス) |
@sol.wrap(segment, layout, children) | ネストされたレイアウトグループ |
@sol.with_mw(middleware, children) | ミドルウェアを適用したルートグループ |
@sol.nodes(content) | ノードからsync ServerNodeを作成 |
ミドルウェア
Railway Oriented Programmingベースのミドルウェアシステム。
基本的な使い方
let middleware = @middleware.logger()
.then(@middleware.cors())
.then(@middleware.security_headers())
@sol.with_mw([middleware], [
@sol.api_get("/api/data", get_data),
])
組み込みミドルウェア
| ミドルウェア | 説明 |
|---|---|
logger() | リクエストログ |
cors() | CORSヘッダー |
csrf() | CSRF保護 |
security_headers() | セキュリティヘッダー |
nosniff() | X-Content-Type-Options |
frame_options(value) | X-Frame-Options |
Server Actions
CSRF保護付きのサーバーサイド関数。
let submit_handler = @action.ActionHandler(async fn(ctx) {
let body = ctx.body
@action.ActionResult::ok(@js.any({ "success": true }))
})
pub fn action_registry() -> @action.ActionRegistry {
@action.ActionRegistry::new(allowed_origins=["http://localhost:7777"])
.register(@action.ActionDef::new("submit-form", submit_handler))
}
ActionResultタイプ
| タイプ | 説明 |
|---|---|
Success(data) | 成功、JSONデータを返す |
Redirect(url) | クライアントサイドリダイレクト(JSON形式で返す) |
HttpRedirect(url) | HTTPリダイレクト(302ステータスとLocationヘッダー) |
ClientError(status, msg) | クライアントエラー (4xx) |
ServerError(msg) | サーバーエラー (5xx) |
Islandコンポーネント
Islandはサーバーとクライアントで共有されるコンポーネント:
pub fn counter(count : @signal.Signal[Int]) -> @luna.Node[CounterAction] {
div(class="counter", [
span(class="count-display", [text_of(count)]),
button(onclick=@luna.action(Increment), [text("+")]),
])
}
Islandの埋め込み(サーバーサイド)
ComponentRefベースのAPIで型安全にIslandを埋め込みます。sol generateがクライアントのProps型からファクトリ関数を自動生成します。
// 自動生成: app/__gen__/types/types.mbt
pub struct CounterProps { initial_count : Int } derive(ToJson, FromJson)
pub fn counter(props : CounterProps, trigger~ : @luna.Trigger) -> @luna.ComponentRef[CounterProps]
推奨: @sol.island()推奨: + ComponentRef
// app/server/home.mbt
let counter_props : @types.CounterProps = { initial_count: 42 }
@sol.island(
@types.counter(counter_props),
[div([button([text("Count: 42")])])], // SSRフォールバック
)
代替: @server_dom.client()代替: (同等)
@server_dom.client(
@types.counter(counter_props),
[div([text("Loading...")])],
)
低レベル: @sol.island_raw()低レベル: (文字列ベース、非推奨)
@sol.island_raw("counter", "/static/counter.js", props_json, children)
Hydrationトリガー
| トリガー | 説明 |
|---|---|
Load | ページロード時即座 |
Idle | requestIdleCallback時 |
Visible | IntersectionObserver検知時 |
Media(query) | メディアクエリマッチ時 |
None | 手動トリガー |
CSRナビゲーション
data-sol-link属性を持つリンクはCSRで処理されます。sol_linkヘルパを使用:
sol_link(href="/about", [text("About")])
クリック時の動作:
sol-nav.jsがクリックをインターセプトfetchで新しいページのHTMLを取得<main id="main-content">の内容を置換History APIでブラウザ履歴を更新
新しいページのIslandをhydrate
ストリーミングSSR
ServerNode::async_を使用した非同期コンテンツのストリーミング:
@server_dom.ServerNode::async_(async fn() {
let data = fetch_data() // 非同期関数呼び出し
div([text(data)])
})
register_sol_routes でストリーミング応答を使う場合は、RouterConfig で有効化します(デフォルトは無効)。
pub fn config() -> @sol.RouterConfig {
@sol.RouterConfig::default()
.with_default_head(head())
.with_loader_url("/static/loader.js")
.with_streaming_ssr()
}
注: ストリーミング応答は
__LUNA_MAIN__を含む場合root_templateを使い、見つからない場合は組み込み page shell にフォールバックします。
モード
Solは3つの使い方を想定しています。
アプリ (デフォルト)
Islands付きSSRアプリ
moon.mod.jsonが必要sol devでアプリサーバー起動
SSGのみ(ドキュメント)
sol.config.jsonにssgまたはdocsセクションがあり、かつmoon.mod.jsonが無い場合に検出されます。
sol new my-docs --ssg
sol dev # HMR付きSSG開発サーバー
sol build # 静的サイト生成
sol lint # SSGコンテンツをリント
SSG固有の機能と設定については、Sol SSGを参照してください。
ハイブリッド(アプリ + ドキュメント)
アプリを維持したまま、staticDirsでドキュメントをマウントします:
{
"staticDirs": [
{ "path_prefix": "/docs", "source_dir": "docs", "title": "Docs" }
]
}
sol buildでアプリとドキュメントをまとめてビルドsol devはアプリサーバーを起動(ドキュメントのプレビューはsol dev --mode ssg)