- 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 { 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,
+ )
+}
++
+ 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
+ }
type ShallowRef,
markRaw,
onScopeDispose,
- pauseTracking,
proxyRefs,
- resetTracking,
+ setActiveSub,
unref,
} from '@vue/reactivity'
-import { EMPTY_OBJ, invokeArrayFns, isFunction, isString } from '@vue/shared'
+import {
+ EMPTY_OBJ,
+ invokeArrayFns,
+ isFunction,
+ isPromise,
+ isString,
+} from '@vue/shared'
import {
type DynamicPropsSource,
type RawProps,
} from './componentSlots'
import { hmrReload, hmrRerender } from './hmr'
import { isHydrating, locateHydrationNode } from './dom/hydration'
- import { insertionAnchor, insertionParent } from './insertionState'
+ import {
+ insertionAnchor,
+ insertionParent,
+ resetInsertionState,
+ } from './insertionState'
+import { parentSuspense } from './components/Suspense'
export { currentInstance } from '@vue/runtime-dom'
rawProps as RawProps,
rawSlots as RawSlots,
appContext,
+ parentSuspense,
)
+ // HMR
+ if (__DEV__ && component.__hmrId) {
+ registerHMR(instance)
+ instance.isSingleRoot = isSingleRoot
+ instance.hmrRerender = hmrRerender.bind(null, instance)
+ instance.hmrReload = hmrReload.bind(null, instance)
+ }
+
if (__DEV__) {
pushWarningContext(instance)
startMeasure(instance, `init`)
]) || EMPTY_OBJ
: EMPTY_OBJ
- if (__DEV__ && !isBlock(setupResult)) {
- if (isFunction(component)) {
- warn(`Functional vapor component must return a block directly.`)
- instance.block = []
- } else if (!component.render) {
+ const isAsyncSetup = isPromise(setupResult)
+ if (isAsyncSetup) {
+ if (__FEATURE_SUSPENSE__) {
+ // async setup returned Promise.
+ // bail here and wait for re-entry.
+ instance.asyncDep = setupResult
+ if (__DEV__ && !instance.suspense) {
+ const name = getComponentName(component) ?? 'Anonymous'
+ warn(
+ `Component <${name}>: setup function returned a promise, but no ` +
+ `<Suspense> boundary was found in the parent component tree. ` +
+ `A component with async setup() must be nested in a <Suspense> ` +
+ `in order to be rendered.`,
+ )
+ }
+ } else if (__DEV__) {
warn(
- `Vapor component setup() returned non-block value, and has no render function.`,
+ `setup() returned a Promise, but the version of Vue you are using ` +
+ `does not support it yet.`,
)
- instance.block = []
- } else {
- instance.devtoolsRawSetupState = setupResult
- // TODO make the proxy warn non-existent property access during dev
- instance.setupState = proxyRefs(setupResult)
- devRender(instance)
}
} else {
- // component has a render function but no setup function
- // (typically components with only a template and no state)
- if (!setupFn && component.render) {
- instance.block = callWithErrorHandling(
- component.render,
- instance,
- ErrorCodes.RENDER_FUNCTION,
- )
- } else {
- // in prod result can only be block
- instance.block = setupResult as Block
- }
- }
-
- // single root, inherit attrs
- if (
- instance.hasFallthrough &&
- component.inheritAttrs !== false &&
- Object.keys(instance.attrs).length
- ) {
- const el = getRootElement(instance)
- if (el) {
- renderEffect(() => {
- isApplyingFallthroughProps = true
- setDynamicProps(el, [instance.attrs])
- isApplyingFallthroughProps = false
- })
- }
+ handleSetupResult(setupResult, component, instance, isSingleRoot, setupFn)
}
- resetTracking()
- simpleSetCurrentInstance(prev, instance)
+ setActiveSub(prevSub)
+ setCurrentInstance(...prevInstance)
if (__DEV__) {
popWarningContext()
onScopeDispose(() => unmountComponent(instance), true)
- if (!isHydrating && _insertionParent) {
+ if (!isHydrating && _insertionParent && !isAsyncSetup) {
- insert(instance.block, _insertionParent, _insertionAnchor)
+ mountComponent(instance, _insertionParent, _insertionAnchor)
}
return instance
}
}
- instance.block instanceof Element &&
+export function handleSetupResult(
+ setupResult: any,
+ component: VaporComponent,
+ instance: VaporComponentInstance,
+ isSingleRoot?: boolean,
+ setupFn?: VaporSetupFn,
+): void {
+ if (__DEV__) {
+ pushWarningContext(instance)
+ }
+ if (__DEV__ && !isBlock(setupResult)) {
+ if (isFunction(component)) {
+ warn(`Functional vapor component must return a block directly.`)
+ instance.block = []
+ } else if (!component.render) {
+ warn(
+ `Vapor component setup() returned non-block value, and has no render function.`,
+ )
+ instance.block = []
+ } else {
+ instance.devtoolsRawSetupState = setupResult
+ // TODO make the proxy warn non-existent property access during dev
+ instance.setupState = proxyRefs(setupResult)
+ devRender(instance)
+
+ // HMR
+ if (component.__hmrId) {
+ registerHMR(instance)
+ instance.isSingleRoot = isSingleRoot
+ instance.hmrRerender = hmrRerender.bind(null, instance)
+ instance.hmrReload = hmrReload.bind(null, instance)
+ }
+ }
+ } else {
+ // component has a render function but no setup function
+ // (typically components with only a template and no state)
+ if (!setupFn && component.render) {
+ instance.block = callWithErrorHandling(
+ component.render,
+ instance,
+ ErrorCodes.RENDER_FUNCTION,
+ )
+ } else {
+ // in prod result can only be block
+ instance.block = setupResult as Block
+ }
+ }
+
+ // single root, inherit attrs
+ if (
+ instance.hasFallthrough &&
+ component.inheritAttrs !== false &&
- renderEffect(() => {
- isApplyingFallthroughProps = true
- setDynamicProps(instance.block as Element, [instance.attrs])
- isApplyingFallthroughProps = false
- })
+ Object.keys(instance.attrs).length
+ ) {
++ const el = getRootElement(instance)
++ if (el) {
++ renderEffect(() => {
++ isApplyingFallthroughProps = true
++ setDynamicProps(el, [instance.attrs])
++ isApplyingFallthroughProps = false
++ })
++ }
+ }
+
+ if (__DEV__) {
+ popWarningContext()
+ }
+}
++
+ function getRootElement({
+ block,
+ }: VaporComponentInstance): Element | undefined {
+ if (block instanceof Element) {
+ return block
+ }
+
+ if (block instanceof DynamicFragment) {
+ const { nodes } = block
+ if (nodes instanceof Element && (nodes as any).$root) {
+ return nodes
+ }
+ }
+ }