2020-02-16 02:04:29 +00:00
|
|
|
import {
|
|
|
|
Component,
|
|
|
|
Data,
|
|
|
|
validateComponentName,
|
|
|
|
PublicAPIComponent
|
|
|
|
} from './component'
|
2019-10-02 14:03:43 +00:00
|
|
|
import { ComponentOptions } from './apiOptions'
|
|
|
|
import { ComponentPublicInstance } from './componentProxy'
|
2019-10-18 16:34:45 +00:00
|
|
|
import { Directive, validateDirectiveName } from './directives'
|
2019-11-02 16:18:35 +00:00
|
|
|
import { RootRenderFunction } from './renderer'
|
2019-09-02 20:09:34 +00:00
|
|
|
import { InjectionKey } from './apiInject'
|
2019-12-09 19:06:21 +00:00
|
|
|
import { isFunction, NO, isObject } from '@vue/shared'
|
2019-09-02 20:09:34 +00:00
|
|
|
import { warn } from './warning'
|
2020-02-14 04:31:03 +00:00
|
|
|
import { createVNode, cloneVNode, VNode } from './vnode'
|
2019-09-02 20:09:34 +00:00
|
|
|
|
2019-09-06 20:58:32 +00:00
|
|
|
export interface App<HostElement = any> {
|
2019-09-02 20:09:34 +00:00
|
|
|
config: AppConfig
|
2019-12-24 15:33:47 +00:00
|
|
|
use(plugin: Plugin, ...options: any[]): this
|
2019-09-02 20:09:34 +00:00
|
|
|
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
|
2020-02-14 04:31:03 +00:00
|
|
|
mount(
|
|
|
|
rootContainer: HostElement | string,
|
|
|
|
isHydrate?: boolean
|
|
|
|
): ComponentPublicInstance
|
2020-01-16 17:23:47 +00:00
|
|
|
unmount(rootContainer: HostElement | string): void
|
2019-10-28 00:54:33 +00:00
|
|
|
provide<T>(key: InjectionKey<T> | string, value: T): this
|
2020-01-24 02:01:56 +00:00
|
|
|
|
|
|
|
// internal. We need to expose these for the server-renderer
|
|
|
|
_component: Component
|
|
|
|
_props: Data | null
|
|
|
|
_container: HostElement | null
|
2020-01-30 17:20:23 +00:00
|
|
|
_context: AppContext
|
2019-09-02 20:09:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface AppConfig {
|
|
|
|
devtools: boolean
|
|
|
|
performance: boolean
|
2019-10-14 19:36:30 +00:00
|
|
|
readonly isNativeTag?: (tag: string) => boolean
|
2019-10-15 21:30:47 +00:00
|
|
|
isCustomElement?: (tag: string) => boolean
|
2019-09-02 20:09:34 +00:00
|
|
|
errorHandler?: (
|
|
|
|
err: Error,
|
2019-09-06 16:58:31 +00:00
|
|
|
instance: ComponentPublicInstance | null,
|
2019-09-02 20:09:34 +00:00
|
|
|
info: string
|
|
|
|
) => void
|
|
|
|
warnHandler?: (
|
|
|
|
msg: string,
|
2019-09-06 16:58:31 +00:00
|
|
|
instance: ComponentPublicInstance | null,
|
2019-09-02 20:09:34 +00:00
|
|
|
trace: string
|
|
|
|
) => void
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface AppContext {
|
|
|
|
config: AppConfig
|
|
|
|
mixins: ComponentOptions[]
|
|
|
|
components: Record<string, Component>
|
|
|
|
directives: Record<string, Directive>
|
|
|
|
provides: Record<string | symbol, any>
|
2019-12-22 17:25:04 +00:00
|
|
|
reload?: () => void // HMR only
|
2019-09-02 20:09:34 +00:00
|
|
|
}
|
|
|
|
|
2019-12-24 15:33:47 +00:00
|
|
|
type PluginInstallFunction = (app: App, ...options: any[]) => any
|
2019-09-02 20:09:34 +00:00
|
|
|
|
2019-09-03 22:11:04 +00:00
|
|
|
export type Plugin =
|
2020-01-08 17:40:24 +00:00
|
|
|
| PluginInstallFunction & { install?: PluginInstallFunction }
|
2019-09-02 20:09:34 +00:00
|
|
|
| {
|
|
|
|
install: PluginInstallFunction
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createAppContext(): AppContext {
|
|
|
|
return {
|
|
|
|
config: {
|
|
|
|
devtools: true,
|
|
|
|
performance: false,
|
2019-10-14 19:36:30 +00:00
|
|
|
isNativeTag: NO,
|
2019-10-15 21:30:47 +00:00
|
|
|
isCustomElement: NO,
|
2019-09-02 20:09:34 +00:00
|
|
|
errorHandler: undefined,
|
2019-09-02 20:43:26 +00:00
|
|
|
warnHandler: undefined
|
2019-09-02 20:09:34 +00:00
|
|
|
},
|
|
|
|
mixins: [],
|
|
|
|
components: {},
|
|
|
|
directives: {},
|
|
|
|
provides: {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-23 20:05:38 +00:00
|
|
|
export type CreateAppFunction<HostElement> = (
|
2020-02-16 02:04:29 +00:00
|
|
|
rootComponent: PublicAPIComponent,
|
2020-01-23 20:05:38 +00:00
|
|
|
rootProps?: Data | null
|
|
|
|
) => App<HostElement>
|
|
|
|
|
2019-09-06 20:58:32 +00:00
|
|
|
export function createAppAPI<HostNode, HostElement>(
|
2020-02-14 04:31:03 +00:00
|
|
|
render: RootRenderFunction<HostNode, HostElement>,
|
2020-02-14 06:30:08 +00:00
|
|
|
hydrate?: (vnode: VNode, container: Element) => void
|
2020-01-23 20:05:38 +00:00
|
|
|
): CreateAppFunction<HostElement> {
|
2020-01-24 02:01:56 +00:00
|
|
|
return function createApp(rootComponent: Component, rootProps = null) {
|
|
|
|
if (rootProps != null && !isObject(rootProps)) {
|
|
|
|
__DEV__ && warn(`root props passed to app.mount() must be an object.`)
|
|
|
|
rootProps = null
|
|
|
|
}
|
|
|
|
|
2019-09-02 20:09:34 +00:00
|
|
|
const context = createAppContext()
|
2019-11-05 15:49:00 +00:00
|
|
|
const installedPlugins = new Set()
|
2019-09-02 20:09:34 +00:00
|
|
|
|
2019-09-03 22:11:04 +00:00
|
|
|
let isMounted = false
|
|
|
|
|
2019-09-02 20:09:34 +00:00
|
|
|
const app: App = {
|
2020-01-24 02:01:56 +00:00
|
|
|
_component: rootComponent,
|
|
|
|
_props: rootProps,
|
|
|
|
_container: null,
|
2020-01-30 17:20:23 +00:00
|
|
|
_context: context,
|
2020-01-23 20:05:38 +00:00
|
|
|
|
2019-09-02 20:09:34 +00:00
|
|
|
get config() {
|
|
|
|
return context.config
|
|
|
|
},
|
|
|
|
|
|
|
|
set config(v) {
|
2019-09-02 20:16:08 +00:00
|
|
|
if (__DEV__) {
|
|
|
|
warn(
|
|
|
|
`app.config cannot be replaced. Modify individual options instead.`
|
|
|
|
)
|
|
|
|
}
|
2019-09-02 20:09:34 +00:00
|
|
|
},
|
|
|
|
|
2019-12-24 15:33:47 +00:00
|
|
|
use(plugin: Plugin, ...options: any[]) {
|
2019-11-05 15:49:00 +00:00
|
|
|
if (installedPlugins.has(plugin)) {
|
|
|
|
__DEV__ && warn(`Plugin has already been applied to target app.`)
|
2019-11-27 14:18:03 +00:00
|
|
|
} else if (plugin && isFunction(plugin.install)) {
|
2019-11-05 15:49:00 +00:00
|
|
|
installedPlugins.add(plugin)
|
2019-12-24 15:33:47 +00:00
|
|
|
plugin.install(app, ...options)
|
2020-01-08 17:40:24 +00:00
|
|
|
} else if (isFunction(plugin)) {
|
|
|
|
installedPlugins.add(plugin)
|
|
|
|
plugin(app, ...options)
|
2019-09-02 20:09:34 +00:00
|
|
|
} else if (__DEV__) {
|
|
|
|
warn(
|
|
|
|
`A plugin must either be a function or an object with an "install" ` +
|
|
|
|
`function.`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return app
|
|
|
|
},
|
|
|
|
|
|
|
|
mixin(mixin: ComponentOptions) {
|
2020-02-15 16:40:09 +00:00
|
|
|
if (__FEATURE_OPTIONS__) {
|
|
|
|
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}` : '')
|
|
|
|
)
|
|
|
|
}
|
2019-10-28 20:22:03 +00:00
|
|
|
} else if (__DEV__) {
|
2020-02-15 16:40:09 +00:00
|
|
|
warn('Mixins are only available in builds supporting Options API')
|
2019-10-28 20:22:03 +00:00
|
|
|
}
|
2019-09-02 20:09:34 +00:00
|
|
|
return app
|
|
|
|
},
|
|
|
|
|
2020-02-16 02:04:29 +00:00
|
|
|
component(name: string, component?: PublicAPIComponent): any {
|
2019-10-14 19:36:30 +00:00
|
|
|
if (__DEV__) {
|
|
|
|
validateComponentName(name, context.config)
|
|
|
|
}
|
2019-09-02 20:09:34 +00:00
|
|
|
if (!component) {
|
2019-10-10 18:54:06 +00:00
|
|
|
return context.components[name]
|
2019-09-02 20:09:34 +00:00
|
|
|
}
|
2019-11-27 14:18:03 +00:00
|
|
|
if (__DEV__ && context.components[name]) {
|
|
|
|
warn(`Component "${name}" has already been registered in target app.`)
|
|
|
|
}
|
2020-02-16 02:04:29 +00:00
|
|
|
context.components[name] = component as Component
|
2019-11-27 14:18:03 +00:00
|
|
|
return app
|
2019-09-02 20:09:34 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
directive(name: string, directive?: Directive) {
|
2019-10-18 16:34:45 +00:00
|
|
|
if (__DEV__) {
|
|
|
|
validateDirectiveName(name)
|
|
|
|
}
|
|
|
|
|
2019-09-02 20:09:34 +00:00
|
|
|
if (!directive) {
|
|
|
|
return context.directives[name] as any
|
|
|
|
}
|
2019-11-27 14:18:03 +00:00
|
|
|
if (__DEV__ && context.directives[name]) {
|
|
|
|
warn(`Directive "${name}" has already been registered in target app.`)
|
|
|
|
}
|
|
|
|
context.directives[name] = directive
|
|
|
|
return app
|
2019-09-02 20:09:34 +00:00
|
|
|
},
|
|
|
|
|
2020-02-14 04:31:03 +00:00
|
|
|
mount(rootContainer: HostElement, isHydrate?: boolean): any {
|
2019-09-03 22:11:04 +00:00
|
|
|
if (!isMounted) {
|
|
|
|
const vnode = createVNode(rootComponent, rootProps)
|
|
|
|
// store app context on the root VNode.
|
|
|
|
// this will be set on the root instance on initial mount.
|
|
|
|
vnode.appContext = context
|
2019-12-22 17:25:04 +00:00
|
|
|
|
|
|
|
// HMR root reload
|
|
|
|
if (__BUNDLER__ && __DEV__) {
|
|
|
|
context.reload = () => {
|
|
|
|
render(cloneVNode(vnode), rootContainer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-14 06:30:08 +00:00
|
|
|
if (isHydrate && hydrate) {
|
2020-02-14 04:31:03 +00:00
|
|
|
hydrate(vnode, rootContainer as any)
|
|
|
|
} else {
|
|
|
|
render(vnode, rootContainer)
|
|
|
|
}
|
2019-09-03 22:11:04 +00:00
|
|
|
isMounted = true
|
2020-01-24 02:01:56 +00:00
|
|
|
app._container = rootContainer
|
2019-12-10 16:14:29 +00:00
|
|
|
return vnode.component!.proxy
|
2019-09-03 22:11:04 +00:00
|
|
|
} else if (__DEV__) {
|
|
|
|
warn(
|
|
|
|
`App has already been mounted. Create a new app instance instead.`
|
|
|
|
)
|
|
|
|
}
|
2019-09-02 20:09:34 +00:00
|
|
|
},
|
|
|
|
|
2020-01-23 20:05:38 +00:00
|
|
|
unmount() {
|
|
|
|
if (isMounted) {
|
2020-02-15 16:33:22 +00:00
|
|
|
render(null, app._container)
|
2020-01-23 20:05:38 +00:00
|
|
|
} else if (__DEV__) {
|
|
|
|
warn(`Cannot unmount an app that is not mounted.`)
|
|
|
|
}
|
2020-01-16 17:23:47 +00:00
|
|
|
},
|
|
|
|
|
2019-09-02 20:09:34 +00:00
|
|
|
provide(key, value) {
|
|
|
|
if (__DEV__ && key in context.provides) {
|
|
|
|
warn(
|
|
|
|
`App already provides property with key "${key}". ` +
|
|
|
|
`It will be overwritten with the new value.`
|
|
|
|
)
|
|
|
|
}
|
2019-10-08 16:43:13 +00:00
|
|
|
// TypeScript doesn't allow symbols as index type
|
|
|
|
// https://github.com/Microsoft/TypeScript/issues/24587
|
|
|
|
context.provides[key as string] = value
|
2019-10-28 00:54:33 +00:00
|
|
|
|
|
|
|
return app
|
2019-09-02 20:09:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return app
|
|
|
|
}
|
|
|
|
}
|