Прокси и изоляция

Web Host запускает каждый дочерний микрофронтенд в изолированном контексте и связывает его с хостом через Proxy API. И микрофронтенд-приложения, и веб-компоненты обращаются к хосту, импортируя из @wippy-fe/proxy.

Инъекция Proxy API и вложенность

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(...) только при построении собственной инфраструктуры размещения.