Прокси и изоляция
Web Host запускает каждый дочерний микрофронтенд в изолированном контексте и связывает его с хостом через Proxy API. И микрофронтенд-приложения, и веб-компоненты обращаются к хосту, импортируя из @wippy-fe/proxy.
Proxy API
Proxy API — ваша точка входа к хосту. Его доставляет среда выполнения proxy.js: она размещает API и текущий AppConfig на странице и предоставляет их через модуль @wippy-fe/proxy.
- Для микрофронтенд-приложения (
view.page) хост вставляетproxy.jsвsrcdocстраницы. - Для веб-компонента (
view.component) среда выполнения уже присутствует на странице хоста — компонент монтируется в DOM хоста, а не в отдельный iframe.
Ваш код потребляет его через синхронные геттеры, экспортируемые @wippy-fe/proxy:
import { host, api, on, config } from '@wippy-fe/proxy'
host.navigate('/dashboard')
const data = await api.get('/api/v1/agents') // api — экземпляр axios; await относится к HTTP-вызову
on('@visibility', (visible) => { /* приостановить или возобновить работу */ })
Переносимая маршрутизация Vue — исключение: @wippy-fe/router сам потребляет @history и сообщает о локальной навигации. Не добавляйте вокруг него ручные подписки маршрутизации.
Эти геттеры синхронны: host, api, on, config и остальные готовы в тот момент, когда ваш код начинает выполняться — конфигурация на месте ещё до инициализации среды выполнения (см. ниже), поэтому ждать какого-либо рукопожатия не нужно. Пометьте @wippy-fe/proxy как external в своей сборке Vite — хост предоставляет его через import map. Полную поверхность см. в Proxy API.
Как конфигурация попадает в iframe приложения
Когда хост загружает view.page, он строит srcdoc и вставляет по порядку, до скрипта вашего приложения:
<!-- 1. Дочерний AppConfig — задаётся синхронно, до загрузки среды выполнения -->
<script>window.__WIPPY_APP_CONFIG__ = { /* auth, env, theming, hostConfig, context */ }</script>
<!-- 2. Флаги инъекции CSS для этой страницы -->
<script>window.__WIPPY_PROXY_CONFIG__ = { injections: { css: { themeConfig: true, primevue: true /* … */ } } }</script>
<!-- 3. Среда выполнения (перед ней loading.js) -->
<script src="/.../loading.js"></script>
<script src="/.../proxy.js"></script>
Поскольку глобальная переменная конфигурации задаётся до запуска proxy.js, среда выполнения инициализируется синхронно, и геттеры @wippy-fe/proxy работают сразу — без рукопожатия. Страницы не ссылаются на эти скрипты напрямую; хост заменяет заполнитель <script data-role="@wippy/scripts"> корректными упорядоченными тегами. Переопределения на уровне страницы приходят как window.__WIPPY_CONFIG_OVERRIDES__ (см. Proxy API — Переопределения конфигурации).
Веб-компонент видит те же глобальные переменные, потому что работает на странице хоста, где среда выполнения уже задала их до срабатывания connectedCallback компонента.
Чем различаются приложения и веб-компоненты
Оба импортируют один и тот же API из @wippy-fe/proxy. Различаются они контекстом выполнения и способом доставки стилей:
Микрофронтенд-приложение (view.page) |
Веб-компонент (view.component) |
|
|---|---|---|
| Где выполняется | в собственном srcdoc-iframe |
в DOM страницы хоста (Shadow DOM) |
| Доставка среды выполнения | proxy.js вставляется в iframe |
среда выполнения уже присутствует на странице хоста |
| CSS | полный конвейер инъекции (themeConfig, primevue, …) — см. Инъекция CSS |
hostCssKeys в Shadow DOM — см. Оформление: веб-компоненты |
Композиция и вложенность
Потомки компонуются. Микрофронтенд-приложение или веб-компонент сами могут размещать потомков — снова микрофронтенд-приложения или веб-компоненты, — которые могут размещать своих, на любую глубину. Каждый уровень использует один и тот же API @wippy-fe/proxy.
Как узел размещает потомка, зависит от вида потомка:
- Потомок в iframe — микрофронтенд-приложение, артефакт или произвольный HTML Wippy — идёт через
<w-iframe>,<w-artifact>илиhtml.inject. Они вставляют полную среду выполнения (базовый URL, import map,loading.js,proxy.jsи конфигурацию) вsrcdocпотомка, поэтому он получает Proxy API ровно так же, как приложение верхнего уровня. Его прокси связывается вверх через родителя с хостом. - Потомку-веб-компоненту ничего из этого не нужно. Отрисуйте его тег — или загрузите через
loadWebComponent/loadByTagName— и он выполнится в том же DOM, импортируя Proxy API напрямую.
Собственный код потомка одинаков и на верхнем уровне, и на нескольких уровнях вложенности: импортируйте из @wippy-fe/proxy и используйте. Особых правил вложенности нет.
Механику см. в разделах <w-iframe>, <w-artifact> и Продвинутая инъекция HTML ниже.
Внутреннее устройство — не читать и не переопределять
proxy.js устанавливает следующие глобальные переменные для собственных нужд. Код приложений и компонентов никогда не должен их читать или присваивать — используйте вместо этого @wippy-fe/proxy. Они документированы только для того, чтобы вы случайно их не затёрли:
| Глобальная переменная | Что это |
|---|---|
window.$W |
Объект асинхронного доступа ($W.host(), $W.api(), …). Внутренний; поддерживаемая поверхность — @wippy-fe/proxy. |
window.getWippyApi / window.initWippyApi |
Асинхронные функции «разрешить экземпляр». Внутренние (initWippyApi устарела). |
window.__WIPPY_APP_API__ |
Разрешённый экземпляр прокси. |
window.__WIPPY_APP_CONFIG__ |
Снимок дочернего AppConfig. |
window.__WIPPY_PROXY_CONFIG__ / window.__WIPPY_CONFIG_OVERRIDES__ |
Флаги инъекции CSS и переопределения на уровне страницы. |
window.__WIPPY_WEB_COMPONENT_CACHE__ |
Кэш загруженных компонентов. |
Публичный JavaScript API составляют две точки входа: initWippyApp(config, rootContainer?) монтирует весь Web Host (точка входа module-embed, используемая фасадом; см. Точка входа фасада), а @wippy-fe/proxy — синхронный API для дочерних приложений и компонентов. Всё, что в таблице выше, — внутреннее.
Протокол PostMessage (IFrameMessageType) — внутренний транспорт
Это проводной протокол, который среда выполнения использует внутренне; код приложения никогда не отправляет и не получает эти сообщения — @wippy-fe/proxy обрабатывает их за вас.
Стандартному пути с инъекцией от хоста рукопожатие для запуска не нужно — конфигурация уже присутствует синхронно как window.__WIPPY_APP_CONFIG__ до запуска proxy.js, поэтому среда выполнения сразу строит свой экземпляр. Обмен get-config/set-config на этом пути всё же происходит, но лишь как неблокирующий канал повторной синхронизации и живых обновлений: после построения синхронного экземпляра среда выполнения iframe всегда отправляет get-config, хост отвечает set-config и повторно шлёт set-config при каждом последующем обновлении конфигурации. Вложенные потомки <w-iframe> ведут себя так же. Ваш код ничего из этого не ждёт — синхронные геттеры уже живы.
Рукопожатие является единственным блокирующим источником конфигурации ровно в одном сценарии: при ручном встраивании в iframe без фасада (iframe.html?waitForCustomConfig), где нет предварительно вставленного window.__WIPPY_APP_CONFIG__, поэтому инициализация блокируется до первого set-config, и родитель обязан ответить на запрос get-config (см. Точка входа фасада § Ручное встраивание в iframe).
Каждое сообщение — это JSON-конверт формы { type: '@gen2-chat', action: IFrameMessageType.*, ...payload }. Поле type настраивается через APP_CONFIG_IFRAME_EVENT_TYPE, но по умолчанию равно '@gen2-chat'.
Все типы сообщений определены в перечислении IFrameMessageType:
| Член перечисления | Значение в протоколе | Направление | Описание |
|---|---|---|---|
GetConfig |
get-config |
Потомок → Хост | Начальное рукопожатие: потомок запрашивает свой AppConfig |
SetConfig |
set-config |
Хост → Потомок | Хост доставляет AppConfig в ответ на GetConfig |
UrlWasUpdatedInParent |
url-was-updated-in-parent |
Хост → Потомок | URL хоста изменился; порождает событие @history у потомка |
VisibilityWasUpdatedInParent |
visibility-was-updated-in-parent |
Хост → Потомок | Видимость iframe изменилась; порождает событие @visibility у потомка |
TopicWasReceivedInParent |
topic-was-received-in-parent |
Хост → Потомок | Доставляет событие темы WebSocket подписанным потомкам |
CmdRouteChanged |
cmd-route-changed |
Потомок → Хост | Внутренний маршрут потомка изменился; хост обновляет URL браузера |
CmdTitleChanged |
cmd-title-changed |
Потомок → Хост | document.title потомка изменился; хост обновляет заголовок страницы |
CmdStartChat |
cmd-start-chat |
Потомок → Хост | Открыть новую сессию чата |
CmdOpenSession |
cmd-open-session |
Потомок → Хост | Перейти к существующей сессии чата |
CmdOpenArtifact |
cmd-open-artifact |
Потомок → Хост | Открыть артефакт в боковой панели или модальном окне |
CmdNavigate |
cmd-navigate |
Потомок → Хост | Запрос SPA-навигации |
CmdShowToast |
cmd-show-toast |
Потомок → Хост | Показать всплывающее уведомление |
CmdShowConfirm |
cmd-show-confirm |
Потомок → Хост | Показать диалог подтверждения |
OnConfirmResult |
on-confirm-result |
Хост → Потомок | Доставляет результат диалога подтверждения |
CmdSetContext |
cmd-set-context |
Потомок → Хост | Отправить контекст в сессию чата |
CmdHandleError |
cmd-handle-error |
Потомок → Хост | Сообщить хосту об ошибке |
CmdLogout |
cmd-logout |
Потомок → Хост | Инициировать выход |
CmdSubscribe |
cmd-subscribe |
Потомок → Хост | Подписаться на тему WebSocket |
CmdUnSubscribe |
cmd-unsubscribe |
Потомок → Хост | Отписаться от темы |
OnSubscription |
on-subscription |
Хост → Потомок | Доставить данные события подписки |
CmdStateGet |
cmd-state-get |
Потомок → Хост | Прочитать сохранённый ключ состояния |
CmdStateSet |
cmd-state-set |
Потомок → Хост | Записать сохранённый ключ состояния |
CmdStateRemove |
cmd-state-remove |
Потомок → Хост | Удалить сохранённый ключ состояния |
CmdStateClear |
cmd-state-clear |
Потомок → Хост | Очистить всё состояние этой страницы |
CmdStateGetAll |
cmd-state-get-all |
Потомок → Хост | Прочитать всё сохранённое состояние |
OnStateResult |
on-state-result |
Хост → Потомок | Доставляет результат чтения состояния |
OnStateError |
on-state-error |
Хост → Потомок | Сообщает о сбое операции с состоянием |
CmdWsSend |
cmd-ws-send |
Потомок → Хост | Переслать команду WebSocket через соединение хоста |
CmdBodySize |
cmd-body-size |
Потомок → Хост | Сообщить размер body для auto-height |
CmdBridgePost |
cmd-bridge-post |
Потомок ↔ Родитель | Сообщение канала без ожидания ответа через host.bridge |
CmdBridgeRequest |
cmd-bridge-request |
Потомок ↔ Родитель | Сообщение канала запрос/ответ через host.bridge |
CmdClaimNavOwner |
cmd-claim-nav-owner |
Потомок → Хост | Заявить владение навигацией (режим nav-owner) |
CmdReleaseNavOwner |
cmd-release-nav-owner |
Потомок → Хост | Освободить владение навигацией |
CmdLayoutSubscribe |
cmd-layout-subscribe |
Потомок → Хост | Подписаться на обновления управляемой вёрстки |
CmdLayoutUpdatePanel |
cmd-layout-update-panel |
Потомок → Хост | Изменить определение панели |
CmdLayoutBroadcast |
cmd-layout-broadcast |
Потомок ↔ Хост | Сообщение шины вёрстки внутри вкладки |
OnLayoutChange |
on-layout-change |
Хост → Потомок | Полное обновление снимка вёрстки |
OnLayoutPanelChanged |
on-layout-panel-changed |
Хост → Потомок | Дельта живого состояния отдельной панели |
OnLayoutBroadcast |
on-layout-broadcast |
Хост → Потомок | Доставка широковещательного сообщения шины вёрстки |
Код приложения никогда не отправляет и не получает эти сообщения напрямую. Прокси обрабатывает протокол прозрачно и предоставляет только поверхность API @wippy-fe/proxy.
Пользовательский элемент <w-iframe>
<w-iframe> — низкоуровневый примитив iframe, встроенный в proxy.js. Он принимает сырой исходный HTML, вставляет полную среду выполнения Wippy (базовый URL, import map, loading.js, proxy.js, дочернюю конфигурацию) и отрисовывает результат как изолированный srcdoc-iframe.
Используйте <w-iframe>, когда у вас есть исходный HTML и вам нужно то же поведение среды выполнения, которое микрофронтенд-приложения Wippy получают автоматически: аутентифицированный API, ретрансляция состояния, ретрансляция WebSocket, маршрутизация nav-owner и обмен сообщениями через мост родитель-потомок.
Атрибуты и свойства
| Атрибут / свойство | Обязательно | По умолчанию | Описание |
|---|---|---|---|
src |
Нет | — | URL, откуда загрузить сырой исходный HTML через api прокси. |
srcdoc |
Нет | — | Сырой исходный HTML. Также задаётся как element.srcdoc = html для больших строк. |
base-url |
Нет | Выводится из src или document.baseURI |
<base href>, вставляемый для разрешения относительных ресурсов. |
resource-id |
Нет | id элемента, затем src |
Идентификатор дочернего контекста; задаёт область состояния и логов по умолчанию. |
resource-type |
Нет | page |
Тип дочернего контекста: page или artifact. |
sub-path |
Нет | Маршрут родителя | Начальный маршрут потомка. Передаётся как config.context.route в рукопожатии GetConfig. |
auto-height |
Нет | false |
Подгоняет высоту iframe под отчёты CmdBodySize потомка. |
nav-owner |
Нет | false |
Перехватывает CmdRouteChanged потомка и диспетчеризует DOM-события nav-owner-route вместо изменения URL хоста. |
JS-свойства, принимаемые элементом:
const frame = document.querySelector('w-iframe')
frame.proxyConfig = { injections: { css: { markdown: false } } }
frame.configOverrides = { customization: { customCSS: ':root { --brand: red }' } }
frame.srcdoc = sourceHtml
События и методы
| Событие | Detail | Описание |
|---|---|---|
loading |
— | Порождается до начала загрузки/обработки/отрисовки. |
load |
— | Порождается после загрузки изолированного iframe. |
error |
Исходная ошибка | Порождается при сбое загрузки, инъекции или отрисовки. |
nav-owner-route |
{ path: string, navId?: number } |
Изменение маршрута потомка при заданном nav-owner. Событие всплывает и имеет composed. |
wippy-message |
{ channel, payload, requestId?, respond?, reject? } |
Сообщение моста от потомка. |
| Метод | Описание |
|---|---|
post(channel, payload?) |
Сообщение моста потомку без ожидания ответа. |
request<T>(channel, payload?, { timeoutMs }?) |
Сообщение моста запрос/ответ; разрешается возвращаемым значением обработчика. |
Части shadow DOM: loader, error, frame.
Когда задан nav-owner, стандартный цикл синхронизации маршрута полностью подавляется: хост не обновляет собственную адресную строку и не отправляет UrlWasUpdatedInParent обратно потомку. Владение навигацией целиком делегируется родительскому коду, слушающему nav-owner-route. Значение path в detail события — это сырой внутренний маршрут потомка ровно в том виде, в каком потомок передал его в host.onRouteChanged(internalRoute, navId?); он не снабжается префиксом монтирования (в отличие от стандартного пути CmdRouteChanged, где хост добавляет префикс монтирования страницы). За любые префиксы или сопоставление с роутером отвечает встраивающий родитель:
const frame = document.querySelector('w-iframe')
frame.addEventListener('nav-owner-route', (event) => {
const { path, navId } = event.detail
myRouter.push(path)
})
Мост родитель-потомок
Мост использует именованные каналы, поэтому ни одной из сторон не нужны сырые конверты postMessage.
Сторона родителя:
const frame = document.querySelector('w-iframe')
frame.addEventListener('wippy-message', async (event) => {
const { channel, payload, respond, reject } = event.detail
if (channel === 'pick-file') {
try {
respond({ id: 'file-1', name: 'data.csv' })
} catch (error) {
reject(error)
}
}
})
frame.post('refresh', { reason: 'parent-click' })
const result = await frame.request('get-selection', undefined, { timeoutMs: 5000 })
Сторона потомка:
import { host } from '@wippy-fe/proxy'
host.bridge.post('ready', { value: 1 })
const file = await host.bridge.request('pick-file', { accept: '.csv' })
const off = host.bridge.on('refresh', async (payload) => {
console.log('refresh requested', payload)
return { ok: true }
})
host.bridge.on() возвращает функцию отписки (() => void). Один канал = один активный обработчик. Если для одного канала зарегистрировано несколько обработчиков, побеждает зарегистрированный последним и обрабатывает все входящие сообщения этого канала — и post() без ожидания ответа, и request(). on() не аддитивен: более ранние обработчики затеняются (не удаляются) и не выполняются, пока существует более новый, а прокси пишет console.warn при повторной регистрации. Если самый новый обработчик отписывается, предыдущий обработчик этого канала снова становится активным. Если вам нужны несколько независимых слушателей, используйте разные имена каналов.
Если вы опустите options.timeoutMs, host.bridge.request() (и frame.request() на стороне родителя) используют срок по умолчанию в 10 секунд (10000 мс). По истечении срока возвращённый Promise отклоняется с Error, сообщение которого — Bridge request <id> timed out after <ms>ms. Запрос к каналу, для которого у другой стороны нет обработчика, отклоняется немедленно с No handler registered for channel "<channel>", а не ждёт истечения срока.
Пользовательский элемент <w-artifact>
<w-artifact> разрешает метаданные и содержимое артефакта или страницы, а затем внутренне делегирует типы, основанные на iframe, элементу <w-iframe>. Он занимается определением типа содержимого (HTML, Markdown, пакеты веб-страниц, ESM-пакеты, компоненты с прямым тегом) и предоставляет более высокоуровневый API, чем сырой <w-iframe>.
Атрибуты
| Атрибут | Обязательно | Значения | По умолчанию | Описание |
|---|---|---|---|---|
id |
Да | UUID артефакта / страницы | — | Идентификатор содержимого. |
type |
Нет | artifact | page |
artifact |
Определяет вызываемую конечную точку REST: /api/v1/artifact/<id>/content или /api/public/pages/content/<id>. |
auto-height |
Нет | булев флаг | false |
Передаётся внутреннему <w-iframe> для синхронизации высоты по CmdBodySize. |
url |
Нет | Любой URL | — | Загружать содержимое напрямую с этого URL; id/type игнорируются. |
sub-path |
Нет | Строка пути | — | Передаётся внутреннему <w-iframe> как начальный маршрут потомка. |
nav-owner |
Нет | булев флаг | false |
Передаётся внутреннему <w-iframe>; изменения маршрута потомка порождают nav-owner-route. |
События
| Событие | Когда | Detail |
|---|---|---|
loading |
До начала загрузки | — |
load |
После загрузки iframe | — |
error |
Сбой загрузки или отрисовки | Исходная ошибка |
nav-owner-route |
Изменение маршрута потомка в режиме nav-owner | { path: string, navId?: number } |
wippy-message |
Сообщение моста от вложенного iframe | { channel, payload, requestId?, respond?, reject? } |
CSS-статус и части
Элемент задаёт атрибут status (loading, ready, error) и предоставляет части shadow DOM:
w-artifact[status="loading"] { opacity: 0.5; }
w-artifact[status="error"] { border: 1px solid var(--p-danger-color); }
w-artifact::part(loader) { font-size: 1rem; }
w-artifact::part(frame) { border: 0; }
<w-iframe> против <w-artifact> против сырого <iframe>
| Возможность | <w-iframe> |
<w-artifact> |
Сырой <iframe> |
|---|---|---|---|
| Вставляет среду выполнения Wippy | Да | Да (через <w-iframe>) |
Нет |
| Разрешает метаданные артефакта/страницы | Нет | Да | Нет |
| Аутентифицированная загрузка содержимого | Да (сырой HTML) | Да (полный резолвер) | Нет |
| Ретрансляция состояния | Да | Да | Нет |
| Ретрансляция WebSocket | Да | Да | Нет |
| Мост родитель-потомок | Да | Да (передаётся) | Нет |
| Поддержка nav-owner | Да | Да | Нет |
| Определение типа содержимого | Нет | Да | Нет |
| Части shadow DOM в CSS | loader, error, frame |
loader, error, frame |
— |
Атрибут status |
Да | Да | Нет |
Используйте <w-artifact>, когда у вас есть UUID артефакта Wippy или идентификатор страницы и вы хотите, чтобы платформа взяла на себя всё разрешение. Используйте <w-iframe>, когда у вас уже есть исходный HTML и нужна прямая инъекция среды выполнения. Используйте сырой <iframe> только для полностью внешнего содержимого, которому не нужен API Wippy.
Продвинутая инъекция HTML
Для случаев, когда нужно преобразование исходного HTML в srcdoc без монтирования элемента, прокси предоставляет html.inject(...):
import { html } from '@wippy-fe/proxy'
const processed = await html.inject(sourceHtml, {
baseUrl: 'https://example.com/app/',
resourceId: 'child-id',
resourceType: 'page',
route: '/initial',
})
Та же функция доступна как instance.html.inject, $W.html и import { html } from '@wippy-fe/proxy'. Для обычного монтирования предпочтительнее <w-iframe>; применяйте html.inject(...) только при построении собственной инфраструктуры размещения.