Компоненты remote
Что такое remote-компонент?
Ссылка на заголовокRemote-компонент — это Vue-компонент, который рендерится в sandboxed remote-runtime и синхронизируется с host через обновления канала.
Remote-компоненты описывают, что нужно отрисовать. Host-компоненты (зарегистрированные на стороне host) определяют, как это рендерится в реальном DOM.
Ответственность host и remote
Ссылка на заголовокcreateProvider()существует только на стороне host.- На стороне remote шага регистрации provider нет.
- Сторона remote строит деревья через:
defineRemoteComponent(...)для компонентов, экспортированных host;- native-теги (
html,svg,mathml) для локальной структуры.
Где нужна сериализация
Ссылка на заголовокСам Vue-код на remote не ограничен только scalars.
Внутри remote-компонентов можно использовать обычные паттерны Vue (ref, computed, локальные функции, сложные объекты).
Правила сериализации применяются только на границах, которые зеркалятся на host:
defineRemoteComponent(...): props/attrs и payload событий, которые уходят в host-компоненты.- Native
html/svg/mathmlузлы: attrs/props и children, которые синхронизируются в host DOM. На практике они ведут себя как встроенные мини host-компоненты.
Для этих границ используйте данные, дружественные к postMessage:
- Scalars:
string,number,boolean,null. - Массивы сериализуемых значений.
- Plain objects (POJO) с сериализуемыми вложенными значениями.
- Комбинации трех вариантов выше.
Если значение несериализуемое, нормализуйте его до передачи через границу.
Утилита defineRemoteComponent
Ссылка на заголовокИспользуйте defineRemoteComponent, чтобы описать типизированный bridge к host-компонентам по имени.
import { defineRemoteComponent } from '@omnicajs/vue-remote/remote'
const VButton = defineRemoteComponent('VButton')const VInput = defineRemoteComponent('VInput', [ 'update:value',] as unknown as { 'update:value': (value: string) => true;})Пример: remote-app с host-компонентами
Ссылка на заголовокimport { createRemoteRenderer, createRemoteRoot, defineRemoteComponent } from '@omnicajs/vue-remote/remote'import { defineComponent, h, ref } from 'vue'
const VButton = defineRemoteComponent('VButton')const VInput = defineRemoteComponent('VInput', [ 'update:value',] as unknown as { 'update:value': (value: string) => true;})
export async function mountRemote(channel: Parameters<typeof createRemoteRoot>[0]) { const root = createRemoteRoot(channel, { components: ['VButton', 'VInput'], })
await root.mount()
const app = createRemoteRenderer(root).createApp(defineComponent({ setup () { const text = ref('')
return () => h('section', { class: 'remote-form' }, [ h('h2', 'Remote form'), h(VInput, { value: text.value, placeholder: 'Type here', 'onUpdate:value': (value: string) => text.value = value, }), h(VButton, { onClick: () => text.value = '' }, 'Clear'), ]) }, }))
app.mount(root)}Пример: SVG + MathML + host-компонент в одном дереве
Ссылка на заголовокimport { defineComponent, h } from 'vue'import { defineRemoteComponent } from '@omnicajs/vue-remote/remote'
const VCard = defineRemoteComponent('VCard', [], ['title'])
export default defineComponent({ setup () { return () => h('article', { class: 'metrics' }, [ h('svg', { viewBox: '0 0 120 24', width: '100%', height: '24' }, [ h('path', { d: 'M0 18 L20 12 L40 14 L60 6 L80 10 L100 4 L120 8', stroke: 'currentColor', fill: 'none' }), ]), h('math', { display: 'block' }, [ h('mrow', [h('mi', 'x'), h('mo', '='), h('mn', '42')]), ]), h(VCard, {}, { title: () => 'Summary', default: () => 'Rendered by host, composed by remote.', }), ]) },})Примеры Vue SFC
Ссылка на заголовокSFC с host-компонентами
Ссылка на заголовок<script setup lang="ts">import { ref } from 'vue'import { defineRemoteComponent } from '@omnicajs/vue-remote/remote'
const VButton = defineRemoteComponent('VButton')const VInput = defineRemoteComponent('VInput', [ 'update:value',] as unknown as { 'update:value': (value: string) => true;})
const text = ref('')</script>
<template> <section class="remote-form"> <h2>Remote form</h2> <VInput :value="text" placeholder="Type here" @update:value="text = $event" /> <VButton @click="text = ''">Clear</VButton> </section></template>SFC с native HTML/SVG/MathML + host-компонентами
Ссылка на заголовок<script setup lang="ts">import { defineRemoteComponent } from '@omnicajs/vue-remote/remote'
const VCard = defineRemoteComponent('VCard', [], ['title'])</script>
<template> <article class="metrics"> <svg viewBox="0 0 120 24" width="100%" height="24"> <path d="M0 18 L20 12 L40 14 L60 6 L80 10 L100 4 L120 8" stroke="currentColor" fill="none" /> </svg>
<math display="block"> <mrow> <mi>x</mi> <mo>=</mo> <mn>42</mn> </mrow> </math>
<VCard> <template #title>Summary</template> Rendered by host, composed by remote. </VCard> </article></template>Ограничение ref на стороне remote
Ссылка на заголовокДля native html / svg / mathml элементов в remote-шаблонах refs не указывают на реальные browser DOM nodes.
Они указывают на узлы remote-дерева (proxy-объекты вроде RemoteComponent и связанных node-types).
<script setup lang="ts">import { onMounted, ref } from 'vue'
const el = ref<unknown>(null)
onMounted(() => { // Not HTMLElement/SVGElement/MathMLElement. // This is a remote tree node proxy. console.log(el.value)})</script>
<template> <div ref="el" /></template>Из-за этого многие привычные DOM APIs недоступны в remote-refs
(getBoundingClientRect, classList, прямой querySelector и т.д.).
DOM-библиотеки в remote-runtime
Ссылка на заголовокБольшинство библиотек, которым нужен прямой доступ к DOM, не будут работать на стороне remote по той же причине: remote-runtime управляет proxy-узлами дерева, а не реальными browser-элементами.
Если вам нужна такая библиотека:
- интегрируйте ее на стороне host (внутри host-компонента),
- отдайте наружу узкий сериализуемый API через props/events,
- держите сторону remote сфокусированной на декларативном состоянии и intent.