wip: root mount api compat
This commit is contained in:
		
							parent
							
								
									24850a99c6
								
							
						
					
					
						commit
						e2d6ff845b
					
				@ -2,19 +2,28 @@ import {
 | 
			
		||||
  ConcreteComponent,
 | 
			
		||||
  Data,
 | 
			
		||||
  validateComponentName,
 | 
			
		||||
  Component
 | 
			
		||||
  Component,
 | 
			
		||||
  createComponentInstance,
 | 
			
		||||
  setupComponent,
 | 
			
		||||
  finishComponentSetup
 | 
			
		||||
} from './component'
 | 
			
		||||
import { ComponentOptions } from './componentOptions'
 | 
			
		||||
import { ComponentPublicInstance } from './componentPublicInstance'
 | 
			
		||||
import { Directive, validateDirectiveName } from './directives'
 | 
			
		||||
import { RootRenderFunction } from './renderer'
 | 
			
		||||
import { InjectionKey } from './apiInject'
 | 
			
		||||
import { isFunction, NO, isObject } from '@vue/shared'
 | 
			
		||||
import { warn } from './warning'
 | 
			
		||||
import { createVNode, cloneVNode, VNode } from './vnode'
 | 
			
		||||
import { RootHydrateFunction } from './hydration'
 | 
			
		||||
import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
 | 
			
		||||
import { version } from '.'
 | 
			
		||||
import {
 | 
			
		||||
  isFunction,
 | 
			
		||||
  NO,
 | 
			
		||||
  isObject,
 | 
			
		||||
  warnDeprecation,
 | 
			
		||||
  DeprecationTypes
 | 
			
		||||
} from '@vue/shared'
 | 
			
		||||
 | 
			
		||||
export interface App<HostElement = any> {
 | 
			
		||||
  version: string
 | 
			
		||||
@ -39,6 +48,11 @@ export interface App<HostElement = any> {
 | 
			
		||||
  _props: Data | null
 | 
			
		||||
  _container: HostElement | null
 | 
			
		||||
  _context: AppContext
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @internal 2.x compat only
 | 
			
		||||
   */
 | 
			
		||||
  _createRoot?(options: ComponentOptions): ComponentPublicInstance
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type OptionMergeFunction = (
 | 
			
		||||
@ -298,6 +312,129 @@ export function createAppAPI<HostElement>(
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    if (__COMPAT__) {
 | 
			
		||||
      /**
 | 
			
		||||
       * Vue 2 supports the behavior of creating a component instance but not
 | 
			
		||||
       * mounting it, which is no longer possible in Vue 3 - this internal
 | 
			
		||||
       * function simulates that behavior.
 | 
			
		||||
       */
 | 
			
		||||
      app._createRoot = options => {
 | 
			
		||||
        const vnode = createVNode(
 | 
			
		||||
          rootComponent as ConcreteComponent,
 | 
			
		||||
          options.propsData || null
 | 
			
		||||
        )
 | 
			
		||||
        vnode.appContext = context
 | 
			
		||||
 | 
			
		||||
        const hasNoRender =
 | 
			
		||||
          !isFunction(rootComponent) &&
 | 
			
		||||
          !rootComponent.render &&
 | 
			
		||||
          !rootComponent.template
 | 
			
		||||
        const emptyRender = () => {}
 | 
			
		||||
 | 
			
		||||
        // create root instance
 | 
			
		||||
        const instance = createComponentInstance(vnode, null, null)
 | 
			
		||||
        // suppress "missing render fn" warning since it can't be determined
 | 
			
		||||
        // until $mount is called
 | 
			
		||||
        if (hasNoRender) {
 | 
			
		||||
          instance.render = emptyRender
 | 
			
		||||
        }
 | 
			
		||||
        setupComponent(instance, __NODE_JS__)
 | 
			
		||||
        vnode.component = instance
 | 
			
		||||
 | 
			
		||||
        // $mount & $destroy
 | 
			
		||||
        // these are defined on ctx and picked up by the $mount/$destroy
 | 
			
		||||
        // public property getters on the instance proxy.
 | 
			
		||||
        // Note: the following assumes DOM environment since the compat build
 | 
			
		||||
        // only targets web. It essentially includes logic for app.mount from
 | 
			
		||||
        // both runtime-core AND runtime-dom.
 | 
			
		||||
        instance.ctx._compat_mount = (selectorOrEl: string | Element) => {
 | 
			
		||||
          if (isMounted) {
 | 
			
		||||
            __DEV__ && warn(`Root instance is already mounted.`)
 | 
			
		||||
            return
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          let container: Element
 | 
			
		||||
          if (typeof selectorOrEl === 'string') {
 | 
			
		||||
            // eslint-disable-next-line
 | 
			
		||||
            const result = document.querySelector(selectorOrEl)
 | 
			
		||||
            if (!result) {
 | 
			
		||||
              __DEV__ &&
 | 
			
		||||
                warn(
 | 
			
		||||
                  `Failed to mount root instance: selector "${selectorOrEl}" returned null.`
 | 
			
		||||
                )
 | 
			
		||||
              return
 | 
			
		||||
            }
 | 
			
		||||
            container = result
 | 
			
		||||
          } else {
 | 
			
		||||
            if (!selectorOrEl) {
 | 
			
		||||
              __DEV__ &&
 | 
			
		||||
                warn(
 | 
			
		||||
                  `Failed to mount root instance: invalid mount target ${selectorOrEl}.`
 | 
			
		||||
                )
 | 
			
		||||
              return
 | 
			
		||||
            }
 | 
			
		||||
            container = selectorOrEl
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          const isSVG = container instanceof SVGElement
 | 
			
		||||
 | 
			
		||||
          // HMR root reload
 | 
			
		||||
          if (__DEV__) {
 | 
			
		||||
            context.reload = () => {
 | 
			
		||||
              const cloned = cloneVNode(vnode)
 | 
			
		||||
              // compat mode will use instance if not reset to null
 | 
			
		||||
              cloned.component = null
 | 
			
		||||
              render(cloned, container, isSVG)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // resolve in-DOM template if component did not provide render
 | 
			
		||||
          // and no setup/mixin render functions are provided (by checking
 | 
			
		||||
          // that the instance is still using the placeholder render fn)
 | 
			
		||||
          if (hasNoRender && instance.render === emptyRender) {
 | 
			
		||||
            // root directives check
 | 
			
		||||
            if (__DEV__) {
 | 
			
		||||
              for (let i = 0; i < container.attributes.length; i++) {
 | 
			
		||||
                const attr = container.attributes[i]
 | 
			
		||||
                if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
 | 
			
		||||
                  warnDeprecation(DeprecationTypes.DOM_TEMPLATE_MOUNT)
 | 
			
		||||
                  break
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            instance.render = null
 | 
			
		||||
            ;(rootComponent as ComponentOptions).template = container.innerHTML
 | 
			
		||||
            finishComponentSetup(instance, __NODE_JS__, true /* skip options */)
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // clear content before mounting
 | 
			
		||||
          container.innerHTML = ''
 | 
			
		||||
 | 
			
		||||
          // TODO hydration
 | 
			
		||||
          render(vnode, container, isSVG)
 | 
			
		||||
 | 
			
		||||
          if (container instanceof Element) {
 | 
			
		||||
            container.removeAttribute('v-cloak')
 | 
			
		||||
            container.setAttribute('data-v-app', '')
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          isMounted = true
 | 
			
		||||
          app._container = container
 | 
			
		||||
          // for devtools and telemetry
 | 
			
		||||
          ;(container as any).__vue_app__ = app
 | 
			
		||||
          if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
 | 
			
		||||
            devtoolsInitApp(app, version)
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          return instance.proxy!
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        instance.ctx._compat_destroy = app.unmount
 | 
			
		||||
 | 
			
		||||
        return instance.proxy!
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return app
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -674,9 +674,10 @@ export function registerRuntimeCompiler(_compile: any) {
 | 
			
		||||
  compile = _compile
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function finishComponentSetup(
 | 
			
		||||
export function finishComponentSetup(
 | 
			
		||||
  instance: ComponentInternalInstance,
 | 
			
		||||
  isSSR: boolean
 | 
			
		||||
  isSSR: boolean,
 | 
			
		||||
  skipOptions?: boolean
 | 
			
		||||
) {
 | 
			
		||||
  const Component = instance.type as ComponentOptions
 | 
			
		||||
 | 
			
		||||
@ -719,7 +720,7 @@ function finishComponentSetup(
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // support for 2.x options
 | 
			
		||||
  if (__FEATURE_OPTIONS_API__) {
 | 
			
		||||
  if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
 | 
			
		||||
    currentInstance = instance
 | 
			
		||||
    pauseTracking()
 | 
			
		||||
    applyOptions(instance, Component)
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,9 @@ import {
 | 
			
		||||
  isGloballyWhitelisted,
 | 
			
		||||
  NOOP,
 | 
			
		||||
  extend,
 | 
			
		||||
  isString
 | 
			
		||||
  isString,
 | 
			
		||||
  warnDeprecation,
 | 
			
		||||
  DeprecationTypes
 | 
			
		||||
} from '@vue/shared'
 | 
			
		||||
import {
 | 
			
		||||
  ReactiveEffect,
 | 
			
		||||
@ -233,6 +235,25 @@ const publicPropertiesMap: PublicPropertiesMap = extend(Object.create(null), {
 | 
			
		||||
  $watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP)
 | 
			
		||||
} as PublicPropertiesMap)
 | 
			
		||||
 | 
			
		||||
if (__COMPAT__) {
 | 
			
		||||
  extend(publicPropertiesMap, {
 | 
			
		||||
    $mount: i => {
 | 
			
		||||
      if (__DEV__) {
 | 
			
		||||
        warnDeprecation(DeprecationTypes.$MOUNT)
 | 
			
		||||
      }
 | 
			
		||||
      // root mount override from apiCreateApp.ts
 | 
			
		||||
      return i.ctx._compat_mount || NOOP
 | 
			
		||||
    },
 | 
			
		||||
    $destroy: i => {
 | 
			
		||||
      if (__DEV__) {
 | 
			
		||||
        warnDeprecation(DeprecationTypes.$DESTROY)
 | 
			
		||||
      }
 | 
			
		||||
      // root destroy override from apiCreateApp.ts
 | 
			
		||||
      return i.ctx._compat_destroy || NOOP
 | 
			
		||||
    }
 | 
			
		||||
  } as PublicPropertiesMap)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const enum AccessTypes {
 | 
			
		||||
  SETUP,
 | 
			
		||||
  DATA,
 | 
			
		||||
 | 
			
		||||
@ -1292,11 +1292,16 @@ function baseCreateRenderer(
 | 
			
		||||
    isSVG,
 | 
			
		||||
    optimized
 | 
			
		||||
  ) => {
 | 
			
		||||
    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
 | 
			
		||||
      initialVNode,
 | 
			
		||||
      parentComponent,
 | 
			
		||||
      parentSuspense
 | 
			
		||||
    ))
 | 
			
		||||
    // 2.x compat may pre-creaate the component instance before actually
 | 
			
		||||
    // mounting
 | 
			
		||||
    const compatMountInstance = __COMPAT__ && initialVNode.component
 | 
			
		||||
    const instance: ComponentInternalInstance =
 | 
			
		||||
      compatMountInstance ||
 | 
			
		||||
      (initialVNode.component = createComponentInstance(
 | 
			
		||||
        initialVNode,
 | 
			
		||||
        parentComponent,
 | 
			
		||||
        parentSuspense
 | 
			
		||||
      ))
 | 
			
		||||
 | 
			
		||||
    if (__DEV__ && instance.type.__hmrId) {
 | 
			
		||||
      registerHMR(instance)
 | 
			
		||||
@ -1313,12 +1318,14 @@ function baseCreateRenderer(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // resolve props and slots for setup context
 | 
			
		||||
    if (__DEV__) {
 | 
			
		||||
      startMeasure(instance, `init`)
 | 
			
		||||
    }
 | 
			
		||||
    setupComponent(instance)
 | 
			
		||||
    if (__DEV__) {
 | 
			
		||||
      endMeasure(instance, `init`)
 | 
			
		||||
    if (!(__COMPAT__ && compatMountInstance)) {
 | 
			
		||||
      if (__DEV__) {
 | 
			
		||||
        startMeasure(instance, `init`)
 | 
			
		||||
      }
 | 
			
		||||
      setupComponent(instance)
 | 
			
		||||
      if (__DEV__) {
 | 
			
		||||
        endMeasure(instance, `init`)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // setup() is async. This component relies on async logic to be resolved
 | 
			
		||||
 | 
			
		||||
@ -72,17 +72,6 @@ export const createApp = ((...args) => {
 | 
			
		||||
    const container = normalizeContainer(containerOrSelector)
 | 
			
		||||
    if (!container) return
 | 
			
		||||
 | 
			
		||||
    // 2.x compat check
 | 
			
		||||
    if (__COMPAT__ && __DEV__) {
 | 
			
		||||
      for (let i = 0; i < container.attributes.length; i++) {
 | 
			
		||||
        const attr = container.attributes[i]
 | 
			
		||||
        if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
 | 
			
		||||
          warnDeprecation(DeprecationTypes.DOM_TEMPLATE_MOUNT)
 | 
			
		||||
          break
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const component = app._component
 | 
			
		||||
    if (!isFunction(component) && !component.render && !component.template) {
 | 
			
		||||
      // __UNSAFE__
 | 
			
		||||
@ -90,7 +79,18 @@ export const createApp = ((...args) => {
 | 
			
		||||
      // The user must make sure the in-DOM template is trusted. If it's
 | 
			
		||||
      // rendered by the server, the template should not contain any user data.
 | 
			
		||||
      component.template = container.innerHTML
 | 
			
		||||
      // 2.x compat check
 | 
			
		||||
      if (__COMPAT__ && __DEV__) {
 | 
			
		||||
        for (let i = 0; i < container.attributes.length; i++) {
 | 
			
		||||
          const attr = container.attributes[i]
 | 
			
		||||
          if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
 | 
			
		||||
            warnDeprecation(DeprecationTypes.DOM_TEMPLATE_MOUNT)
 | 
			
		||||
            break
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // clear content before mounting
 | 
			
		||||
    container.innerHTML = ''
 | 
			
		||||
    const proxy = mount(container, false, container instanceof SVGElement)
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,7 @@
 | 
			
		||||
export const enum DeprecationTypes {
 | 
			
		||||
  DOM_TEMPLATE_MOUNT
 | 
			
		||||
  DOM_TEMPLATE_MOUNT,
 | 
			
		||||
  $MOUNT,
 | 
			
		||||
  $DESTROY
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DeprecationData = {
 | 
			
		||||
@ -14,6 +16,18 @@ const deprecations: Record<DeprecationTypes, DeprecationData> = {
 | 
			
		||||
      `In Vue 3, the container is no longer considered part of the template ` +
 | 
			
		||||
      `and will not be processed/replaced.`,
 | 
			
		||||
    link: `https://v3.vuejs.org/guide/migration/mount-changes.html`
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  [DeprecationTypes.$MOUNT]: {
 | 
			
		||||
    message:
 | 
			
		||||
      `vm.$mount() has been deprecated. ` +
 | 
			
		||||
      `Use createApp(RootComponent).mount() instead.`,
 | 
			
		||||
    link: `https://v3.vuejs.org/guide/migration/global-api.html#mounting-app-instance`
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  [DeprecationTypes.$DESTROY]: {
 | 
			
		||||
    message: `vm.$destroy() has been deprecated. Use app.unmount() instead.`,
 | 
			
		||||
    link: `https://v3.vuejs.org/api/application-api.html#unmount`
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,7 @@ import {
 | 
			
		||||
  RenderFunction,
 | 
			
		||||
  isRuntimeOnly
 | 
			
		||||
} from '@vue/runtime-dom'
 | 
			
		||||
import { extend } from '@vue/shared'
 | 
			
		||||
 | 
			
		||||
// TODO make these getter/setters and trigger deprecation warnings
 | 
			
		||||
export type LegacyConfig = AppConfig & {
 | 
			
		||||
@ -89,6 +90,7 @@ export type GlobalVue = Pick<App, 'version' | 'component' | 'directive'> & {
 | 
			
		||||
 | 
			
		||||
export const Vue: GlobalVue = function Vue(options: ComponentOptions = {}) {
 | 
			
		||||
  const app = createApp(options)
 | 
			
		||||
 | 
			
		||||
  // copy over global config mutations
 | 
			
		||||
  for (const key in singletonApp.config) {
 | 
			
		||||
    if (
 | 
			
		||||
@ -99,8 +101,13 @@ export const Vue: GlobalVue = function Vue(options: ComponentOptions = {}) {
 | 
			
		||||
      app.config[key] = singletonApp.config[key]
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // TODO copy prototype augmentations as config.globalProperties
 | 
			
		||||
 | 
			
		||||
  if (options.el) {
 | 
			
		||||
    return app.mount(options.el)
 | 
			
		||||
  } else {
 | 
			
		||||
    return app._createRoot!(options)
 | 
			
		||||
  }
 | 
			
		||||
} as any
 | 
			
		||||
 | 
			
		||||
@ -109,7 +116,18 @@ const singletonApp = createApp({})
 | 
			
		||||
Vue.version = __VERSION__
 | 
			
		||||
Vue.config = singletonApp.config
 | 
			
		||||
 | 
			
		||||
Vue.extend = defineComponent
 | 
			
		||||
Vue.extend = ((baseOptions: ComponentOptions = {}) => {
 | 
			
		||||
  return function ExtendedVueConstructor(inlineOptions?: ComponentOptions) {
 | 
			
		||||
    if (!inlineOptions) {
 | 
			
		||||
      return new Vue(baseOptions)
 | 
			
		||||
    } else {
 | 
			
		||||
      const mergedOptions = extend({}, baseOptions)
 | 
			
		||||
      mergedOptions.mixins = [inlineOptions, ...(mergedOptions.mixins || [])]
 | 
			
		||||
      return new Vue(mergedOptions)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}) as any
 | 
			
		||||
 | 
			
		||||
Vue.nextTick = nextTick
 | 
			
		||||
 | 
			
		||||
Vue.set = (target, key, value) => {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user