From: Evan You Date: Wed, 4 Dec 2024 03:54:26 +0000 (+0800) Subject: wip(vapor): reuse createApp from core X-Git-Tag: v3.6.0-alpha.1~16^2~244 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4fe05bdd749ac5e4763060fac7c94eed59824d41;p=thirdparty%2Fvuejs%2Fcore.git wip(vapor): reuse createApp from core --- diff --git a/packages/runtime-core/src/apiCreateApp.ts b/packages/runtime-core/src/apiCreateApp.ts index 4ca4f78da2..ada767b1ab 100644 --- a/packages/runtime-core/src/apiCreateApp.ts +++ b/packages/runtime-core/src/apiCreateApp.ts @@ -1,7 +1,8 @@ import { type Component, - type ComponentInternalInstance, type ConcreteComponent, + type GenericComponent, + type GenericComponentInstance, getComponentPublicInstance, validateComponentName, } from './component' @@ -18,8 +19,7 @@ import { type Directive, validateDirectiveName } from './directives' 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' @@ -95,11 +95,11 @@ export interface App { // 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 @@ -257,15 +257,30 @@ export function createAppContext(): AppContext { } export type CreateAppFunction = ( - rootComponent: Component, + rootComponent: GenericComponent, rootProps?: Data | null, ) => App let uid = 0 +export type AppMountFn = ( + app: App, + rootContainer: HostElement, + isHydrate?: boolean, + namespace?: boolean | ElementNamespace, +) => GenericComponentInstance + +export type AppUnmountFn = (app: App) => void + +/** + * @internal + */ export function createAppAPI( - render: RootRenderFunction, - hydrate?: RootHydrateFunction, + // render: RootRenderFunction, + // hydrate?: RootHydrateFunction, + mount: AppMountFn, + unmount: AppUnmountFn, + render?: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent, rootProps = null) { if (!isFunction(rootComponent)) { @@ -369,59 +384,32 @@ export function createAppAPI( }, 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, 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` + @@ -449,7 +437,7 @@ export function createAppAPI( app._instance, ErrorCodes.APP_UNMOUNT_CLEANUP, ) - render(null, app._container) + unmount(app) if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { app._instance = null devtoolsUnmountApp(app) @@ -485,7 +473,12 @@ export function createAppAPI( }) if (__COMPAT__) { - installAppCompatProperties(app, context, render) + installAppCompatProperties( + app, + context, + // vapor doesn't have compat mode so this is always passed + render!, + ) } return app diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 2bac2a3a0c..2a1598d80b 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -367,6 +367,9 @@ export interface GenericComponentInstance { */ propsDefaults: Data | null + // exposed properties via expose() + exposed: Record | null + // lifecycle isMounted: boolean isUnmounted: boolean @@ -519,8 +522,7 @@ export interface ComponentInternalInstance extends GenericComponentInstance { data: Data // options API only emit: EmitFn slots: InternalSlots - // exposed properties via expose() - exposed: Record | null + exposeProxy: Record | null /** @@ -1228,24 +1230,33 @@ export function createSetupContext( } 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 } diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index dc7f65b342..f897de253c 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -501,3 +501,8 @@ export { nextUid, } from './component' export { pushWarningContext, popWarningContext } from './warning' +export { + createAppAPI, + type AppMountFn, + type AppUnmountFn, +} from './apiCreateApp' diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index efa761cd2b..3aa7bad223 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -8,6 +8,7 @@ import { type VNodeHook, type VNodeProps, cloneIfMounted, + cloneVNode, createVNode, invokeVNodeHook, isSameVNodeType, @@ -57,7 +58,12 @@ import { 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, @@ -2397,10 +2403,49 @@ function baseCreateRenderer( ) } + const mountApp: AppMountFn = ( + 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, 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), } } diff --git a/packages/runtime-dom/src/index.ts b/packages/runtime-dom/src/index.ts index ca9a307dd9..3b65be697e 100644 --- a/packages/runtime-dom/src/index.ts +++ b/packages/runtime-dom/src/index.ts @@ -1,5 +1,6 @@ import { type App, + type ConcreteComponent, type CreateAppFunction, type DefineComponent, DeprecationTypes, @@ -108,7 +109,7 @@ export const createApp = ((...args) => { 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. @@ -225,7 +226,10 @@ function injectCompilerOptionsCheck(app: App) { } } -function normalizeContainer( +/** + * @internal + */ +export function normalizeContainer( container: Element | ShadowRoot | string, ): Element | ShadowRoot | null { if (isString(container)) { diff --git a/packages/runtime-vapor/src/_new/apiCreateApp.ts b/packages/runtime-vapor/src/_new/apiCreateApp.ts index ffdff0731d..becfc1b86c 100644 --- a/packages/runtime-vapor/src/_new/apiCreateApp.ts +++ b/packages/runtime-vapor/src/_new/apiCreateApp.ts @@ -1,18 +1,36 @@ 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 + +const mountApp: AppMountFn = (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 } diff --git a/packages/runtime-vapor/src/_new/component.ts b/packages/runtime-vapor/src/_new/component.ts index 0b47a60982..9bfe4c3f40 100644 --- a/packages/runtime-vapor/src/_new/component.ts +++ b/packages/runtime-vapor/src/_new/component.ts @@ -143,7 +143,7 @@ export class VaporComponentInstance implements GenericComponentInstance { rawProps: RawProps | undefined props: Record attrs: Record - exposed?: Record + exposed: Record | null emitted: Record | null propsDefaults: Record | null @@ -178,7 +178,7 @@ export class VaporComponentInstance implements GenericComponentInstance { 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