]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(vapor): reuse createApp from core
authorEvan You <evan@vuejs.org>
Wed, 4 Dec 2024 03:54:26 +0000 (11:54 +0800)
committerEvan You <evan@vuejs.org>
Wed, 4 Dec 2024 03:54:26 +0000 (11:54 +0800)
packages/runtime-core/src/apiCreateApp.ts
packages/runtime-core/src/component.ts
packages/runtime-core/src/index.ts
packages/runtime-core/src/renderer.ts
packages/runtime-dom/src/index.ts
packages/runtime-vapor/src/_new/apiCreateApp.ts
packages/runtime-vapor/src/_new/component.ts

index 4ca4f78da2b84473511258afde9692ebedb75c54..ada767b1abadc0e3939733187e301cb003b13090 100644 (file)
@@ -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<HostElement = any> {
 
   // 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<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)) {
@@ -369,59 +384,32 @@ export function createAppAPI<HostElement>(
       },
 
       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` +
@@ -449,7 +437,7 @@ export function createAppAPI<HostElement>(
             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<HostElement>(
     })
 
     if (__COMPAT__) {
-      installAppCompatProperties(app, context, render)
+      installAppCompatProperties(
+        app,
+        context,
+        // vapor doesn't have compat mode so this is always passed
+        render!,
+      )
     }
 
     return app
index 2bac2a3a0c87973afbf7c7f164de0f0d5f953835..2a1598d80bc3c77d2737b8fa982a4b1fc885c950 100644 (file)
@@ -367,6 +367,9 @@ export interface GenericComponentInstance {
    */
   propsDefaults: Data | null
 
+  // exposed properties via expose()
+  exposed: Record<string, any> | 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<string, any> | null
+
   exposeProxy: Record<string, any> | 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
   }
index dc7f65b34234c5542d1781690b07247fdffabbc4..f897de253ca79c288eec95cd96e9c4accc014d19 100644 (file)
@@ -501,3 +501,8 @@ export {
   nextUid,
 } from './component'
 export { pushWarningContext, popWarningContext } from './warning'
+export {
+  createAppAPI,
+  type AppMountFn,
+  type AppUnmountFn,
+} from './apiCreateApp'
index efa761cd2b2de2368ec4168dc139a8afa112c5d7..3aa7bad223d13c11bae4bc625bd490d7fb1073e7 100644 (file)
@@ -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<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),
   }
 }
 
index ca9a307dd98086e0fd6c1bffb4801e94799bd1f5..3b65be697eded00ef6a290b98d2b9756bdfaad34 100644 (file)
@@ -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)) {
index ffdff0731d76cfb9ddc2c357f6ca819e6634e4cf..becfc1b86c1d8b5d483cc914b2815227ce05da3d 100644 (file)
@@ -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<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
 }
index 0b47a60982c321525a5401b01b1e61675b6b5f73..9bfe4c3f40f99681398d2b8934c8f975df48c2bf 100644 (file)
@@ -143,7 +143,7 @@ export class VaporComponentInstance implements GenericComponentInstance {
   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
@@ -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