wip: root mount api compat
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user