wip: root mount api compat
This commit is contained in:
parent
24850a99c6
commit
e2d6ff845b
@ -2,19 +2,28 @@ import {
|
|||||||
ConcreteComponent,
|
ConcreteComponent,
|
||||||
Data,
|
Data,
|
||||||
validateComponentName,
|
validateComponentName,
|
||||||
Component
|
Component,
|
||||||
|
createComponentInstance,
|
||||||
|
setupComponent,
|
||||||
|
finishComponentSetup
|
||||||
} from './component'
|
} from './component'
|
||||||
import { ComponentOptions } from './componentOptions'
|
import { ComponentOptions } from './componentOptions'
|
||||||
import { ComponentPublicInstance } from './componentPublicInstance'
|
import { ComponentPublicInstance } from './componentPublicInstance'
|
||||||
import { Directive, validateDirectiveName } from './directives'
|
import { Directive, validateDirectiveName } from './directives'
|
||||||
import { RootRenderFunction } from './renderer'
|
import { RootRenderFunction } from './renderer'
|
||||||
import { InjectionKey } from './apiInject'
|
import { InjectionKey } from './apiInject'
|
||||||
import { isFunction, NO, isObject } from '@vue/shared'
|
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { createVNode, cloneVNode, VNode } from './vnode'
|
import { createVNode, cloneVNode, VNode } from './vnode'
|
||||||
import { RootHydrateFunction } from './hydration'
|
import { RootHydrateFunction } from './hydration'
|
||||||
import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
|
import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
|
||||||
import { version } from '.'
|
import { version } from '.'
|
||||||
|
import {
|
||||||
|
isFunction,
|
||||||
|
NO,
|
||||||
|
isObject,
|
||||||
|
warnDeprecation,
|
||||||
|
DeprecationTypes
|
||||||
|
} from '@vue/shared'
|
||||||
|
|
||||||
export interface App<HostElement = any> {
|
export interface App<HostElement = any> {
|
||||||
version: string
|
version: string
|
||||||
@ -39,6 +48,11 @@ export interface App<HostElement = any> {
|
|||||||
_props: Data | null
|
_props: Data | null
|
||||||
_container: HostElement | null
|
_container: HostElement | null
|
||||||
_context: AppContext
|
_context: AppContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal 2.x compat only
|
||||||
|
*/
|
||||||
|
_createRoot?(options: ComponentOptions): ComponentPublicInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OptionMergeFunction = (
|
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
|
return app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -674,9 +674,10 @@ export function registerRuntimeCompiler(_compile: any) {
|
|||||||
compile = _compile
|
compile = _compile
|
||||||
}
|
}
|
||||||
|
|
||||||
function finishComponentSetup(
|
export function finishComponentSetup(
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
isSSR: boolean
|
isSSR: boolean,
|
||||||
|
skipOptions?: boolean
|
||||||
) {
|
) {
|
||||||
const Component = instance.type as ComponentOptions
|
const Component = instance.type as ComponentOptions
|
||||||
|
|
||||||
@ -719,7 +720,7 @@ function finishComponentSetup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// support for 2.x options
|
// support for 2.x options
|
||||||
if (__FEATURE_OPTIONS_API__) {
|
if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
|
||||||
currentInstance = instance
|
currentInstance = instance
|
||||||
pauseTracking()
|
pauseTracking()
|
||||||
applyOptions(instance, Component)
|
applyOptions(instance, Component)
|
||||||
|
@ -11,7 +11,9 @@ import {
|
|||||||
isGloballyWhitelisted,
|
isGloballyWhitelisted,
|
||||||
NOOP,
|
NOOP,
|
||||||
extend,
|
extend,
|
||||||
isString
|
isString,
|
||||||
|
warnDeprecation,
|
||||||
|
DeprecationTypes
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
ReactiveEffect,
|
ReactiveEffect,
|
||||||
@ -233,6 +235,25 @@ const publicPropertiesMap: PublicPropertiesMap = extend(Object.create(null), {
|
|||||||
$watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP)
|
$watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP)
|
||||||
} as PublicPropertiesMap)
|
} 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 {
|
const enum AccessTypes {
|
||||||
SETUP,
|
SETUP,
|
||||||
DATA,
|
DATA,
|
||||||
|
@ -1292,11 +1292,16 @@ function baseCreateRenderer(
|
|||||||
isSVG,
|
isSVG,
|
||||||
optimized
|
optimized
|
||||||
) => {
|
) => {
|
||||||
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
|
// 2.x compat may pre-creaate the component instance before actually
|
||||||
initialVNode,
|
// mounting
|
||||||
parentComponent,
|
const compatMountInstance = __COMPAT__ && initialVNode.component
|
||||||
parentSuspense
|
const instance: ComponentInternalInstance =
|
||||||
))
|
compatMountInstance ||
|
||||||
|
(initialVNode.component = createComponentInstance(
|
||||||
|
initialVNode,
|
||||||
|
parentComponent,
|
||||||
|
parentSuspense
|
||||||
|
))
|
||||||
|
|
||||||
if (__DEV__ && instance.type.__hmrId) {
|
if (__DEV__ && instance.type.__hmrId) {
|
||||||
registerHMR(instance)
|
registerHMR(instance)
|
||||||
@ -1313,12 +1318,14 @@ function baseCreateRenderer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// resolve props and slots for setup context
|
// resolve props and slots for setup context
|
||||||
if (__DEV__) {
|
if (!(__COMPAT__ && compatMountInstance)) {
|
||||||
startMeasure(instance, `init`)
|
if (__DEV__) {
|
||||||
}
|
startMeasure(instance, `init`)
|
||||||
setupComponent(instance)
|
}
|
||||||
if (__DEV__) {
|
setupComponent(instance)
|
||||||
endMeasure(instance, `init`)
|
if (__DEV__) {
|
||||||
|
endMeasure(instance, `init`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup() is async. This component relies on async logic to be resolved
|
// setup() is async. This component relies on async logic to be resolved
|
||||||
|
@ -72,17 +72,6 @@ export const createApp = ((...args) => {
|
|||||||
const container = normalizeContainer(containerOrSelector)
|
const container = normalizeContainer(containerOrSelector)
|
||||||
if (!container) return
|
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
|
const component = app._component
|
||||||
if (!isFunction(component) && !component.render && !component.template) {
|
if (!isFunction(component) && !component.render && !component.template) {
|
||||||
// __UNSAFE__
|
// __UNSAFE__
|
||||||
@ -90,7 +79,18 @@ export const createApp = ((...args) => {
|
|||||||
// The user must make sure the in-DOM template is trusted. If it's
|
// 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.
|
// rendered by the server, the template should not contain any user data.
|
||||||
component.template = container.innerHTML
|
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
|
// clear content before mounting
|
||||||
container.innerHTML = ''
|
container.innerHTML = ''
|
||||||
const proxy = mount(container, false, container instanceof SVGElement)
|
const proxy = mount(container, false, container instanceof SVGElement)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
export const enum DeprecationTypes {
|
export const enum DeprecationTypes {
|
||||||
DOM_TEMPLATE_MOUNT
|
DOM_TEMPLATE_MOUNT,
|
||||||
|
$MOUNT,
|
||||||
|
$DESTROY
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeprecationData = {
|
type DeprecationData = {
|
||||||
@ -14,6 +16,18 @@ const deprecations: Record<DeprecationTypes, DeprecationData> = {
|
|||||||
`In Vue 3, the container is no longer considered part of the template ` +
|
`In Vue 3, the container is no longer considered part of the template ` +
|
||||||
`and will not be processed/replaced.`,
|
`and will not be processed/replaced.`,
|
||||||
link: `https://v3.vuejs.org/guide/migration/mount-changes.html`
|
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,
|
RenderFunction,
|
||||||
isRuntimeOnly
|
isRuntimeOnly
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
|
import { extend } from '@vue/shared'
|
||||||
|
|
||||||
// TODO make these getter/setters and trigger deprecation warnings
|
// TODO make these getter/setters and trigger deprecation warnings
|
||||||
export type LegacyConfig = AppConfig & {
|
export type LegacyConfig = AppConfig & {
|
||||||
@ -89,6 +90,7 @@ export type GlobalVue = Pick<App, 'version' | 'component' | 'directive'> & {
|
|||||||
|
|
||||||
export const Vue: GlobalVue = function Vue(options: ComponentOptions = {}) {
|
export const Vue: GlobalVue = function Vue(options: ComponentOptions = {}) {
|
||||||
const app = createApp(options)
|
const app = createApp(options)
|
||||||
|
|
||||||
// copy over global config mutations
|
// copy over global config mutations
|
||||||
for (const key in singletonApp.config) {
|
for (const key in singletonApp.config) {
|
||||||
if (
|
if (
|
||||||
@ -99,8 +101,13 @@ export const Vue: GlobalVue = function Vue(options: ComponentOptions = {}) {
|
|||||||
app.config[key] = singletonApp.config[key]
|
app.config[key] = singletonApp.config[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO copy prototype augmentations as config.globalProperties
|
||||||
|
|
||||||
if (options.el) {
|
if (options.el) {
|
||||||
return app.mount(options.el)
|
return app.mount(options.el)
|
||||||
|
} else {
|
||||||
|
return app._createRoot!(options)
|
||||||
}
|
}
|
||||||
} as any
|
} as any
|
||||||
|
|
||||||
@ -109,7 +116,18 @@ const singletonApp = createApp({})
|
|||||||
Vue.version = __VERSION__
|
Vue.version = __VERSION__
|
||||||
Vue.config = singletonApp.config
|
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.nextTick = nextTick
|
||||||
|
|
||||||
Vue.set = (target, key, value) => {
|
Vue.set = (target, key, value) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user