wip: progress

This commit is contained in:
Evan You 2021-04-05 18:13:29 -04:00
parent 40e3dd28e1
commit 53b8127a9c
10 changed files with 341 additions and 256 deletions

View File

@ -15,8 +15,7 @@ import { RootHydrateFunction } from './hydration'
import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
import { isFunction, NO, isObject } from '@vue/shared'
import { version } from '.'
import { installCompatMount } from './compat/globalMount'
import { installLegacyConfigTraps } from './compat/globalConfig'
import { installCompatMount, installLegacyConfigTraps } from './compat/global'
export interface App<HostElement = any> {
version: string
@ -307,7 +306,7 @@ export function createAppAPI<HostElement>(
if (__COMPAT__) {
installCompatMount(app, context, render, hydrate)
installLegacyConfigTraps(app.config)
if (__DEV__) installLegacyConfigTraps(app.config)
}
return app

View File

@ -1,15 +1,22 @@
import { isRuntimeOnly } from '../component'
export const enum DeprecationTypes {
DOM_TEMPLATE_MOUNT,
$MOUNT,
$DESTROY,
CONFIG_SILENT,
CONFIG_DEVTOOLS,
CONFIG_KEY_CODES,
CONFIG_PRODUCTION_TIP,
CONFIG_IGNORED_ELEMENTS
CONFIG_IGNORED_ELEMENTS,
GLOBAL_PROTOTYPE,
GLOBAL_SET,
GLOBAL_DELETE,
GLOBAL_OBSERVABLE,
GLOBAL_DOM_TEMPLATE_MOUNT,
INSTANCE_SET,
INSTANCE_DELETE,
INSTANCE_MOUNT,
INSTANCE_DESTROY
}
type DeprecationData = {
@ -18,26 +25,6 @@ type DeprecationData = {
}
const deprecations: Record<DeprecationTypes, DeprecationData> = {
[DeprecationTypes.DOM_TEMPLATE_MOUNT]: {
message:
`Vue detected directives on the mount container. ` +
`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 removed. ` +
`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 removed. Use app.unmount() instead.`,
link: `https://v3.vuejs.org/api/application-api.html#unmount`
},
[DeprecationTypes.CONFIG_SILENT]: {
message:
`config.silent has been removed because it is not good practice to ` +
@ -75,6 +62,64 @@ const deprecations: Record<DeprecationTypes, DeprecationData> = {
return msg
},
link: `https://v3.vuejs.org/guide/migration/global-api.html#config-ignoredelements-is-now-config-iscustomelement`
},
[DeprecationTypes.GLOBAL_PROTOTYPE]: {
message:
`Vue.prototype is no longer available in Vue 3. ` +
`Use config.globalProperties instead.`,
link: `https://v3.vuejs.org/guide/migration/global-api.html#vue-prototype-replaced-by-config-globalproperties`
},
[DeprecationTypes.GLOBAL_SET]: {
message:
`Vue.set() has been removed as it is no longer needed in Vue 3. ` +
`Simply use native JavaScript mutations.`
},
[DeprecationTypes.GLOBAL_DELETE]: {
message:
`Vue.delete() has been removed as it is no longer needed in Vue 3. ` +
`Simply use native JavaScript mutations.`
},
[DeprecationTypes.GLOBAL_OBSERVABLE]: {
message:
`Vue.observable() has been removed. ` +
`Use \`import { reactive } from "vue"\` from Composition API instead.`,
link: `https://v3.vuejs.org/api/basic-reactivity.html`
},
[DeprecationTypes.GLOBAL_DOM_TEMPLATE_MOUNT]: {
message:
`Vue detected directives on the mount container. ` +
`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.INSTANCE_SET]: {
message:
`vm.$set() has been removed as it is no longer needed in Vue 3. ` +
`Simply use native JavaScript mutations.`
},
[DeprecationTypes.INSTANCE_DELETE]: {
message:
`vm.$delete() has been removed as it is no longer needed in Vue 3. ` +
`Simply use native JavaScript mutations.`
},
[DeprecationTypes.INSTANCE_MOUNT]: {
message:
`The global app boostrapping API has changed: vm.$mount() and the "el" ` +
`option have been removed. Use createApp(RootComponent).mount() instead.`,
link: `https://v3.vuejs.org/guide/migration/global-api.html#mounting-app-instance`
},
[DeprecationTypes.INSTANCE_DESTROY]: {
message: `vm.$destroy() has been removed. Use app.unmount() instead.`,
link: `https://v3.vuejs.org/api/application-api.html#unmount`
}
}

View File

@ -1,13 +1,32 @@
import { reactive } from '@vue/reactivity'
import { extend } from '@vue/shared'
import { createApp } from '../../../runtime-dom/src'
import { App, AppConfig, Plugin } from '../apiCreateApp'
import { isFunction } from '@vue/shared'
import { warn } from '../warning'
import { cloneVNode, createVNode } from '../vnode'
import { RootRenderFunction } from '../renderer'
import { RootHydrateFunction } from '../hydration'
import {
App,
AppConfig,
AppContext,
CreateAppFunction,
Plugin
} from '../apiCreateApp'
import { defineComponent } from '../apiDefineComponent'
import { Component, ComponentOptions, isRuntimeOnly } from '../component'
import {
Component,
ComponentOptions,
createComponentInstance,
finishComponentSetup,
isRuntimeOnly,
setupComponent
} from '../component'
import { RenderFunction } from '../componentOptions'
import { ComponentPublicInstance } from '../componentPublicInstance'
import { devtoolsInitApp } from '../devtools'
import { Directive } from '../directives'
import { nextTick } from '../scheduler'
import { warnDeprecation, DeprecationTypes } from './deprecations'
import { version } from '..'
/**
* @deprecated the default `Vue` export has been removed in Vue 3. The type for
@ -21,7 +40,7 @@ export type CompatVue = Pick<App, 'version' | 'component' | 'directive'> & {
new (options?: ComponentOptions): ComponentPublicInstance
version: string
config: AppConfig
config: AppConfig & LegacyConfig
extend: typeof defineComponent
nextTick: typeof nextTick
@ -54,10 +73,40 @@ export type CompatVue = Pick<App, 'version' | 'component' | 'directive'> & {
filter(name: string, arg: any): null
}
// legacy config warnings
export type LegacyConfig = {
/**
* @deprecated `config.silent` option has been removed
*/
silent?: boolean
/**
* @deprecated use __VUE_PROD_DEVTOOLS__ compile-time feature flag instead
* https://github.com/vuejs/vue-next/tree/master/packages/vue#bundler-build-feature-flags
*/
devtools?: boolean
/**
* @deprecated use `config.isCustomElement` instead
* https://v3.vuejs.org/guide/migration/global-api.html#config-ignoredelements-is-now-config-iscustomelement
*/
ignoredElements?: (string | RegExp)[]
/**
* @deprecated
* https://v3.vuejs.org/guide/migration/keycode-modifiers.html
*/
keyCodes?: Record<string, number | number[]>
/**
* @deprecated
* https://v3.vuejs.org/guide/migration/global-api.html#config-productiontip-removed
*/
productionTip?: boolean
}
export let isCopyingConfig = false
// Legacy global Vue constructor
export function createCompatVue(): CompatVue {
export function createCompatVue(
createApp: CreateAppFunction<Element>
): CompatVue {
if (!__COMPAT__) {
// @ts-ignore this function will never be called in non-compat mode
return
@ -86,9 +135,16 @@ export function createCompatVue(): CompatVue {
isCopyingConfig = false
// copy prototype augmentations as config.globalProperties
let hasPrototypeAugmentations = false
for (const key in Ctor.prototype) {
if (key !== 'constructor') {
hasPrototypeAugmentations = true
}
app.config.globalProperties[key] = Ctor.prototype[key]
}
if (hasPrototypeAugmentations) {
__DEV__ && warnDeprecation(DeprecationTypes.GLOBAL_PROTOTYPE)
}
const vm = app._createRoot!(options)
if (options.el) {
@ -124,15 +180,21 @@ export function createCompatVue(): CompatVue {
Vue.nextTick = nextTick
Vue.set = (target, key, value) => {
// TODO deprecation warnings
__DEV__ && warnDeprecation(DeprecationTypes.GLOBAL_SET)
target[key] = value
}
Vue.delete = (target, key) => {
// TODO deprecation warnings
__DEV__ && warnDeprecation(DeprecationTypes.GLOBAL_DELETE)
delete target[key]
}
// TODO wrap with deprecation warning
Vue.observable = reactive
Vue.observable = __DEV__
? (target: any) => {
warnDeprecation(DeprecationTypes.GLOBAL_OBSERVABLE)
return reactive(target)
}
: reactive
Vue.use = (p, ...options) => {
singletonApp.use(p, ...options)
@ -169,3 +231,156 @@ export function createCompatVue(): CompatVue {
return Vue
}
export function installCompatMount(
app: App,
context: AppContext,
render: RootRenderFunction,
hydrate?: RootHydrateFunction
) {
let isMounted = false
/**
* 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 component = app._component
const vnode = createVNode(component, options.propsData || null)
vnode.appContext = context
const hasNoRender =
!isFunction(component) && !component.render && !component.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.GLOBAL_DOM_TEMPLATE_MOUNT)
break
}
}
}
instance.render = null
;(component 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!
}
}
// dev only
export function installLegacyConfigTraps(config: AppConfig) {
const legacyConfigOptions: Record<string, DeprecationTypes> = {
silent: DeprecationTypes.CONFIG_SILENT,
devtools: DeprecationTypes.CONFIG_DEVTOOLS,
ignoredElements: DeprecationTypes.CONFIG_IGNORED_ELEMENTS,
keyCodes: DeprecationTypes.CONFIG_KEY_CODES,
productionTip: DeprecationTypes.CONFIG_PRODUCTION_TIP
}
Object.keys(legacyConfigOptions).forEach(key => {
let val = (config as any)[key]
Object.defineProperty(config, key, {
enumerable: true,
get() {
return val
},
set(newVal) {
if (!isCopyingConfig) {
warnDeprecation(legacyConfigOptions[key])
}
val = newVal
}
})
})
}

View File

@ -1,57 +0,0 @@
import { AppConfig } from '../apiCreateApp'
import { DeprecationTypes, warnDeprecation } from './deprecations'
import { isCopyingConfig } from './global'
// legacy config warnings
export type LegacyConfig = {
/**
* @deprecated `config.silent` option has been removed
*/
silent?: boolean
/**
* @deprecated use __VUE_PROD_DEVTOOLS__ compile-time feature flag instead
* https://github.com/vuejs/vue-next/tree/master/packages/vue#bundler-build-feature-flags
*/
devtools?: boolean
/**
* @deprecated use `config.isCustomElement` instead
* https://v3.vuejs.org/guide/migration/global-api.html#config-ignoredelements-is-now-config-iscustomelement
*/
ignoredElements?: (string | RegExp)[]
/**
* @deprecated
* https://v3.vuejs.org/guide/migration/keycode-modifiers.html
*/
keyCodes?: Record<string, number | number[]>
/**
* @deprecated
* https://v3.vuejs.org/guide/migration/global-api.html#config-productiontip-removed
*/
productionTip?: boolean
}
export function installLegacyConfigTraps(config: AppConfig) {
const legacyConfigOptions: Record<string, DeprecationTypes> = {
silent: DeprecationTypes.CONFIG_SILENT,
devtools: DeprecationTypes.CONFIG_DEVTOOLS,
ignoredElements: DeprecationTypes.CONFIG_IGNORED_ELEMENTS,
keyCodes: DeprecationTypes.CONFIG_KEY_CODES,
productionTip: DeprecationTypes.CONFIG_PRODUCTION_TIP
}
Object.keys(legacyConfigOptions).forEach(key => {
let val = (config as any)[key]
Object.defineProperty(config, key, {
enumerable: true,
get() {
return val
},
set(newVal) {
if (!isCopyingConfig) {
warnDeprecation(legacyConfigOptions[key])
}
val = newVal
}
})
})
}

View File

@ -1,141 +0,0 @@
import { isFunction } from '@vue/shared'
import { App, AppContext } from '../apiCreateApp'
import {
ComponentOptions,
createComponentInstance,
finishComponentSetup,
setupComponent
} from '../component'
import { devtoolsInitApp } from '../devtools'
import { RootHydrateFunction } from '../hydration'
import { RootRenderFunction } from '../renderer'
import { cloneVNode, createVNode } from '../vnode'
import { warn } from '../warning'
import { version } from '..'
import { DeprecationTypes, warnDeprecation } from './deprecations'
export function installCompatMount(
app: App,
context: AppContext,
render: RootRenderFunction,
hydrate?: RootHydrateFunction
) {
let isMounted = false
/**
* 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 component = app._component
const vnode = createVNode(component, options.propsData || null)
vnode.appContext = context
const hasNoRender =
!isFunction(component) && !component.render && !component.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
;(component 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!
}
}

View File

@ -0,0 +1,34 @@
import { extend, NOOP } from '@vue/shared'
import { PublicPropertiesMap } from '../componentPublicInstance'
import { DeprecationTypes, warnDeprecation } from './deprecations'
export function installCompatInstanceProperties(map: PublicPropertiesMap) {
const set = (target: any, key: any, val: any) => {
target[key] = val
}
const del = (target: any, key: any) => {
delete target[key]
}
extend(map, {
$set: () => {
__DEV__ && warnDeprecation(DeprecationTypes.INSTANCE_SET)
return set
},
$delete: () => {
__DEV__ && warnDeprecation(DeprecationTypes.INSTANCE_DELETE)
return del
},
$mount: i => {
__DEV__ && warnDeprecation(DeprecationTypes.INSTANCE_MOUNT)
// root mount override from ./global.ts in installCompatMount
return i.ctx._compat_mount || NOOP
},
$destroy: i => {
__DEV__ && warnDeprecation(DeprecationTypes.INSTANCE_DESTROY)
// root destroy override from ./global.ts in installCompatMount
return i.ctx._compat_destroy || NOOP
}
} as PublicPropertiesMap)
}

View File

@ -41,7 +41,8 @@ import { markAttrsAccessed } from './componentRenderUtils'
import { currentRenderingInstance } from './componentRenderContext'
import { warn } from './warning'
import { UnionToIntersection } from './helpers/typeUtils'
import { warnDeprecation, DeprecationTypes } from './compat/deprecations'
import { installCompatInstanceProperties } from './compat/instance'
/**
* Custom properties added to component instances in any way and can be accessed through `this`
*
@ -202,7 +203,10 @@ export type ComponentPublicInstance<
M &
ComponentCustomProperties
type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>
export type PublicPropertiesMap = Record<
string,
(i: ComponentInternalInstance) => any
>
/**
* #2437 In Vue 3, functional components do not have a public instance proxy but
@ -235,22 +239,7 @@ const publicPropertiesMap: PublicPropertiesMap = extend(Object.create(null), {
} 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)
installCompatInstanceProperties(publicPropertiesMap)
}
const enum AccessTypes {

View File

@ -78,7 +78,7 @@ export const createApp = ((...args) => {
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)
warnDeprecation(DeprecationTypes.GLOBAL_DOM_TEMPLATE_MOUNT)
break
}
}

View File

@ -6,6 +6,7 @@ import {
registerRuntimeCompiler,
RenderFunction,
warn,
createApp,
createCompatVue
} from '@vue/runtime-dom'
import { isString, NOOP, generateCodeFrame, extend } from '@vue/shared'
@ -91,7 +92,7 @@ function compileToFunction(
registerRuntimeCompiler(compileToFunction)
const Vue = createCompatVue()
const Vue = createCompatVue(createApp)
Vue.compile = compileToFunction
extend(Vue, runtimeDom)