import { type Component, type ComponentInternalInstance, type ConcreteComponent, type Data, type GenericComponent, type GenericComponentInstance, validateComponentName, } from './component' import type { ComponentOptions, MergedComponentOptions, RuntimeCompilerOptions, } from './componentOptions' import type { ComponentCustomProperties, ComponentPublicInstance, } from './componentPublicInstance' import { type Directive, validateDirectiveName } from './directives' import type { ElementNamespace, RootRenderFunction, UnmountComponentFn, } from './renderer' import type { InjectionKey } from './apiInject' import { warn } from './warning' import type { VNode } from './vnode' import { devtoolsInitApp, devtoolsUnmountApp } from './devtools' import { NO, extend, hasOwn, isFunction, isObject } from '@vue/shared' import { type SuspenseBoundary, type TransitionHooks, version } from '.' import { installAppCompatProperties } from './compat/global' import type { NormalizedPropsOptions } from './componentProps' import type { ObjectEmitsOptions } from './componentEmits' import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling' import type { DefineComponent } from './apiDefineComponent' export interface App { vapor?: boolean version: string config: AppConfig use( plugin: Plugin, ...options: NoInfer ): this use(plugin: Plugin, options: NoInfer): this mixin(mixin: ComponentOptions): this component(name: string): Component | undefined component( name: string, component: T, ): this directive< HostElement = any, Value = any, Modifiers extends string = string, Arg = any, >( name: string, ): Directive | undefined directive< HostElement = any, Value = any, Modifiers extends string = string, Arg = any, >( name: string, directive: Directive, ): this mount( rootContainer: HostElement | string, /** * @internal */ isHydrate?: boolean, /** * @internal */ namespace?: boolean | ElementNamespace, /** * @internal */ vnode?: VNode, ): ComponentPublicInstance unmount(): void onUnmount(cb: () => void): void provide | string | number>( key: K, value: K extends InjectionKey ? V : T, ): this /** * Runs a function with the app as active instance. This allows using of `inject()` within the function to get access * to variables provided via `app.provide()`. * * @param fn - function to run with the app as active instance */ runWithContext(fn: () => T): T // internal, but we need to expose these for the server-renderer and devtools _uid: number _component: GenericComponent _props: Data | null _container: HostElement | null _context: AppContext _instance: GenericComponentInstance | null /** * @internal custom element vnode */ _ceVNode?: VNode /** * @internal vapor custom element instance */ _ceComponent?: GenericComponentInstance | null /** * v2 compat only */ filter?(name: string): Function | undefined filter?(name: string, filter: Function): this /** * @internal v3 compat only */ _createRoot?(options: ComponentOptions): ComponentPublicInstance } export type OptionMergeFunction = (to: unknown, from: unknown) => any /** * Shared app config between vdom and vapor */ export interface GenericAppConfig { performance?: boolean errorHandler?: ( err: unknown, instance: ComponentPublicInstance | null, info: string, ) => void warnHandler?: ( msg: string, instance: ComponentPublicInstance | null, trace: string, ) => void /** * Whether to throw unhandled errors in production. * Default is `false` to avoid crashing on any error (and only logs it) * But in some cases, e.g. SSR, throwing might be more desirable. */ throwUnhandledErrorInProduction?: boolean /** * Prefix for all useId() calls within this app */ idPrefix?: string } export interface AppConfig extends GenericAppConfig { // @private readonly isNativeTag: (tag: string) => boolean optionMergeStrategies: Record globalProperties: ComponentCustomProperties & Record /** * Options to pass to `@vue/compiler-dom`. * Only supported in runtime compiler build. */ compilerOptions: RuntimeCompilerOptions /** * @deprecated use config.compilerOptions.isCustomElement */ isCustomElement?: (tag: string) => boolean } /** * The vapor in vdom implementation is in runtime-vapor/src/vdomInterop.ts */ export interface VaporInteropInterface { mount( vnode: VNode, container: any, anchor: any, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, ): GenericComponentInstance // VaporComponentInstance update(n1: VNode, n2: VNode, shouldUpdate: boolean): void unmount(vnode: VNode, doRemove?: boolean): void move(vnode: VNode, container: any, anchor: any): void slot( n1: VNode | null, n2: VNode, container: any, anchor: any, parentComponent: ComponentInternalInstance | null, ): void hydrate( vnode: VNode, node: any, container: any, anchor: any, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, ): Node hydrateSlot(vnode: VNode, node: any): Node activate( vnode: VNode, container: any, anchor: any, parentComponent: ComponentInternalInstance, ): void deactivate(vnode: VNode, container: any): void setTransitionHooks( component: ComponentInternalInstance, transition: TransitionHooks, ): void vdomMount: ( component: ConcreteComponent, parentComponent: any, props?: any, slots?: any, ) => any vdomUnmount: UnmountComponentFn vdomSlot: ( slots: any, name: string | (() => string), props: Record, parentComponent: any, // VaporComponentInstance fallback?: any, // VaporSlot ) => any } /** * Minimal app context shared between vdom and vapor */ export interface GenericAppContext { app: App // for devtools config: GenericAppConfig provides: Record components?: Record directives?: Record /** * HMR only * @internal */ reload?: () => void /** * @internal vapor interop only */ vapor?: VaporInteropInterface } export interface AppContext extends GenericAppContext { config: AppConfig components: Record directives: Record mixins: ComponentOptions[] /** * Cache for merged/normalized component options * Each app instance has its own cache because app-level global mixins and * optionMergeStrategies can affect merge behavior. * @internal */ optionsCache: WeakMap /** * Cache for normalized props options * @internal */ propsCache: WeakMap /** * Cache for normalized emits options * @internal */ emitsCache: WeakMap /** * v2 compat only * @internal */ filters?: Record } type PluginInstallFunction = Options extends unknown[] ? (app: App, ...options: Options) => any : (app: App, options: Options) => any export type ObjectPlugin = { install: PluginInstallFunction } export type FunctionPlugin = PluginInstallFunction & Partial> export type Plugin< Options = any[], // TODO: in next major Options extends unknown[] and remove P P extends unknown[] = Options extends unknown[] ? Options : [Options], > = FunctionPlugin

| ObjectPlugin

export function createAppContext(): AppContext { return { app: null as any, config: { isNativeTag: NO, performance: false, globalProperties: {}, optionMergeStrategies: {}, errorHandler: undefined, warnHandler: undefined, compilerOptions: {}, }, mixins: [], components: {}, directives: {}, provides: Object.create(null), optionsCache: new WeakMap(), propsCache: new WeakMap(), emitsCache: new WeakMap(), } } export type CreateAppFunction = ( rootComponent: Comp, rootProps?: Data | null, ) => App let uid = 0 export type AppMountFn = ( app: App, rootContainer: HostElement, isHydrate?: boolean, namespace?: boolean | ElementNamespace, ) => GenericComponentInstance export type AppUnmountFn = (app: App) => void /** * @internal */ export function createAppAPI( // render: RootRenderFunction, // hydrate?: RootHydrateFunction, mount: AppMountFn, unmount: AppUnmountFn, getPublicInstance: (instance: GenericComponentInstance) => any, render?: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent, rootProps = null) { if (!isFunction(rootComponent)) { rootComponent = extend({}, rootComponent) } if (rootProps != null && !isObject(rootProps)) { __DEV__ && warn(`root props passed to app.mount() must be an object.`) rootProps = null } const context = createAppContext() const installedPlugins = new WeakSet() const pluginCleanupFns: Array<() => any> = [] let isMounted = false const app: App = (context.app = { _uid: uid++, _component: rootComponent as ConcreteComponent, _props: rootProps, _container: null, _context: context, _instance: null, version, get config() { return context.config }, set config(v) { if (__DEV__) { warn( `app.config cannot be replaced. Modify individual options instead.`, ) } }, use(plugin: Plugin, ...options: any[]) { if (installedPlugins.has(plugin)) { __DEV__ && warn(`Plugin has already been applied to target app.`) } else if (plugin && isFunction(plugin.install)) { installedPlugins.add(plugin) plugin.install(app, ...options) } else if (isFunction(plugin)) { installedPlugins.add(plugin) plugin(app, ...options) } else if (__DEV__) { warn( `A plugin must either be a function or an object with an "install" ` + `function.`, ) } return app }, mixin(mixin: ComponentOptions) { if (__FEATURE_OPTIONS_API__) { if (!context.mixins.includes(mixin)) { context.mixins.push(mixin) } else if (__DEV__) { warn( 'Mixin has already been applied to target app' + (mixin.name ? `: ${mixin.name}` : ''), ) } } else if (__DEV__) { warn('Mixins are only available in builds supporting Options API') } return app }, component(name: string, component?: Component): any { if (__DEV__) { validateComponentName(name, context.config) } if (!component) { return context.components[name] } if (__DEV__ && context.components[name]) { warn(`Component "${name}" has already been registered in target app.`) } context.components[name] = component return app }, directive(name: string, directive?: Directive) { if (__DEV__) { validateDirectiveName(name) } if (!directive) { return context.directives[name] as any } if (__DEV__ && context.directives[name]) { warn(`Directive "${name}" has already been registered in target app.`) } context.directives[name] = directive return app }, mount( rootContainer: HostElement & { __vue_app__?: App }, isHydrate?: boolean, namespace?: boolean | ElementNamespace, ): any { if (!isMounted) { // #5571 if (__DEV__ && rootContainer.__vue_app__) { warn( `There is already an app instance mounted on the host container.\n` + ` If you want to mount another app on the same host container,` + ` you need to unmount the previous app by calling \`app.unmount()\` first.`, ) } const instance = mount(app, rootContainer, isHydrate, namespace) if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { app._instance = instance devtoolsInitApp(app, version) } isMounted = true app._container = rootContainer // for devtools and telemetry rootContainer.__vue_app__ = app return getPublicInstance(instance) } else if (__DEV__) { warn( `App has already been mounted.\n` + `If you want to remount the same app, move your app creation logic ` + `into a factory function and create fresh app instances for each ` + `mount - e.g. \`const createMyApp = () => createApp(App)\``, ) } }, onUnmount(cleanupFn: () => void) { if (__DEV__ && typeof cleanupFn !== 'function') { warn( `Expected function as first argument to app.onUnmount(), ` + `but got ${typeof cleanupFn}`, ) } pluginCleanupFns.push(cleanupFn) }, unmount() { if (isMounted) { callWithAsyncErrorHandling( pluginCleanupFns, app._instance, ErrorCodes.APP_UNMOUNT_CLEANUP, ) unmount(app) if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { app._instance = null devtoolsUnmountApp(app) } delete app._container.__vue_app__ } else if (__DEV__) { warn(`Cannot unmount an app that is not mounted.`) } }, provide(key, value) { if (__DEV__ && (key as string | symbol) in context.provides) { if (hasOwn(context.provides, key as string | symbol)) { warn( `App already provides property with key "${String(key)}". ` + `It will be overwritten with the new value.`, ) } else { // #13212, context.provides can inherit the provides object from parent on custom elements warn( `App already provides property with key "${String(key)}" inherited from its parent element. ` + `It will be overwritten with the new value.`, ) } } context.provides[key as string | symbol] = value return app }, runWithContext(fn) { const lastApp = currentApp currentApp = app try { return fn() } finally { currentApp = lastApp } }, }) if (__COMPAT__) { installAppCompatProperties( app, context, // vapor doesn't have compat mode so this is always passed render!, ) } return app } } /** * @internal Used to identify the current app when using `inject()` within * `app.runWithContext()`. */ export let currentApp: App | null = null