- import { createVaporApp } from '../src'
- import type { App } from '@vue/runtime-dom'
+ import { createVaporApp, vaporInteropPlugin } from '../src'
+ import { type App, type Component, createApp } from '@vue/runtime-dom'
import type { VaporComponent, VaporComponentInstance } from '../src/component'
import type { RawProps } from '../src/componentProps'
+import { compileScript, parse } from '@vue/compiler-sfc'
+import * as runtimeVapor from '../src'
+import * as runtimeDom from '@vue/runtime-dom'
+import * as VueServerRenderer from '@vue/server-renderer'
export interface RenderContext {
component: VaporComponent
return define
}
+ export interface InteropRenderContext {
+ mount: (container?: string | ParentNode) => InteropRenderContext
+ render: (
+ props?: RawProps,
+ container?: string | ParentNode,
+ ) => InteropRenderContext
+ host: HTMLElement
+ html: () => string
+ }
+
+ export function makeInteropRender(): (comp: Component) => InteropRenderContext {
+ let host: HTMLElement
+ beforeEach(() => {
+ host = document.createElement('div')
+ })
+ afterEach(() => {
+ host.remove()
+ })
+
+ function define(comp: Component) {
+ let app: App
+ function render(
+ props: RawProps | undefined = undefined,
+ container: string | ParentNode = host,
+ ) {
+ app?.unmount()
+ app = createApp(comp, props)
+ app.use(vaporInteropPlugin)
+ return mount(container)
+ }
+
+ function mount(container: string | ParentNode = host) {
+ app.mount(container)
+ return res()
+ }
+
+ function html() {
+ return host.innerHTML
+ }
+
+ const res = () => ({
+ host,
+ mount,
+ render,
+ html,
+ })
+
+ return res()
+ }
+
+ return define
+ }
++
+export { runtimeDom, runtimeVapor, VueServerRenderer }
+export function compile(
+ sfc: string,
+ data: runtimeDom.Ref<any>,
+ components: Record<string, any> = {},
+ {
+ vapor = true,
+ ssr = false,
+ }: {
+ vapor?: boolean | undefined
+ ssr?: boolean | undefined
+ } = {},
+): any {
+ if (!sfc.includes(`<script`)) {
+ sfc =
+ `<script vapor>const data = _data; const components = _components;</script>` +
+ sfc
+ }
+ const descriptor = parse(sfc).descriptor
+
+ const script = compileScript(descriptor, {
+ id: 'x',
+ isProd: true,
+ inlineTemplate: true,
+ genDefaultAs: '__sfc__',
+ vapor,
+ templateOptions: {
+ ssr,
+ },
+ })
+
+ const code =
+ script.content
+ .replace(/\bimport {/g, 'const {')
+ .replace(/ as _/g, ': _')
+ .replace(/} from ['"]vue['"]/g, `} = Vue`)
+ .replace(/} from "vue\/server-renderer"/g, '} = VueServerRenderer') +
+ '\nreturn __sfc__'
+
+ return new Function('Vue', 'VueServerRenderer', '_data', '_components', code)(
+ { ...runtimeDom, ...runtimeVapor },
+ VueServerRenderer,
+ data,
+ components,
+ )
+}
currentInstance,
ensureRenderer,
isEmitListener,
+ normalizeRef,
onScopeDispose,
renderSlot,
+ shallowReactive,
shallowRef,
simpleSetCurrentInstance,
+ setRef as vdomSetRef,
} from '@vue/runtime-dom'
import {
type LooseRawProps,
import { renderEffect } from './renderEffect'
import { createTextNode } from './dom/node'
import { optimizePropertyLookup } from './dom/prop'
+import type { NodeRef } from './apiTemplateRef'
+ export const interopKey: unique symbol = Symbol(`interop`)
+
// mounting vapor components and slots in vdom
const vaporInteropImpl: Omit<
VaporInteropInterface,
const prev = currentInstance
simpleSetCurrentInstance(parentComponent)
- const propsRef = shallowRef(vnode.props)
+ // filter out reserved props
+ const props: VNode['props'] = {}
+ for (const key in vnode.props) {
+ if (!isReservedProp(key)) {
+ props[key] = vnode.props[key]
+ }
+ }
+
+ const propsRef = shallowRef(props)
const slotsRef = shallowRef(vnode.children)
+ const dynamicPropSource: (() => any)[] & { [interopKey]?: boolean } = [
+ () => propsRef.value,
+ ]
+ // mark as interop props
+ dynamicPropSource[interopKey] = true
// @ts-expect-error
const instance = (vnode.component = createComponent(
vnode.type as any as VaporComponent,