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
_props: Data | null
_container: HostElement | null
_context: AppContext
+
+ /**
+ * @internal 2.x compat only
+ */
+ _createRoot?(options: ComponentOptions): ComponentPublicInstance
}
export type OptionMergeFunction = (
}
})
+ 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
}
}
compile = _compile
}
-function finishComponentSetup(
+export function finishComponentSetup(
instance: ComponentInternalInstance,
- isSSR: boolean
+ isSSR: boolean,
+ skipOptions?: boolean
) {
const Component = instance.type as ComponentOptions
}
// support for 2.x options
- if (__FEATURE_OPTIONS_API__) {
+ if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
currentInstance = instance
pauseTracking()
applyOptions(instance, Component)
isGloballyWhitelisted,
NOOP,
extend,
- isString
+ isString,
+ warnDeprecation,
+ DeprecationTypes
} from '@vue/shared'
import {
ReactiveEffect,
$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,
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)
}
// 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
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__
// 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)
export const enum DeprecationTypes {
- DOM_TEMPLATE_MOUNT
+ DOM_TEMPLATE_MOUNT,
+ $MOUNT,
+ $DESTROY
}
type 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`
}
}
RenderFunction,
isRuntimeOnly
} from '@vue/runtime-dom'
+import { extend } from '@vue/shared'
// TODO make these getter/setters and trigger deprecation warnings
export type LegacyConfig = AppConfig & {
export const Vue: GlobalVue = function Vue(options: ComponentOptions = {}) {
const app = createApp(options)
+
// copy over global config mutations
for (const key in singletonApp.config) {
if (
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
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) => {