| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- import {
- ConcreteComponent,
- Data,
- validateComponentName,
- Component
- } from './component'
- import { ComponentOptions } from './componentOptions'
- import { ComponentPublicInstance } from './componentPublicInstance'
- import { Directive, validateDirectiveName } from './directives'
- import { RootRenderFunction } from './renderer'
- import { InjectionKey } from './apiInject'
- import { isFunction, NO, isObject } from '@vue/shared'
- import { warn } from './warning'
- import { createVNode, cloneVNode, VNode } from './vnode'
- import { RootHydrateFunction } from './hydration'
- import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
- import { version } from '.'
- export interface App<HostElement = any> {
- version: string
- config: AppConfig
- use(plugin: Plugin, ...options: any[]): 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
- ): ComponentPublicInstance
- unmount(rootContainer: HostElement | string): void
- provide<T>(key: InjectionKey<T> | string, value: T): this
- // internal, but we need to expose these for the server-renderer and devtools
- _component: ConcreteComponent
- _props: Data | null
- _container: HostElement | null
- _context: AppContext
- }
- export type OptionMergeFunction = (
- to: unknown,
- from: unknown,
- instance: any,
- key: string
- ) => any
- export interface AppConfig {
- // @private
- readonly isNativeTag?: (tag: string) => boolean
- performance: boolean
- optionMergeStrategies: Record<string, OptionMergeFunction>
- globalProperties: Record<string, any>
- isCustomElement: (tag: string) => boolean
- errorHandler?: (
- err: unknown,
- instance: ComponentPublicInstance | null,
- info: string
- ) => void
- warnHandler?: (
- msg: string,
- instance: ComponentPublicInstance | null,
- trace: string
- ) => void
- }
- export interface AppContext {
- app: App // for devtools
- config: AppConfig
- mixins: ComponentOptions[]
- components: Record<string, Component>
- directives: Record<string, Directive>
- provides: Record<string | symbol, any>
- reload?: () => void // HMR only
- }
- type PluginInstallFunction = (app: App, ...options: any[]) => any
- export type Plugin =
- | PluginInstallFunction & { install?: PluginInstallFunction }
- | {
- install: PluginInstallFunction
- }
- export function createAppContext(): AppContext {
- return {
- app: null as any,
- config: {
- isNativeTag: NO,
- performance: false,
- globalProperties: {},
- optionMergeStrategies: {},
- isCustomElement: NO,
- errorHandler: undefined,
- warnHandler: undefined
- },
- mixins: [],
- components: {},
- directives: {},
- provides: Object.create(null)
- }
- }
- export type CreateAppFunction<HostElement> = (
- rootComponent: Component,
- rootProps?: Data | null
- ) => App<HostElement>
- export function createAppAPI<HostElement>(
- render: RootRenderFunction,
- hydrate?: RootHydrateFunction
- ): CreateAppFunction<HostElement> {
- return function createApp(rootComponent, rootProps = null) {
- 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 = {
- _component: rootComponent as ConcreteComponent,
- _props: rootProps,
- _container: null,
- _context: context,
- 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): any {
- if (!isMounted) {
- 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)
- }
- }
- if (isHydrate && hydrate) {
- hydrate(vnode as VNode<Node, Element>, rootContainer as any)
- } else {
- render(vnode, rootContainer)
- }
- isMounted = true
- app._container = rootContainer
- // for devtools and telemetry
- ;(rootContainer as any).__vue_app__ = app
- if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
- devtoolsInitApp(app, version)
- }
- return 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)
- devtoolsUnmountApp(app)
- } else if (__DEV__) {
- warn(`Cannot unmount an app that is not mounted.`)
- }
- },
- provide(key, value) {
- if (__DEV__ && key in context.provides) {
- warn(
- `App already provides property with key "${String(key)}". ` +
- `It will be overwritten with the new value.`
- )
- }
- // TypeScript doesn't allow symbols as index type
- // https://github.com/Microsoft/TypeScript/issues/24587
- context.provides[key as string] = value
- return app
- }
- })
- return app
- }
- }
|