| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493 |
- import {
- type Component,
- type ComponentInternalInstance,
- type ConcreteComponent,
- type Data,
- getComponentPublicInstance,
- 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 } from './renderer'
- import type { InjectionKey } from './apiInject'
- import { warn } from './warning'
- import { type VNode, cloneVNode, createVNode } from './vnode'
- import type { RootHydrateFunction } from './hydration'
- import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
- import { NO, extend, hasOwn, isFunction, isObject } from '@vue/shared'
- import { 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<HostElement = any> {
- version: string
- config: AppConfig
- use<Options extends unknown[]>(
- plugin: Plugin<Options>,
- ...options: NoInfer<Options>
- ): this
- use<Options>(plugin: Plugin<Options>, options: NoInfer<Options>): this
- mixin(mixin: ComponentOptions): this
- component(name: string): Component | undefined
- component<T extends Component | DefineComponent>(
- name: string,
- component: T,
- ): this
- directive<
- HostElement = any,
- Value = any,
- Modifiers extends string = string,
- Arg = any,
- >(
- name: string,
- ): Directive<HostElement, Value, Modifiers, Arg> | undefined
- directive<
- HostElement = any,
- Value = any,
- Modifiers extends string = string,
- Arg = any,
- >(
- name: string,
- directive: Directive<HostElement, Value, Modifiers, Arg>,
- ): this
- mount(
- rootContainer: HostElement | string,
- /**
- * @internal
- */
- isHydrate?: boolean,
- /**
- * @internal
- */
- namespace?: boolean | ElementNamespace,
- /**
- * @internal
- */
- vnode?: VNode,
- ): ComponentPublicInstance
- unmount(): void
- onUnmount(cb: () => void): void
- provide<T, K = InjectionKey<T> | string | number>(
- key: K,
- value: K extends InjectionKey<infer V> ? 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<T>(fn: () => T): T
- // internal, but we need to expose these for the server-renderer and devtools
- _uid: number
- _component: ConcreteComponent
- _props: Data | null
- _container: HostElement | null
- _context: AppContext
- _instance: ComponentInternalInstance | null
- /**
- * @internal custom element vnode
- */
- _ceVNode?: VNode
- /**
- * 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
- export interface AppConfig {
- // @private
- readonly isNativeTag: (tag: string) => boolean
- performance: boolean
- optionMergeStrategies: Record<string, OptionMergeFunction>
- globalProperties: ComponentCustomProperties & Record<string, any>
- errorHandler?: (
- err: unknown,
- instance: ComponentPublicInstance | null,
- info: string,
- ) => void
- warnHandler?: (
- msg: string,
- instance: ComponentPublicInstance | null,
- trace: string,
- ) => void
- /**
- * Options to pass to `@vue/compiler-dom`.
- * Only supported in runtime compiler build.
- */
- compilerOptions: RuntimeCompilerOptions
- /**
- * @deprecated use config.compilerOptions.isCustomElement
- */
- isCustomElement?: (tag: string) => boolean
- /**
- * TODO document for 3.5
- * Enable warnings for computed getters that recursively trigger itself.
- */
- warnRecursiveComputed?: boolean
- /**
- * 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 AppContext {
- app: App // for devtools
- config: AppConfig
- mixins: ComponentOptions[]
- components: Record<string, Component>
- directives: Record<string, Directive>
- provides: Record<string | symbol, any>
- /**
- * 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<ComponentOptions, MergedComponentOptions>
- /**
- * Cache for normalized props options
- * @internal
- */
- propsCache: WeakMap<ConcreteComponent, NormalizedPropsOptions>
- /**
- * Cache for normalized emits options
- * @internal
- */
- emitsCache: WeakMap<ConcreteComponent, ObjectEmitsOptions | null>
- /**
- * HMR only
- * @internal
- */
- reload?: () => void
- /**
- * v2 compat only
- * @internal
- */
- filters?: Record<string, Function>
- }
- type PluginInstallFunction<Options = any[]> = Options extends unknown[]
- ? (app: App, ...options: Options) => any
- : (app: App, options: Options) => any
- export type ObjectPlugin<Options = any[]> = {
- install: PluginInstallFunction<Options>
- }
- export type FunctionPlugin<Options = any[]> = PluginInstallFunction<Options> &
- Partial<ObjectPlugin<Options>>
- export type Plugin<
- Options = any[],
- // TODO: in next major Options extends unknown[] and remove P
- P extends unknown[] = Options extends unknown[] ? Options : [Options],
- > = FunctionPlugin<P> | ObjectPlugin<P>
- 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<HostElement> = (
- rootComponent: Component,
- rootProps?: Data | null,
- ) => App<HostElement>
- let uid = 0
- export function createAppAPI<HostElement>(
- render: RootRenderFunction<HostElement>,
- hydrate?: RootHydrateFunction,
- ): CreateAppFunction<HostElement> {
- 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,
- isHydrate?: boolean,
- namespace?: boolean | ElementNamespace,
- ): any {
- if (!isMounted) {
- // #5571
- if (__DEV__ && (rootContainer as any).__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 vnode = app._ceVNode || createVNode(rootComponent, rootProps)
- // store app context on the root VNode.
- // this will be set on the root instance on initial mount.
- vnode.appContext = context
- if (namespace === true) {
- namespace = 'svg'
- } else if (namespace === false) {
- namespace = undefined
- }
- // HMR root reload
- if (__DEV__) {
- context.reload = () => {
- const cloned = cloneVNode(vnode)
- // avoid hydration for hmr updating
- cloned.el = null
- // casting to ElementNamespace because TS doesn't guarantee type narrowing
- // over function boundaries
- render(cloned, rootContainer, namespace as ElementNamespace)
- }
- }
- if (isHydrate && hydrate) {
- hydrate(vnode as VNode<Node, Element>, rootContainer as any)
- } else {
- render(vnode, rootContainer, namespace)
- }
- isMounted = true
- app._container = rootContainer
- // for devtools and telemetry
- ;(rootContainer as any).__vue_app__ = app
- if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
- app._instance = vnode.component
- devtoolsInitApp(app, version)
- }
- return getComponentPublicInstance(vnode.component!)
- } 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,
- )
- render(null, app._container)
- 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, render)
- }
- return app
- }
- }
- /**
- * @internal Used to identify the current app when using `inject()` within
- * `app.runWithContext()`.
- */
- export let currentApp: App<unknown> | null = null
|