Перейти к основному содержимому

Компоненты 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:

  1. defineRemoteComponent(...): props/attrs и payload событий, которые уходят в host-компоненты.
  2. 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.',
}),
])
},
})

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.