引导序列
Web Host 收到配置之后,会在渲染任何 UI 之前运行一段固定的初始化序列。该序列略有差异,取决于 Web Host 是作为接管页面的 JS 模块加载(标准 facade 路径),还是运行在 iframe 内(手动、无 facade 的路径),但配置可用之后的内部步骤是完全相同的。
路径 A —— JS 模块(标准,facade 路径)
这是当前 wippy/facade 所使用的路径。facade 提供一个页面,该页面加载 Web Host 的 JS 模块入口——compat 模式为 module.js,managed 模式为 managed-layout.js——该模块接管整个页面及其浏览器历史。
-
获取配置并注册映射。 facade requirement 名称为
import_map;/facade/config将其作为cfg.importMap返回。shell 加载共享 import-map bootstrap,并在导入 Host 模块前注册组合后的映射。 -
导入 Host 模块并初始化应用。 shell 导入
module.js或managed-layout.js,该模块会提供window.initWippyApp。随后 shell 使用初始AppConfig调用initWippyApp(appConfig, rootContainer?),其中包含importMap: cfg.importMap。此路径没有 PostMessage 握手。 -
初始化继续进行 —— 参见下文的内部初始化序列。
路径 B —— iframe(手动,无 facade)
当你自己把完整宿主嵌入 iframe 时走这条路径——用于隔离性更强的局部页面嵌入。它加载 iframe.html?waitForCustomConfig,并通过 SetConfig PostMessage 接收配置。当前的 facade 不会产生这条路径;它是为手动插入而存在的。
-
父级准备 iframe。 给带版本的
iframe.htmlURL 添加?waitForCustomConfig。设置iframe.src前先安装messagelistener。standalone bootstrap 会先等待父级SetConfig,再注册 import map 并导入 Host 应用模块。这发生在 Host 应用挂载之前。 -
父页面响应
GetConfig。 等待 iframe 发出的get-config消息。仅当event.origin等于可信 iframe 来源且event.source正是iframe.contentWindow时才接受。然后发送包含完整AppConfig的set-config消息,并将可信来源作为targetOrigin。/facade/config可提供部署设置,但父页面仍须添加$schema、auth和context。完整流程见手动 iframe 示例。 -
Web Host 收到
AppConfig。 它会验证消息 envelope,iframe 消息只接受来自物理父 window 的内容;当父级 origin 可访问时,还会将event.origin与其比较。在 Web Fragment 中,还要求fragmentId与该 occurrence 自己的 ID 匹配。之后有效的SetConfig消息可以更新该文档的 config。 -
初始化继续进行 —— 从这里开始内部路径与路径 A 完全相同。
内部初始化序列
AppConfig 可用之后(无论经由哪条路径),Web Host 按顺序执行以下步骤:
1. Pinia store 初始化。
创建根 Pinia 实例并注册所有 store 模块。认证状态从 AppConfig.auth 加载——令牌保存在内存中(如果 hostConfig.session.type = 'cookie' 则保存在 cookie 中)。AppConfig.env 中的环境 URL 写入 store,供 Axios 和 WebSocket 客户端使用。
2. Axios 配置。
Axios 实例以 APP_API_URL 作为 baseURL 配置,并把认证令牌注入为默认请求头。配置中的任何 axiosDefaults 都会被合并进来。子 iframe 通过 proxy API 收到的就是这个实例。
3. Vue Router 初始化。
路由器按 AppConfig.hostConfig.history 指定的 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 store 上设置致命错误。App.vue 检测到后会渲染带描述性消息的全屏 <wippy-error>,而不是常规 UI。
7. URL 解析。
路由器解析当前 URL(浏览器 history 模式下来自 window.location,hash 模式下来自 hash)。如果 URL 匹配系统路由或已注册的挂载路由,就渲染相应页面。如果不匹配任何路由,路由器回退到聊天主页视图。
8. WebSocket 连接。
WebSocket 客户端使用认证令牌连接到 APP_WEBSOCKET_URL。实时事件(新消息、会话更新、产物状态变更)开始流动。该连接在页面生命周期内保持。
AppConfig TypeScript 接口
initWippyApp 和 SetConfig 都接受的完整配置类型。注意 AppConfig 中没有 feature 字段,也没有 fe_mode 字段——fe_mode 是选择模块入口的 facade 需求参数,而 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 Host 从多个来源解析配置,按优先级从低到高:
- 内置默认值 —— 定义在 Web Host bundle 自身之中。
- URL 查询参数 ——
?token=<token>、?expiresAt=<timestamp>、用于 cookie 会话的?persist。适合在没有父页面的情况下直接进行开发访问。 initWippyApp()参数 —— 标准 facade(JS 模块)路径;优先级高于 URL 参数。- PostMessage
SetConfig—— 手动、无 facade 的 iframe 路径,在存在?waitForCustomConfig时使用。
实践中,生产部署总是使用 initWippyApp()(facade 路径)或 PostMessage(手动 iframe 嵌入)。URL 参数是一种开发便利,用于带令牌直接在浏览器中加载宿主。
引导流程图
标准 facade(JS 模块)路径:
facade shell 获取 /facade/config(requirement import_map → cfg.importMap)
│
├─ 获取 Host map 并加载共享 import-map bootstrap
├─ 注册组合后的 import map
├─ 导入 module.js / managed-layout.js
├─ module 提供 window.initWippyApp
├─ shell 调用 initWippyApp({ ..., importMap: cfg.importMap }, '#app')
├─ resolveConfig() → 迁移、规范化并填充 config/auth/env state
├─ 等待 GET /api/public/pages/routes
├─ 创建 Vue app 和 router
│ static system routes + validated backend mount routes
├─ setupApp() → 配置 Pinia、Axios、PrimeVue、theme 等 provider
├─ 挂载 App.vue → 解析当前 URL
└─ consuming component 按需请求 WebSocket client
另请参阅
- Facade 入口点 ——
wippy/facade如何构造并交付AppConfig - 多面板布局 —— 由
managed-layout.js提供的受管布局启动路径 - 渲染引擎 —— 页面加载后如何渲染(srcdoc iframe 与 Web Fragment)
Iconify 源
AppConfig.iconify.providers 用于配置 Iconify 源。省略此字段时使用在线默认值。显式配置的值会通过 AppConfig 传给子应用。请参阅 Iconify 提供方。
PrimeVue 与浏览器 realm
应用通过固定 Host import map 中的确切说明符导入 PrimeVue。重新构建的消费者会在 自己的 JavaScript realm 中使用共享 PrimeVue vendor 图。每个 iframe 和 Web Fragment 都有独立的 realm 与模块图。import map 不会注入样式;请通过文档化的 Host CSS 键 单独请求所需的 PrimeVue CSS。已经嵌入依赖的消费者 bundle 需要使用新映射重新构建。
AppConfig import map
AppConfig.importMap 是可选的顶层浏览器导入映射,使用标准 imports 和 scopes 字段。
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,同名键采用最后一个值,其他条目保留。配置可以替换页面或 Host 映射,包括 Vue、PrimeVue 和 Wippy。精确键匹配一个 specifier。以 / 结尾的键匹配该 specifier 前缀,目标也必须以 / 结尾。scopes 根据导入模块 URL 选择映射。相对目标 URL 根据文档 base URL 解析。
Host 必须先组合映射并注册原生 import map,再加载模块。之后追加另一张映射无法替换已注册的键。更新时省略 importMap 会保留当前扩展;设为 null 或 {} 会清除扩展。
import map 更新在创建文档时生效。它不会改变现有文档的模块解析。重新加载该文档后才能使用更新;之后创建的文档会使用最新配置。Web Fragment 的 AppConfig.importMap 和 scopes 支持暂缓处理:wippy/views 0.5.12 不保证在加载 Fragment 模块前注册组合后的映射。
替换共享的 Vue、PrimeVue 或 Wippy 条目可能导致 Host 与子代码使用不同的模块或服务实例。部署覆盖项前,请用已发布的准确映射测试这些集成。
直接使用 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。