diff --git a/jest.config.js b/jest.config.js index e8507b5d..7ec66b39 100644 --- a/jest.config.js +++ b/jest.config.js @@ -12,7 +12,8 @@ module.exports = { __NODE_JS__: true, __FEATURE_OPTIONS_API__: true, __FEATURE_SUSPENSE__: true, - __FEATURE_PROD_DEVTOOLS__: false + __FEATURE_PROD_DEVTOOLS__: false, + __COMPAT__: true }, coverageDirectory: 'coverage', coverageReporters: ['html', 'lcov', 'text'], diff --git a/packages/runtime-core/src/apiCreateApp.ts b/packages/runtime-core/src/apiCreateApp.ts index 8ef0a2a2..5af5babd 100644 --- a/packages/runtime-core/src/apiCreateApp.ts +++ b/packages/runtime-core/src/apiCreateApp.ts @@ -2,10 +2,7 @@ import { ConcreteComponent, Data, validateComponentName, - Component, - createComponentInstance, - setupComponent, - finishComponentSetup + Component } from './component' import { ComponentOptions } from './componentOptions' import { ComponentPublicInstance } from './componentPublicInstance' @@ -16,14 +13,10 @@ import { warn } from './warning' import { createVNode, cloneVNode, VNode } from './vnode' import { RootHydrateFunction } from './hydration' import { devtoolsInitApp, devtoolsUnmountApp } from './devtools' +import { isFunction, NO, isObject } from '@vue/shared' import { version } from '.' -import { - isFunction, - NO, - isObject, - warnDeprecation, - DeprecationTypes -} from '@vue/shared' +import { installCompatMount } from './compat/globalMount' +import { installLegacyConfigTraps } from './compat/globalConfig' export interface App { version: string @@ -313,126 +306,8 @@ export function createAppAPI( }) 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! - } + installCompatMount(app, context, render, hydrate) + installLegacyConfigTraps(app.config) } return app diff --git a/packages/runtime-core/src/compat/deprecations.ts b/packages/runtime-core/src/compat/deprecations.ts new file mode 100644 index 00000000..ae5a39b7 --- /dev/null +++ b/packages/runtime-core/src/compat/deprecations.ts @@ -0,0 +1,91 @@ +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 +} + +type DeprecationData = { + message: string | (() => string) + link?: string +} + +const deprecations: Record = { + [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 ` + + `intentionally suppress warnings. You can use your browser console's ` + + `filter features to focus on relevant messages.` + }, + + [DeprecationTypes.CONFIG_DEVTOOLS]: { + message: + `config.devtools has been removed. To enable devtools for ` + + `production, configure the __VUE_PROD_DEVTOOLS__ compile-time flag.`, + link: `https://github.com/vuejs/vue-next/tree/master/packages/vue#bundler-build-feature-flags` + }, + + [DeprecationTypes.CONFIG_KEY_CODES]: { + message: + `config.keyCodes has been removed. ` + + `In Vue 3, you can directly use the kebab-case key names as v-on modifiers.`, + link: `https://v3.vuejs.org/guide/migration/keycode-modifiers.html` + }, + + [DeprecationTypes.CONFIG_PRODUCTION_TIP]: { + message: `config.productionTip has been removed.`, + link: `https://v3.vuejs.org/guide/migration/global-api.html#config-productiontip-removed` + }, + + [DeprecationTypes.CONFIG_IGNORED_ELEMENTS]: { + message: () => { + let msg = `config.ignoredElements has been removed.` + if (isRuntimeOnly()) { + msg += ` Pass the "isCustomElement" option to @vue/compiler-dom instead.` + } else { + msg += ` Use config.isCustomElement instead.` + } + return msg + }, + link: `https://v3.vuejs.org/guide/migration/global-api.html#config-ignoredelements-is-now-config-iscustomelement` + } +} + +export function warnDeprecation(key: DeprecationTypes) { + if (!__COMPAT__ || !__DEV__) { + return + } + const { message, link } = deprecations[key] + console.warn( + `[Vue Deprecation]: ${typeof message === 'function' ? message() : message}${ + link ? `\nFor more details, see ${link}` : `` + }` + ) +} diff --git a/packages/runtime-core/src/compat/global.ts b/packages/runtime-core/src/compat/global.ts new file mode 100644 index 00000000..5088ddf9 --- /dev/null +++ b/packages/runtime-core/src/compat/global.ts @@ -0,0 +1,171 @@ +import { reactive } from '@vue/reactivity' +import { extend } from '@vue/shared' +import { createApp } from '../../../runtime-dom/src' +import { App, AppConfig, Plugin } from '../apiCreateApp' +import { defineComponent } from '../apiDefineComponent' +import { Component, ComponentOptions, isRuntimeOnly } from '../component' +import { RenderFunction } from '../componentOptions' +import { ComponentPublicInstance } from '../componentPublicInstance' +import { Directive } from '../directives' +import { nextTick } from '../scheduler' + +/** + * @deprecated the default `Vue` export has been removed in Vue 3. The type for + * the default export is provided only for migration purposes. Please use + * named imports instead - e.g. `import { createApp } from 'vue'`. + */ +export type CompatVue = Pick & { + // no inference here since these types are not meant for actual use - they + // are merely here to provide type checks for internal implementation and + // information for migration. + new (options?: ComponentOptions): ComponentPublicInstance + + version: string + config: AppConfig + + extend: typeof defineComponent + nextTick: typeof nextTick + + use(plugin: Plugin, ...options: any[]): CompatVue + mixin(mixin: ComponentOptions): CompatVue + + component(name: string): Component | undefined + component(name: string, component: Component): CompatVue + directive(name: string): Directive | undefined + directive(name: string, directive: Directive): CompatVue + + compile(template: string): RenderFunction + + /** + * @deprecated Vue 3 no longer needs set() for adding new properties. + */ + set(target: any, key: string | number | symbol, value: any): void + /** + * @deprecated Vue 3 no longer needs delete() for property deletions. + */ + delete(target: any, key: string | number | symbol): void + /** + * @deprecated use `reactive` instead. + */ + observable: typeof reactive + /** + * @deprecated filters have been removed from Vue 3. + */ + filter(name: string, arg: any): null +} + +export let isCopyingConfig = false + +// Legacy global Vue constructor +export function createCompatVue(): CompatVue { + if (!__COMPAT__) { + // @ts-ignore this function will never be called in non-compat mode + return + } + + const Vue: CompatVue = function Vue(options: ComponentOptions = {}) { + return createCompatApp(options, Vue) + } as any + + const singletonApp = createApp({}) + + function createCompatApp(options: ComponentOptions = {}, Ctor: any) { + const app = createApp(options) + + // copy over global config mutations + isCopyingConfig = true + for (const key in singletonApp.config) { + if ( + key !== 'isNativeTag' && + !(key === 'isCustomElement' && isRuntimeOnly()) + ) { + // @ts-ignore + app.config[key] = singletonApp.config[key] + } + } + isCopyingConfig = false + + // copy prototype augmentations as config.globalProperties + for (const key in Ctor.prototype) { + app.config.globalProperties[key] = Ctor.prototype[key] + } + + const vm = app._createRoot!(options) + if (options.el) { + return (vm as any).$mount(options.el) + } else { + return vm + } + } + + Vue.version = __VERSION__ + Vue.config = singletonApp.config + + Vue.extend = ((options: ComponentOptions = {}) => { + function SubVue(inlineOptions?: ComponentOptions) { + if (!inlineOptions) { + return createCompatApp(options, SubVue) + } else { + return createCompatApp( + { + el: inlineOptions.el, + extends: options, + mixins: [inlineOptions] + }, + SubVue + ) + } + } + SubVue.prototype = Object.create(Vue.prototype) + SubVue.prototype.constructor = SubVue + return SubVue + }) as any + + Vue.nextTick = nextTick + + Vue.set = (target, key, value) => { + // TODO deprecation warnings + target[key] = value + } + Vue.delete = (target, key) => { + // TODO deprecation warnings + delete target[key] + } + // TODO wrap with deprecation warning + Vue.observable = reactive + + Vue.use = (p, ...options) => { + singletonApp.use(p, ...options) + return Vue + } + + Vue.mixin = m => { + singletonApp.mixin(m) + return Vue + } + + Vue.component = ((name: string, comp: any) => { + if (comp) { + singletonApp.component(name, comp) + return Vue + } else { + return singletonApp.component(name) + } + }) as any + + Vue.directive = ((name: string, dir: any) => { + if (dir) { + singletonApp.directive(name, dir) + return Vue + } else { + return singletonApp.directive(name) + } + }) as any + + Vue.filter = ((name: string, filter: any) => { + // TODO deprecation warning + // TODO compiler warning for filters (maybe behavior compat?) + }) as any + + return Vue +} diff --git a/packages/runtime-core/src/compat/globalConfig.ts b/packages/runtime-core/src/compat/globalConfig.ts new file mode 100644 index 00000000..e8eafae0 --- /dev/null +++ b/packages/runtime-core/src/compat/globalConfig.ts @@ -0,0 +1,57 @@ +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 + /** + * @deprecated + * https://v3.vuejs.org/guide/migration/global-api.html#config-productiontip-removed + */ + productionTip?: boolean +} + +export function installLegacyConfigTraps(config: AppConfig) { + const legacyConfigOptions: Record = { + 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 + } + }) + }) +} diff --git a/packages/runtime-core/src/compat/globalMount.ts b/packages/runtime-core/src/compat/globalMount.ts new file mode 100644 index 00000000..0e30d6c5 --- /dev/null +++ b/packages/runtime-core/src/compat/globalMount.ts @@ -0,0 +1,141 @@ +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! + } +} diff --git a/packages/runtime-core/src/componentPublicInstance.ts b/packages/runtime-core/src/componentPublicInstance.ts index c2cc5c16..7572c430 100644 --- a/packages/runtime-core/src/componentPublicInstance.ts +++ b/packages/runtime-core/src/componentPublicInstance.ts @@ -12,8 +12,7 @@ import { NOOP, extend, isString, - warnDeprecation, - DeprecationTypes + isFunction } from '@vue/shared' import { ReactiveEffect, @@ -42,7 +41,7 @@ import { markAttrsAccessed } from './componentRenderUtils' import { currentRenderingInstance } from './componentRenderContext' import { warn } from './warning' import { UnionToIntersection } from './helpers/typeUtils' - +import { warnDeprecation, DeprecationTypes } from './compat/deprecations' /** * Custom properties added to component instances in any way and can be accessed through `this` * @@ -356,7 +355,12 @@ export const PublicInstanceProxyHandlers: ProxyHandler = { ((globalProperties = appContext.config.globalProperties), hasOwn(globalProperties, key)) ) { - return globalProperties[key] + if (__COMPAT__) { + const val = globalProperties[key] + return isFunction(val) ? val.bind(instance.proxy) : val + } else { + return globalProperties[key] + } } else if ( __DEV__ && currentRenderingInstance && @@ -510,7 +514,10 @@ export function createRenderContext(instance: ComponentInternalInstance) { Object.defineProperty(target, key, { configurable: true, enumerable: false, - get: () => globalProperties[key], + get: () => { + const val = globalProperties[key] + return __COMPAT__ && isFunction(val) ? val.bind(instance.proxy) : val + }, set: NOOP }) }) diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 6026d4c2..b0bc0645 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -279,3 +279,10 @@ const _ssrUtils = { * @internal */ export const ssrUtils = (__NODE_JS__ ? _ssrUtils : null) as typeof _ssrUtils + +// 2.x COMPAT ------------------------------------------------------------------ + +// Important: every function exported here must have `if (!__COMPAT__) return` +// checks +export { warnDeprecation, DeprecationTypes } from './compat/deprecations' +export { createCompatVue, CompatVue } from './compat/global' diff --git a/packages/runtime-dom/src/index.ts b/packages/runtime-dom/src/index.ts index 1a4265aa..fe019a44 100644 --- a/packages/runtime-dom/src/index.ts +++ b/packages/runtime-dom/src/index.ts @@ -8,20 +8,14 @@ import { HydrationRenderer, App, RootHydrateFunction, - isRuntimeOnly + isRuntimeOnly, + warnDeprecation, + DeprecationTypes } from '@vue/runtime-core' import { nodeOps } from './nodeOps' import { patchProp, forcePatchProp } from './patchProp' // Importing from the compiler, will be tree-shaken in prod -import { - isFunction, - isString, - isHTMLTag, - isSVGTag, - extend, - warnDeprecation, - DeprecationTypes -} from '@vue/shared' +import { isFunction, isString, isHTMLTag, isSVGTag, extend } from '@vue/shared' declare module '@vue/reactivity' { export interface RefUnwrapBailTypes { diff --git a/packages/shared/src/deprecations.ts b/packages/shared/src/deprecations.ts deleted file mode 100644 index ff097f3d..00000000 --- a/packages/shared/src/deprecations.ts +++ /dev/null @@ -1,39 +0,0 @@ -export const enum DeprecationTypes { - DOM_TEMPLATE_MOUNT, - $MOUNT, - $DESTROY -} - -type DeprecationData = { - message: string - link?: string -} - -const deprecations: Record = { - [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 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` - } -} - -export function warnDeprecation(key: DeprecationTypes) { - const { message, link } = deprecations[key] - console.warn( - `[Deprecation]: ${message}${link ? `\nFor more details, see ${link}` : ``}` - ) -} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 8a16c7c4..84b324be 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -12,7 +12,6 @@ export * from './domAttrConfig' export * from './escapeHtml' export * from './looseEqual' export * from './toDisplayString' -export * from './deprecations' /** * List of @babel/parser plugins that are used for template expression diff --git a/packages/vue-compat/src/apiGlobal.ts b/packages/vue-compat/src/apiGlobal.ts deleted file mode 100644 index 1a9f95e2..00000000 --- a/packages/vue-compat/src/apiGlobal.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { reactive } from '@vue/reactivity' -import { - createApp, - defineComponent, - nextTick, - App, - AppConfig, - Plugin, - Component, - ComponentOptions, - ComponentPublicInstance, - Directive, - RenderFunction, - isRuntimeOnly -} from '@vue/runtime-dom' -import { extend } from '@vue/shared' - -// TODO make these getter/setters and trigger deprecation warnings -export type LegacyConfig = AppConfig & { - /** - * @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 - /** - * @deprecated - * https://v3.vuejs.org/guide/migration/global-api.html#config-productiontip-removed - */ - productionTip?: boolean -} - -/** - * @deprecated the default `Vue` export has been removed in Vue 3. The type for - * the default export is provided only for migration purposes. Please use - * named imports instead - e.g. `import { createApp } from 'vue'`. - */ -export type GlobalVue = Pick & { - // no inference here since these types are not meant for actual use - they - // are merely here to provide type checks for internal implementation and - // information for migration. - new (options?: ComponentOptions): ComponentPublicInstance - - version: string - config: LegacyConfig - - extend: typeof defineComponent - nextTick: typeof nextTick - - use(plugin: Plugin, ...options: any[]): GlobalVue - mixin(mixin: ComponentOptions): GlobalVue - - component(name: string): Component | undefined - component(name: string, component: Component): GlobalVue - directive(name: string): Directive | undefined - directive(name: string, directive: Directive): GlobalVue - - compile(template: string): RenderFunction - - /** - * @deprecated Vue 3 no longer needs set() for adding new properties. - */ - set(target: any, key: string | number | symbol, value: any): void - /** - * @deprecated Vue 3 no longer needs delete() for property deletions. - */ - delete(target: any, key: string | number | symbol): void - /** - * @deprecated use `reactive` instead. - */ - observable: typeof reactive - /** - * @deprecated filters have been removed from Vue 3. - */ - filter(name: string, arg: any): null -} - -export const Vue: GlobalVue = function Vue(options: ComponentOptions = {}) { - const app = createApp(options) - - // copy over global config mutations - for (const key in singletonApp.config) { - if ( - key !== 'isNativeTag' && - !(key === 'isCustomElement' && isRuntimeOnly()) - ) { - // @ts-ignore - 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 - -const singletonApp = createApp({}) - -Vue.version = __VERSION__ -Vue.config = singletonApp.config - -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) => { - // TODO deprecation warnings - target[key] = value -} -Vue.delete = (target, key) => { - // TODO deprecation warnings - delete target[key] -} -// TODO wrap with deprecation warning -Vue.observable = reactive - -Vue.use = (p, ...options) => { - singletonApp.use(p, ...options) - return Vue -} - -Vue.mixin = m => { - singletonApp.mixin(m) - return Vue -} - -Vue.component = ((name: string, comp: any) => { - if (comp) { - singletonApp.component(name, comp) - return Vue - } else { - return singletonApp.component(name) - } -}) as any - -Vue.directive = ((name: string, dir: any) => { - if (dir) { - singletonApp.directive(name, dir) - return Vue - } else { - return singletonApp.directive(name) - } -}) as any - -Vue.filter = ((name: string, filter: any) => { - // TODO deprecation warning - // TODO compiler warning for filters (maybe behavior compat?) -}) as any diff --git a/packages/vue-compat/src/index.ts b/packages/vue-compat/src/index.ts index 481f3b5f..a1aa615f 100644 --- a/packages/vue-compat/src/index.ts +++ b/packages/vue-compat/src/index.ts @@ -2,11 +2,15 @@ // and the compiler, and supports on-the-fly compilation of the template option. import { initDev } from './dev' import { compile, CompilerOptions, CompilerError } from '@vue/compiler-dom' -import { registerRuntimeCompiler, RenderFunction, warn } from '@vue/runtime-dom' +import { + registerRuntimeCompiler, + RenderFunction, + warn, + createCompatVue +} from '@vue/runtime-dom' import { isString, NOOP, generateCodeFrame, extend } from '@vue/shared' import { InternalRenderFunction } from 'packages/runtime-core/src/component' import * as runtimeDom from '@vue/runtime-dom' -import { Vue } from './apiGlobal' if (__DEV__) { initDev() @@ -87,6 +91,8 @@ function compileToFunction( registerRuntimeCompiler(compileToFunction) +const Vue = createCompatVue() + Vue.compile = compileToFunction extend(Vue, runtimeDom)