mcpField
W
workleap-squide

workleap-squide

github.com/workleap開発842 回インストール6 日前に更新
リポジトリ

workleap-squide について

Squideはモジュール型アプリケーションシェルを構築するReactフレームワークです。複数のモジュールからホストアプリを組み立てるチーム向けに、ルート、ナビゲーション、グローバルデータ、遅延登録のAPIを提供します。

主な機能

  1. 01Fireflyランタイムの初期化
  2. 02モジュール単位のルートとナビゲーション登録
  3. 03公開ルートと保護ルートの分離
  4. 04公開・保護グローバルデータの取得
  5. 05ナビゲーション項目の遅延登録

活用シーン

  1. 01モジュールからホストアプリを構築
  2. 02公開ルートと保護ルートを表示
  3. 03取得データに応じたナビゲーション表示
  4. 04機能フラグ変更時のナビゲーション更新

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/workleap/wl-squide --skill workleap-squide

インストール後、Claude Code で/workleap-squideを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
components.md
getting-started.md
hooks-api.md
integrations.md
patterns.md
runtime-api.md

詳細

Squide フレームワーク

Squide は React のモジュラーアプリケーションシェルです。ドキュメントに記載された API のみを使用してください。

コアコンセプト

  • ランタイム:FireflyRuntime インスタンスは Squide アプリケーションの基盤です。直接インスタンス化せず、プラグイン、ロギング、モジュールのライフサイクルを設定する initializeFirefly() を使用してください。
  • モジュール登録:モジュールは登録関数を通じてルート、ナビゲーション項目、MSW ハンドラーを登録し、ホストが起動時にこれらを組み立てます。
  • パブリックルートと保護されたルート:ルートはデフォルトで protected(ProtectedRoutes の下にレンダリング)です。パブリックルートには registerPublicRoute() を使用してください。保護されたルートは、パブリックと保護された両方のグローバルデータを取得します。
  • 遅延登録:リモートデータやフィーチャーフラグに依存するナビゲーション項目では、2 段階登録を使用します。登録関数から関数を返し、2 段階目まで項目の登録を遅延させます。

主なパターン

ホストアプリケーションのセットアップ
// host/src/index.tsx
import { createRoot } from "react-dom/client";
import { FireflyProvider, initializeFirefly } from "@squide/firefly";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { App } from "./App.tsx";
import { registerHost } from "./register.tsx";

const runtime = initializeFirefly({
    localModules: [registerHost]
});

const queryClient = new QueryClient();
const root = createRoot(document.getElementById("root")!);

root.render(
    <FireflyProvider runtime={runtime}>
        <QueryClientProvider client={queryClient}>
            <App />
        </QueryClientProvider>
    </FireflyProvider>
);
// host/src/App.tsx
import { AppRouter, useIsBootstrapping } from "@squide/firefly";
import { createBrowserRouter, Outlet } from "react-router";
import { RouterProvider } from "react-router/dom";

function BootstrappingRoute() {
    if (useIsBootstrapping()) {
        return <div>Loading...</div>;
    }
    return <Outlet />;
}

export function App() {
    return (
        <AppRouter>
            {({ rootRoute, registeredRoutes, routerProps, routerProviderProps }) => (
                <RouterProvider
                    router={createBrowserRouter([{
                        element: rootRoute,
                        children: [{
                            element: <BootstrappingRoute />,
                            children: registeredRoutes
                        }]
                    }], routerProps)}
                    {...routerProviderProps}
                />
            )}
        </AppRouter>
    );
}
// host/src/register.tsx
import { PublicRoutes, ProtectedRoutes, type ModuleRegisterFunction, type FireflyRuntime } from "@squide/firefly";
import { RootLayout } from "./RootLayout.tsx";

export const registerHost: ModuleRegisterFunction<FireflyRuntime> = runtime => {
    runtime.registerRoute({
        element: <RootLayout />,
        children: [PublicRoutes, ProtectedRoutes]
    }, { hoist: true });

    // HomePage and NotFoundPage are local page components
    runtime.registerRoute({ index: true, element: <HomePage /> });
    runtime.registerPublicRoute({ path: "*", element: <NotFoundPage /> });
};
ナビゲーションのレンダリング

重要: RenderItemFunction のシグネチャは (item, key, index, level) => ReactNode、RenderSectionFunction のシグネチャは (elements, key, index, level) => ReactNode です。これらのシグネチャは固定されており、カスタムコンテキストパラメーターは受け付けませんが、引数が少ない場合もあります。外部の値にはクロージャーを使用してください。

重要: additionalProps はキーごとではなく、全体を展開してください。レンダラーが転送せずに読み取る必要のある値は、$context に格納し、context として公開してください。要素に渡さないように additionalProps からキーを分割代入してはいけません。そのために $context があります。この $context はレイアウト用のアイテムごとのデータであり、モジュール登録コンテキストでも React コンテキストでもありません。@squide/firefly 18.2.0-19.0.0(@squide/react-router 9.1.0-10.0.0)では $meta という名前でした。

import { Link, Outlet } from "react-router";
import {
    useNavigationItems, useRenderedNavigationItems, isNavigationLink,
    type RenderItemFunction, type RenderSectionFunction
} from "@squide/firefly";

// Signature: (item, key, index, level) => ReactNode
const renderItem: RenderItemFunction = (item, key, index, level) => {
    if (!isNavigationLink(item)) return null;
    const { label, linkProps, additionalProps } = item;
    return (
        <li key={key}>
            <Link {...linkProps} {...additionalProps}>{label}</Link>
        </li>
    );
};

// Signature: (elements, key, index, level) => ReactNode
const renderSection: RenderSectionFunction = (elements, key, index, level) => (
    <ul key={key}>{elements}</ul>
);

export function RootLayout() {
    const navigationItems = useNavigationItems();
    const navigationElements = useRenderedNavigationItems(navigationItems, renderItem, renderSection);
    return (
        <>
            <nav>{navigationElements}</nav>
            <Outlet />
        </>
    );
}
グローバルデータの取得
// Protected data
import { useProtectedDataQueries, useIsBootstrapping, AppRouter } from "@squide/firefly";

// ApiError and isApiError are app-specific; define them to match your API's error shape
function BootstrappingRoute() {
    const [session] = useProtectedDataQueries([{
        queryKey: ["/api/session"],
        queryFn: async () => {
            const response = await fetch("/api/session");
            if (!response.ok) throw new ApiError(response.status);
            return response.json();
        }
    }], error => isApiError(error) && error.status === 401);

    if (useIsBootstrapping()) return <div>Loading...</div>;

    return (
        <SessionContext.Provider value={session}>
            <Outlet />
        </SessionContext.Provider>
    );
}

// In App component, set waitForProtectedData
<AppRouter waitForProtectedData>...</AppRouter>
// Public data
const [data] = usePublicDataQueries([{ queryKey: [...], queryFn: ... }]);
<AppRouter waitForPublicData>...</AppRouter>
ナビゲーション項目の遅延登録
export const register: ModuleRegisterFunction<FireflyRuntime, unknown, DeferredRegistrationData> = runtime => {
    // Always register routes
    runtime.registerRoute({ path: "/feature", element: <FeaturePage /> });

    // Return function for deferred navigation items
    return (deferredRuntime, { userData }) => {
        if (userData.isAdmin && deferredRuntime.getFeatureFlag("enable-feature")) {
            deferredRuntime.registerNavigationItem({
                $id: "feature",
                $label: "Feature",
                to: "/feature"
            });
        }
    };
};
// Execute deferred registrations in BootstrappingRoute.
// Wrap in useMemo — without it, a new object reference each render re-triggers all deferred registrations.
const data = useMemo(() => ({ userData }), [userData]);
useDeferredRegistrations(data);

重要: 遅延登録関数は、更新のたびに再実行されます(フィーチャーフラグの変更または新しいデータの取得)。Squide は再実行前に前回の実行で登録されたものをすべて破棄するため、各実行ではレンダリングしたい項目の全セットを登録する必要があります。前回の実行との差分だけを登録してはいけません。Squide が破棄するのは、自身が所有するものだけです。プラグインが独自のレジストリをモジュールに公開する場合は、任意の Plugin.onDeferredRegistrationScopeStarted フックを使ってクリアする必要があります。プラグインが所有しないレジストリの場合は、runtime.registerDeferredRegistrationScopeStartedListener を使用してください。同じフック、同じオプション、同じ保証が適用されます(references/runtime-api.md を参照)。

関連項目: エラーバウンダリー、認証、テストパターン、高度なナビゲーション(マルチレベル、モジュラータブ、動的セグメント、アクティブ状態)については、references/patterns.md を参照してください。MSW のセットアップ、LaunchDarkly、Honeycomb、i18next、Storybook の統合については、references/integrations.md を参照してください。プラグインの作成とランタイム API の全容については、references/runtime-api.md を参照してください。

リファレンスガイド

上記のパターン以外の詳しい API ドキュメントについては、以下のリファレンスファイルを参照してください。

  • references/getting-started.md — Squide の概要と解決する問題、モジュール設計の原則、ホストアプリケーションとローカルモジュールを一から作成する方法(パッケージ、ファイル、Rsbuild の設定、テンプレート)
  • references/runtime-api.md — initializeFirefly のオプション、ルート登録オプション(hoist、parentPath、parentId)、ルートのプロパティ、ナビゲーション項目のプロパティ、ナビゲーション登録オプション(menuId、sectionId)、遅延登録スコープの開始リスナー、リクエストハンドラーの登録
  • references/hooks-api.md — Squide のすべてのフック:データ取得(usePublicDataQueries、useProtectedDataQueries)、ナビゲーション(レンダープロパティの型と canRender() を含む)、イベントバス、環境変数、機能フラグ、ロギング、ルーティング、i18next フック
  • references/components.md — AppRouter のプロパティ(strictMode と登録の検証を含む)、FireflyProvider、ヘルパー関数(isNavigationLink、resolveRouteSegments、mergeDeferredRegistrations)
  • references/patterns.md — ローカルモジュールのセットアップ、遅延登録の更新実行と保留中のセクション、エラーバウンダリー、認証、モジュール式タブ、MSW リクエストハンドラー、テスト(createDeferredRegistrationsRunner を含む)、その他の一般的なパターン
  • references/integrations.md — LaunchDarkly(プラグイン、ユーティリティ、テスト用クライアント)、Honeycomb、i18next、Storybook の統合に関する詳細

よくある落とし穴

スキルのメンテナーの方へ: このスキルを更新する前に、ODR-0008 を読んでください。本文は約 250 行未満に収める必要があります。新しい API の内容は、適切な references/ ファイルに記載してください。

Squide API を使用する際は、次のよくある間違いに注意してください。

  1. useRenderedNavigationItems の関数シグネチャ: 必ず (item, key, index, level) および (elements, key, index, level) にしてください。これらはカスタムコンテキストパラメーターを受け取りません。外部の値(ルートパラメーター、location など)が必要な場合は、クロージャーまたは React フックを使用してください。これらの関数にパラメーターを追加するよう提案してはいけません。

  2. アクティブ状態のスタイル設定: React Router の NavLink と、className/style のレンダー関数に渡される isActive 引数を使用してください(例:className={({ isActive }) => ... })。コンテキストパラメーターとして location/pathname を渡すよう提案してはいけません。

  3. 動的なルートセグメント: resolveRouteSegments ヘルパーをクロージャーとともに使用し、userId などの値をキャプチャしてください。パターンの例:RenderItemFunction を返す高階関数を作成します。

  4. 遅延登録のランタイムパラメーター: 遅延登録のコールバックは、最初の引数として deferredRuntime を受け取ります。これは外側の登録関数の runtime と同じものではありません。遅延コールバック内では、registerNavigationItem や getFeatureFlag などに必ず deferredRuntime を使用してください。

  5. 遅延 i18next インスタンス: モジュールの register() 関数から遅延 i18next インスタンスを登録してください(モジュール登録後は、遅延インスタンスに対する registerInstance は例外をスローします。静的インスタンスはいつでも登録できます)。静的リソースの場合、これまでどおり changeLanguage は await を付けずに呼び出してください。遅延切り替えが完了してから処理を続ける必要がある場合に限り、返された Promise を await し、エフェクトではブロック本体を使用してください(references/integrations.md を参照)。

コメント