| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- import {
- ConcreteComponent,
- Data,
- validateComponentName,
- Component,
- ComponentInternalInstance,
- getExposeProxy
- } from './component'
- import {
- ComponentOptions,
- MergedComponentOptions,
- RuntimeCompilerOptions
- } from './componentOptions'
- import {
- ComponentCustomProperties,
- ComponentPublicInstance
- } from './componentPublicInstance'
- import { Directive, validateDirectiveName } from './directives'
- import { RootRenderFunction } from './renderer'
- import { InjectionKey } from './apiInject'
- 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 { installAppCompatProperties } from './compat/global'
- import { NormalizedPropsOptions } from './componentProps'
- import { ObjectEmitsOptions } from './componentEmits'
- export interface App<HostElement = any> {
- version: string
- config: AppConfig
- use<Options extends unknown[]>(
- plugin: Plugin<Options>,
- ...options: Options
- ): this
- use<Options>(plugin: Plugin<Options>, options: Options): this
- mixin(mixin: ComponentOptions): this
- component(name: string): Component | undefined
- component(name: string, component: Component): this
- directive(name: string): Directive | undefined
- directive(name: string, directive: Directive): this
- mount(
- rootContainer: HostElement | string,
- isHydrate?: boolean,
- isSVG?: boolean
- ): ComponentPublicInstance
- unmount(): void
- provide<T>(key: InjectionKey<T> | string, value: T): this
- // 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
- /**
- * 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
- /**
- * Temporary config for opt-in to unwrap injected refs.
- * TODO deprecate in 3.3
- */
- unwrapInjectedRef?: boolean
- }
- 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> = Options extends unknown[]
- ? (app: App, ...options: Options) => any
- : (app: App, options: Options) => any
- export type Plugin<Options = any[]> =
- | (PluginInstallFunction<Options> & {
- install?: PluginInstallFunction<Options>
- })
- | {
- install: PluginInstallFunction<Options>
- }
- 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 = { ...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 Set()
- 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,
- isSVG?: boolean
- ): 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 = createVNode(
- rootComponent as ConcreteComponent,
- rootProps
- )
- // store app context on the root VNode.
- // this will be set on the root instance on initial mount.
- vnode.appContext = context
- // HMR root reload
- if (__DEV__) {
- context.reload = () => {
- render(cloneVNode(vnode), rootContainer, isSVG)
- }
- }
- if (isHydrate && hydrate) {
- hydrate(vnode as VNode<Node, Element>, rootContainer as any)
- } else {
- render(vnode, rootContainer, isSVG)
- }
- 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 getExposeProxy(vnode.component!) || vnode.component!.proxy
- } 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)\``
- )
- }
- },
- unmount() {
- if (isMounted) {
- 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) {
- warn(
- `App already provides property with key "${String(key)}". ` +
- `It will be overwritten with the new value.`
- )
- }
- context.provides[key as string | symbol] = value
- return app
- }
- })
- if (__COMPAT__) {
- installAppCompatProperties(app, context, render)
- }
- return app
- }
- }
|