import {
type Component,
- type ComponentInternalInstance,
type ConcreteComponent,
+ type GenericComponent,
+ type GenericComponentInstance,
getComponentPublicInstance,
validateComponentName,
} from './component'
import type { ElementNamespace, RootRenderFunction } from './renderer'
import type { InjectionKey } from './apiInject'
import { warn } from './warning'
-import { type VNode, cloneVNode, createVNode } from './vnode'
-import type { RootHydrateFunction } from './hydration'
+import type { VNode } from './vnode'
import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
import { NO, extend, isFunction, isObject } from '@vue/shared'
import type { Data } from '@vue/runtime-shared'
// internal, but we need to expose these for the server-renderer and devtools
_uid: number
- _component: ConcreteComponent
+ _component: GenericComponent
_props: Data | null
_container: HostElement | null
_context: AppContext
- _instance: ComponentInternalInstance | null
+ _instance: GenericComponentInstance | null
/**
* @internal custom element vnode
}
export type CreateAppFunction<HostElement> = (
- rootComponent: Component,
+ rootComponent: GenericComponent,
rootProps?: Data | null,
) => App<HostElement>
let uid = 0
+export type AppMountFn<HostElement> = (
+ app: App,
+ rootContainer: HostElement,
+ isHydrate?: boolean,
+ namespace?: boolean | ElementNamespace,
+) => GenericComponentInstance
+
+export type AppUnmountFn = (app: App) => void
+
+/**
+ * @internal
+ */
export function createAppAPI<HostElement>(
- render: RootRenderFunction<HostElement>,
- hydrate?: RootHydrateFunction,
+ // render: RootRenderFunction<HostElement>,
+ // hydrate?: RootHydrateFunction,
+ mount: AppMountFn<HostElement>,
+ unmount: AppUnmountFn,
+ render?: RootRenderFunction,
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
if (!isFunction(rootComponent)) {
},
mount(
- rootContainer: HostElement,
+ rootContainer: HostElement & { __vue_app__?: App },
isHydrate?: boolean,
namespace?: boolean | ElementNamespace,
): any {
if (!isMounted) {
// #5571
- if (__DEV__ && (rootContainer as any).__vue_app__) {
+ if (__DEV__ && rootContainer.__vue_app__) {
warn(
`There is already an app instance mounted on the host container.\n` +
` If you want to mount another app on the same host container,` +
` you need to unmount the previous app by calling \`app.unmount()\` first.`,
)
}
- const vnode = app._ceVNode || createVNode(rootComponent, rootProps)
- // store app context on the root VNode.
- // this will be set on the root instance on initial mount.
- vnode.appContext = context
-
- if (namespace === true) {
- namespace = 'svg'
- } else if (namespace === false) {
- namespace = undefined
- }
+ const instance = mount(app, rootContainer, isHydrate, namespace)
- // HMR root reload
- if (__DEV__) {
- context.reload = () => {
- // casting to ElementNamespace because TS doesn't guarantee type narrowing
- // over function boundaries
- render(
- cloneVNode(vnode),
- rootContainer,
- namespace as ElementNamespace,
- )
- }
+ if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
+ app._instance = instance
+ devtoolsInitApp(app, version)
}
- if (isHydrate && hydrate) {
- hydrate(vnode as VNode<Node, Element>, rootContainer as any)
- } else {
- render(vnode, rootContainer, namespace)
- }
isMounted = true
app._container = rootContainer
// for devtools and telemetry
- ;(rootContainer as any).__vue_app__ = app
-
- if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
- app._instance = vnode.component
- devtoolsInitApp(app, version)
- }
+ rootContainer.__vue_app__ = app
- return getComponentPublicInstance(vnode.component!)
+ return getComponentPublicInstance(instance)
} else if (__DEV__) {
warn(
`App has already been mounted.\n` +
app._instance,
ErrorCodes.APP_UNMOUNT_CLEANUP,
)
- render(null, app._container)
+ unmount(app)
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
app._instance = null
devtoolsUnmountApp(app)
})
if (__COMPAT__) {
- installAppCompatProperties(app, context, render)
+ installAppCompatProperties(
+ app,
+ context,
+ // vapor doesn't have compat mode so this is always passed
+ render!,
+ )
}
return app
*/
propsDefaults: Data | null
+ // exposed properties via expose()
+ exposed: Record<string, any> | null
+
// lifecycle
isMounted: boolean
isUnmounted: boolean
data: Data // options API only
emit: EmitFn
slots: InternalSlots
- // exposed properties via expose()
- exposed: Record<string, any> | null
+
exposeProxy: Record<string, any> | null
/**
}
export function getComponentPublicInstance(
- instance: ComponentInternalInstance,
+ instance: GenericComponentInstance,
): ComponentPublicInstance | ComponentInternalInstance['exposed'] | null {
if (instance.exposed) {
- return (
- instance.exposeProxy ||
- (instance.exposeProxy = new Proxy(proxyRefs(markRaw(instance.exposed)), {
- get(target, key: string) {
- if (key in target) {
- return target[key]
- } else if (key in publicPropertiesMap) {
- return publicPropertiesMap[key](instance)
- }
- },
- has(target, key: string) {
- return key in target || key in publicPropertiesMap
- },
- }))
- )
+ if ('exposeProxy' in instance) {
+ return (
+ instance.exposeProxy ||
+ (instance.exposeProxy = new Proxy(
+ proxyRefs(markRaw(instance.exposed)),
+ {
+ get(target, key: string) {
+ if (key in target) {
+ return target[key]
+ } else if (key in publicPropertiesMap) {
+ return publicPropertiesMap[key](
+ instance as ComponentInternalInstance,
+ )
+ }
+ },
+ has(target, key: string) {
+ return key in target || key in publicPropertiesMap
+ },
+ },
+ ))
+ )
+ } else {
+ return instance.exposed
+ }
} else {
return instance.proxy
}
nextUid,
} from './component'
export { pushWarningContext, popWarningContext } from './warning'
+export {
+ createAppAPI,
+ type AppMountFn,
+ type AppUnmountFn,
+} from './apiCreateApp'
type VNodeHook,
type VNodeProps,
cloneIfMounted,
+ cloneVNode,
createVNode,
invokeVNodeHook,
isSameVNodeType,
import { updateProps } from './componentProps'
import { updateSlots } from './componentSlots'
import { popWarningContext, pushWarningContext, warn } from './warning'
-import { type CreateAppFunction, createAppAPI } from './apiCreateApp'
+import {
+ type AppMountFn,
+ type AppUnmountFn,
+ type CreateAppFunction,
+ createAppAPI,
+} from './apiCreateApp'
import { setRef } from './rendererTemplateRef'
import {
type SuspenseBoundary,
)
}
+ const mountApp: AppMountFn<Element> = (
+ app,
+ container,
+ isHydrate,
+ namespace,
+ ) => {
+ const vnode = app._ceVNode || createVNode(app._component, app._props)
+ // store app context on the root VNode.
+ // this will be set on the root instance on initial mount.
+ vnode.appContext = app._context
+
+ if (namespace === true) {
+ namespace = 'svg'
+ } else if (namespace === false) {
+ namespace = undefined
+ }
+
+ // HMR root reload
+ if (__DEV__) {
+ app._context.reload = () => {
+ // casting to ElementNamespace because TS doesn't guarantee type narrowing
+ // over function boundaries
+ render(cloneVNode(vnode), container, namespace as ElementNamespace)
+ }
+ }
+
+ if (isHydrate && hydrate) {
+ hydrate(vnode as VNode<Node, Element>, container as any)
+ } else {
+ render(vnode, container, namespace)
+ }
+
+ return vnode.component!
+ }
+
+ const unmountApp: AppUnmountFn = app => {
+ render(null, app._container)
+ }
+
return {
render,
hydrate,
- createApp: createAppAPI(render, hydrate),
+ createApp: createAppAPI(mountApp, unmountApp, render),
}
}
import {
type App,
+ type ConcreteComponent,
type CreateAppFunction,
type DefineComponent,
DeprecationTypes,
const container = normalizeContainer(containerOrSelector)
if (!container) return
- const component = app._component
+ const component = app._component as ConcreteComponent
if (!isFunction(component) && !component.render && !component.template) {
// __UNSAFE__
// Reason: potential execution of JS expressions in in-DOM template.
}
}
-function normalizeContainer(
+/**
+ * @internal
+ */
+export function normalizeContainer(
container: Element | ShadowRoot | string,
): Element | ShadowRoot | null {
if (isString(container)) {
import { normalizeContainer } from '../apiRender'
import { insert } from '../dom/element'
import { type VaporComponent, createComponent } from './component'
+import {
+ type AppMountFn,
+ type AppUnmountFn,
+ type CreateAppFunction,
+ createAppAPI,
+} from '@vue/runtime-core'
+
+let _createApp: CreateAppFunction<ParentNode>
+
+const mountApp: AppMountFn<ParentNode> = (app, container) => {
+ // clear content before mounting
+ if (container.nodeType === 1 /* Node.ELEMENT_NODE */) {
+ container.textContent = ''
+ }
+ const instance = createComponent(app._component)
+ insert(instance.block, container)
+ return instance
+}
+
+const unmountApp: AppUnmountFn = app => {
+ // TODO
+}
export function createVaporApp(comp: VaporComponent): any {
- return {
- mount(container: string | ParentNode) {
- container = normalizeContainer(container)
- // clear content before mounting
- if (container.nodeType === 1 /* Node.ELEMENT_NODE */) {
- container.textContent = ''
- }
- const instance = createComponent(comp)
- insert(instance.block, container)
- return instance
- },
+ if (!_createApp) _createApp = createAppAPI(mountApp, unmountApp)
+ const app = _createApp(comp)
+ const mount = app.mount
+ app.mount = (container, ...args: any[]) => {
+ container = normalizeContainer(container) // TODO reuse from runtime-dom
+ return mount(container, ...args)
}
+ return app
}
rawProps: RawProps | undefined
props: Record<string, any>
attrs: Record<string, any>
- exposed?: Record<string, any>
+ exposed: Record<string, any> | null
emitted: Record<string, boolean> | null
propsDefaults: Record<string, any> | null
this.rawProps = rawProps
this.provides = this.refs = EMPTY_OBJ
- this.emitted = this.ec = null
+ this.emitted = this.ec = this.exposed = null
this.isMounted = this.isUnmounted = this.isDeactivated = false
// init props