ブートストラップのシーケンス
Web ホストは設定を受け取った後、UI をレンダリングする前に決まった初期化シーケンスを実行します。シーケンスは、Web ホストがページを引き継ぐ JS モジュールとして読み込まれるか(標準のファサード経路)、iframe の内側で動くか(手動のファサードなし経路)によって少し異なりますが、設定が利用可能になった後の内部ステップは同一です。
経路 A — JS モジュール(標準、ファサード経路)
これは現在の wippy/facade が使う経路です。ファサードは、Web ホストの JS モジュールエントリ — compat モードなら module.js、managed モードなら managed-layout.js — を読み込むページを配信し、そのモジュールがページ全体とブラウザー履歴を引き継ぎます。
-
設定を取得して map を登録する。 facade の requirement 名は
import_mapで、/facade/configはcfg.importMapとして返します。shell は共有 import-map bootstrap を読み込み、Host module を import する前に合成済み map を登録します。 -
Host module を import してアプリを初期化する。 shell は
module.jsまたはmanaged-layout.jsを import します。module はwindow.initWippyAppを公開します。その後、importMap: cfg.importMapを含む初期AppConfigを使ってinitWippyApp(appConfig, rootContainer?)を呼び出します。PostMessage handshake はありません。 -
初期化が進む — 下記の 内部初期化シーケンス を参照してください。
経路 B — iframe(手動、ファサードなし)
これは、ホスト全体を自分で iframe に埋め込む場合の経路です — より強い分離を伴う部分埋め込みのためのものです。iframe.html?waitForCustomConfig を読み込み、SetConfig の PostMessage で設定を受け取ります。現在のファサードはこれを生成しません。手動での挿入のために存在します。
-
親が iframe を準備する。 versioned
iframe.htmlURL に?waitForCustomConfigを付けます。iframe.srcを設定する前にmessagelistener を登録します。standalone bootstrap は、import map の登録や Host app module の import より前に、親からのSetConfigを待ちます。これは Host app の mount より前です。 -
親が
GetConfigに応答する。 iframe からのget-configを待ちます。event.originが信頼する iframe origin と一致し、event.sourceが正確にiframe.contentWindowの場合だけ受け入れます。次に完全なAppConfigをset-configmessage で送り、信頼済み origin をtargetOriginに指定します。/facade/configは deployment 設定を提供しますが、親は$schema、auth、contextを追加する必要があります。詳細はiframe の例 を参照してください。 -
Web Host が
AppConfigを受け取る。 message envelope を検証し、iframe message は物理的な親 window からのものだけを受け付けます。親の origin にアクセスできる場合はevent.originも照合します。Web Fragment では、その occurrence 自身の ID とfragmentIdが一致することも確認します。後続の有効なSetConfigmessage は、その document の config を更新できます。 -
初期化が進む — この時点から先の内部経路は経路 A と同一です。
内部初期化シーケンス
(どちらの経路であれ)AppConfig が利用可能になると、Web ホストは次のステップを順に実行します。
1. Pinia ストアの初期化。
ルートの Pinia インスタンスが作成され、すべてのストアモジュールが登録されます。認証状態は AppConfig.auth から読み込まれます — トークンはメモリに保存されます(hostConfig.session.type = 'cookie' の場合は cookie に保存されます)。AppConfig.env の環境 URL は、Axios と WebSocket クライアントが使えるようストアへ書き込まれます。
2. Axios の設定。
Axios インスタンスは APP_API_URL を baseURL として設定され、認証トークンがデフォルトヘッダーとして注入されます。設定にある axiosDefaults はマージされます。子の iframe がプロキシ API 経由で受け取るのは、このインスタンスです。
3. Vue Router の初期化。
ルーターは AppConfig.hostConfig.history("hash" または "browser")で指定された履歴モードで作成されます。システムルート(/c/:id、/chat/:id、/keeper/:id など)が登録されます。これは静的な集合であり、動的なマウントルートは後のステップで追加されます。
4. PrimeVue とテーマの注入。
PrimeVue が Vue アプリにインストールされます。AppConfig.theming.global と AppConfig.theming.host の CSS カスタムプロパティが、適切なスコープに対する :root { --key: value; } のオーバーライドとして注入されます。theming.global と theming.host の customCSS 文字列は <style> タグとして注入され、theming.global / theming.host のアイコンは Iconify へ登録されます。このステップはアプリのマウント前に適用されるため、最初のレンダリングから正しいテーマになります。
5. Vue アプリのマウント。
ルートの App.vue コンポーネントが DOM へマウントされます。この時点でユーザーはクローム — サイドバー、チャットパネル、レイアウトのスケルトン — を目にしますが、ページのコンテンツはまだ読み込み中の場合があります。
6. 動的ルートの登録。
アプリは GET /api/public/pages/routes を呼び、登録済みのビューページの一覧を取得します。レジストリエントリが mountRoute を宣言している各ページについて、router.addRoute('app', ...) を呼んでライブのルーターへルートを追加します。app という名前付きルートは、すべてのコンテンツを包む親レイアウトのルートです。
この段階でマウントルートに競合(パスの重複、予約セグメント、不正な構文)があると、pages ストアに致命的エラーが設定されます。App.vue がこれを検出し、通常の UI の代わりに説明的なメッセージ付きの全画面 <wippy-error> を描画します。
7. URL の解決。
ルーターが現在の URL を解決します(ブラウザー履歴モードでは window.location から、ハッシュモードではハッシュから)。URL がシステムルートまたは登録済みのマウントルートに一致すれば、対応するページが描画されます。どのルートにも一致しない場合、ルーターはチャットのホームビューへフォールバックします。
8. WebSocket 接続。
WebSocket クライアントが認証トークンを使って APP_WEBSOCKET_URL へ接続します。リアルタイムイベント(受信メッセージ、セッションの更新、アーティファクトの状態変化)が流れ始めます。接続はページの生存期間中維持されます。
AppConfig の TypeScript インターフェース
initWippyApp と SetConfig の双方が受け付ける完全な設定の型です。AppConfig に feature フィールドと fe_mode フィールドは存在しない点に注意してください — fe_mode はモジュールエントリを選択するファサードの要件パラメーターであり、managed モードは hostConfig.layout を通じてホストへ伝えられます。
interface AppConfig {
$schema: 'wippy-context-2.2'
auth: AppAuthConfig
env: AppEnv
axiosDefaults?: Partial<AxiosDefaults>
routePrefix?: string
apiRoutes?: ApiRoutesOverride
tanstack?: TanstackConfig // TanStack Query のデフォルト(グローバル + ロールベースのカテゴリーごと)
theming: AppTheming
allowSelectModel?: boolean
hideSessionSelector?: boolean
allowAdditionalTags?: Record<string, string[]>
hostConfig: HostConfig
context: AppContext
}
interface AppAuthConfig {
token: string // Bearer トークン
expiresAt: string // ISO 8601 の有効期限タイムスタンプ
}
interface AppEnv {
APP_API_URL: string
APP_AUTH_API_URL: string
APP_WEBSOCKET_URL: string
[key: string]: string | undefined
}
interface AppTheming {
global?: ThemingScope
host?: ThemingScope
children?: ThemingScope
}
interface ThemingScope {
customCSS?: string
cssVariables?: Record<string, string>
icons?: Record<string, unknown>
iconSets?: Record<string, Record<string, unknown>>
}
interface HostConfig {
session?: { type: 'non-persistent' | 'cookie' }
history?: 'browser' | 'hash'
showAdmin?: boolean
allowSelectModel?: boolean
startNavOpen?: boolean
hideNavBar?: boolean
disableRightPanel?: boolean
hideSessionSelector?: boolean
additionalNavItems?: PageApi.Page[]
stateCache?: { maxPages?: number; maxSizePerPage?: number }
allowAdditionalTags?: Record<string, string[]> // タグ → 許可される属性
chat?: {
convertPasteToFile?: {
enabled: boolean
minFileSize: number
allowHtml: boolean
}
}
layout?: HostLayoutDeclaration
}
// TanStack Query のデフォルト。トップレベルのフィールドで、apiRoutes と同様に
// ホストと子で共有される。設定がない場合のデフォルト挙動は
// refetchOnWindowFocus: false であり、alt-tab で戻っても読み込み中の
// コンテンツが再読み込みされない。
interface TanstackConfig {
default?: TanstackQueryOptions // グローバルなクエリのデフォルトを上書きする
content?: TanstackQueryOptions // 単一リソースの描画(page/artifact/session/entry/model/upload)
lists?: TanstackQueryOptions // ナビゲーション/インデックス/一覧のクエリ
}
// TanStack のクエリオプションのうち JSON で安全な部分集合(関数はなし — 設定は JSON)。
interface TanstackQueryOptions {
refetchOnWindowFocus?: boolean
refetchOnReconnect?: boolean
refetchOnMount?: boolean
staleTime?: number
gcTime?: number
retry?: boolean | number
refetchInterval?: number | false
}
interface AppContext {
resourceId: string
resourceType: 'page' | 'artifact'
route?: string
[key: string]: unknown
}
設定のソースと優先順位
Web ホストは複数のソースから設定を解決します。優先度の低いものから高いものへ順に示します。
- 組み込みのデフォルト — Web ホストのバンドル自体に定義されています。
- URL クエリパラメーター —
?token=<token>、?expiresAt=<timestamp>、cookie セッション用の?persist。親ページなしで直接開発アクセスするのに便利です。 initWippyApp()の引数 — 標準のファサード(JS モジュール)経路。URL パラメーターより優先されます。- PostMessage の
SetConfig— 手動のファサードなし iframe 経路。?waitForCustomConfigがある場合に使われます。
実際には、本番のデプロイでは常に initWippyApp()(ファサード経路)または PostMessage(手動の iframe 埋め込み)を使います。URL パラメーターは、トークン付きでホストをブラウザーへ直接読み込むための開発上の便宜です。
ブートストラップの図
標準のファサード(JS モジュール)経路:
facade shell が /facade/config を取得(requirement import_map → cfg.importMap)
│
├─ Host map を取得し共有 import-map bootstrap を読み込む
├─ 合成済み import map を登録する
├─ module.js / managed-layout.js を import する
├─ module が window.initWippyApp を公開する
├─ shell が initWippyApp({ ..., importMap: cfg.importMap }, '#app') を呼ぶ
├─ resolveConfig() → config/auth/env state を migrate・normalize する
├─ GET /api/public/pages/routes を待つ
├─ Vue app と router を作成する
│ static system routes + validated backend mount routes
├─ setupApp() → Pinia、Axios、PrimeVue、theme などを設定する
├─ App.vue を mount し、現在の URL を解決する
└─ component が必要に応じて WebSocket client を要求する
関連項目
- ファサードのエントリーポイント —
wippy/facadeがAppConfigをどう構築し配信するか - マルチパネルレイアウト —
managed-layout.jsが配信するマネージドレイアウトのブート経路 - レンダリングエンジン — 読み込み後にページがどう描画されるか(srcdoc iframe と Web Fragment)
Iconify ソース
AppConfig.iconify.providers で Iconify ソースを設定します。省略時はオンラインの既定値を使います。明示的に設定した値は AppConfig を通じて子アプリに渡されます。Iconify プロバイダーを参照してください。
PrimeVue とブラウザー Realm
アプリケーションは固定された Host import map の正確な指定子から PrimeVue を import します。再ビルドした利用側は、JavaScript realm 内の共有 PrimeVue vendor graph を使用します。各 iframe と Web Fragment は独自の realm と module graph を 持ちます。import map は style を挿入しません。必要な PrimeVue CSS は文書化された Host CSS key から要求してください。既存の組み込み bundle は、新しい map に 合わせて再ビルドする必要があります。
AppConfig import map
AppConfig.importMap は、標準の imports と scopes を持つ任意のトップレベルブラウザー import map です。
interface AppConfig {
importMap?: {
imports?: Record<string, string>
scopes?: Record<string, Record<string, string>>
} | null
}
srcdoc ページのドキュメントでは、Host はページの map、生成された Host の既定値、AppConfig.importMap の順で合成します。standalone と facade のドキュメントには srcdoc ページの map がないため、Host の既定値と AppConfig.importMap を合成します。imports と各 scope では一致するキーの最後の値が優先され、他のエントリーは保持されます。設定は Vue、PrimeVue、Wippy を含むページまたは Host の割り当てを置き換えられます。完全一致キーは1つのspecifierに一致します。末尾が / のキーはspecifierのprefixに一致し、対象URLも / で終わる必要があります。scopes は import 元モジュールのURLで割り当てを選びます。相対URLはドキュメントのベースURLから解決されます。
モジュールを読み込む前に map を作成し、ネイティブ import map を登録してください。後から別の map を追加しても登録済みのキーは置き換えられません。更新で importMap を省略すると現在の拡張を維持します。null または {} を指定すると拡張を解除します。
import map の更新は document 作成時に適用されます。既存 document の module 解決は変わりません。更新を使うにはその document を再読み込みしてください。後から作成する document には最新の config が使われます。Web Fragment における AppConfig.importMap と scopes のサポートは保留中です。wippy/views 0.5.12 では、合成 map が Fragment の module 読み込み前に登録される保証はありません。
Vue、PrimeVue、Wippy の共有エントリーを置き換えると、Host と子コードで module や service の identity が分かれることがあります。置き換えを公開する前に、公開済みの正確な map で統合を確認してください。
AppConfig.importMap の直接利用について確認済みの Web Host リリース基準は 1.0.64 です。
UI policy in 1.0.62
Web Host 1.0.62 は次のチャット設定を子アプリケーションと共有します。hostConfig は Web Host ラッパー専用であり、子へのデータには含まれません。管理レイアウトには標準のチャットがありません。子として動作するアプリは、トップレベルの設定またはパッケージの上書きを使用します。
// Shared reader: host override, shared root field, then default.
const value = config.hostConfig?.[key] ?? config[key] ?? fallback
// Child override in package.json (no hostConfig in child payloads).
const configOverrides = {
allowSelectModel: false,
hideSessionSelector: true,
allowAdditionalTags: { svg: ['viewBox', 'preserveAspectRatio'], path: ['d'] },
}
WC の明示的な属性がインスタンス単位で優先されます。show-selector は hideSessionSelector の反転値を上書きします。設定がない WC のセレクターは非表示のままです。組み込みホストチャットの既定値は表示です。属性がない場合は設定を継承します。false と {} は明示的な値です。タグマップは既存の設定を置き換え、組み込みタグは維持します。読み込みに成功した定義だけが、そのスコープに属性を登録します。 タグ許可リストは AppConfig と読み込みに成功したコンポーネントから取得します。WC 属性による上書きはありません。
既存の SetConfig は内部イベント configUpdated を通じてランタイム設定を更新します。Proxy API は @theme や @history と同様に、通知を on('@config', callback) として公開します。注入された設定と公開設定はコールバックの前に更新されます。独立したバンドルのチャット WC はこの購読を使い、注入された生の設定でストアを初期化します。config.auth の直接読み取りはサポートされ、警告は出ません。通常の認証済み HTTP リクエストには api を使ってください。新しい PostMessage アクションや childrenConfig は追加しません。従来の feature 形式とスキーマ 2.0 も対応します。SVG の viewBox と preserveAspectRatio を維持し、スクリプト、イベント属性、安全でない URL は除去します。