2019-09-02 20:09:34 +00:00
|
|
|
import {
|
|
|
|
ComponentOptions,
|
|
|
|
Component,
|
|
|
|
ComponentRenderProxy,
|
|
|
|
Data,
|
2019-09-02 20:43:26 +00:00
|
|
|
ComponentInstance,
|
|
|
|
currentRenderingInstance,
|
|
|
|
currentInstance
|
2019-09-02 20:09:34 +00:00
|
|
|
} from './component'
|
|
|
|
import { Directive } from './directives'
|
|
|
|
import { HostNode, RootRenderFunction } from './createRenderer'
|
|
|
|
import { InjectionKey } from './apiInject'
|
2019-09-02 20:43:26 +00:00
|
|
|
import { isFunction, camelize, capitalize } from '@vue/shared'
|
2019-09-02 20:09:34 +00:00
|
|
|
import { warn } from './warning'
|
|
|
|
import { createVNode } from './vnode'
|
|
|
|
|
|
|
|
export interface App {
|
|
|
|
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(
|
|
|
|
rootComponent: Component,
|
|
|
|
rootContainer: string | HostNode,
|
|
|
|
rootProps?: Data
|
|
|
|
): ComponentRenderProxy
|
|
|
|
provide<T>(key: InjectionKey<T> | string, value: T): void
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface AppConfig {
|
|
|
|
devtools: boolean
|
|
|
|
performance: boolean
|
|
|
|
errorHandler?: (
|
|
|
|
err: Error,
|
2019-09-03 22:11:04 +00:00
|
|
|
instance: ComponentRenderProxy | null,
|
2019-09-02 20:09:34 +00:00
|
|
|
info: string
|
|
|
|
) => void
|
|
|
|
warnHandler?: (
|
|
|
|
msg: string,
|
2019-09-03 22:11:04 +00:00
|
|
|
instance: ComponentRenderProxy | 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>
|
|
|
|
}
|
|
|
|
|
|
|
|
type PluginInstallFunction = (app: App) => any
|
|
|
|
|
2019-09-03 22:11:04 +00:00
|
|
|
export type Plugin =
|
2019-09-02 20:09:34 +00:00
|
|
|
| PluginInstallFunction
|
|
|
|
| {
|
|
|
|
install: PluginInstallFunction
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createAppContext(): AppContext {
|
|
|
|
return {
|
|
|
|
config: {
|
|
|
|
devtools: true,
|
|
|
|
performance: false,
|
|
|
|
errorHandler: undefined,
|
2019-09-02 20:43:26 +00:00
|
|
|
warnHandler: undefined
|
2019-09-02 20:09:34 +00:00
|
|
|
},
|
|
|
|
mixins: [],
|
|
|
|
components: {},
|
|
|
|
directives: {},
|
|
|
|
provides: {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createAppAPI(render: RootRenderFunction): () => App {
|
|
|
|
return function createApp(): App {
|
|
|
|
const context = createAppContext()
|
|
|
|
|
2019-09-03 22:11:04 +00:00
|
|
|
let isMounted = false
|
|
|
|
|
2019-09-02 20:09:34 +00:00
|
|
|
const app: App = {
|
|
|
|
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
|
|
|
},
|
|
|
|
|
|
|
|
use(plugin: Plugin) {
|
|
|
|
if (isFunction(plugin)) {
|
|
|
|
plugin(app)
|
|
|
|
} else if (isFunction(plugin.install)) {
|
|
|
|
plugin.install(app)
|
|
|
|
} else if (__DEV__) {
|
|
|
|
warn(
|
|
|
|
`A plugin must either be a function or an object with an "install" ` +
|
|
|
|
`function.`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return app
|
|
|
|
},
|
|
|
|
|
|
|
|
mixin(mixin: ComponentOptions) {
|
|
|
|
context.mixins.push(mixin)
|
|
|
|
return app
|
|
|
|
},
|
|
|
|
|
|
|
|
component(name: string, component?: Component) {
|
|
|
|
// TODO component name validation
|
|
|
|
if (!component) {
|
|
|
|
return context.components[name] as any
|
|
|
|
} else {
|
|
|
|
context.components[name] = component
|
|
|
|
return app
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
directive(name: string, directive?: Directive) {
|
|
|
|
// TODO directive name validation
|
|
|
|
if (!directive) {
|
|
|
|
return context.directives[name] as any
|
|
|
|
} else {
|
|
|
|
context.directives[name] = directive
|
|
|
|
return app
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-09-03 22:11:04 +00:00
|
|
|
mount(rootComponent, rootContainer, rootProps?: Data): any {
|
|
|
|
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
|
|
|
|
render(vnode, rootContainer)
|
|
|
|
isMounted = true
|
|
|
|
return (vnode.component as ComponentInstance).renderProxy
|
|
|
|
} else if (__DEV__) {
|
|
|
|
warn(
|
|
|
|
`App has already been mounted. Create a new app instance instead.`
|
|
|
|
)
|
|
|
|
}
|
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.`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
context.provides[key as any] = value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return app
|
|
|
|
}
|
|
|
|
}
|
2019-09-02 20:43:26 +00:00
|
|
|
|
|
|
|
export function resolveAsset(type: 'components' | 'directives', name: string) {
|
|
|
|
const instance = currentRenderingInstance || currentInstance
|
|
|
|
if (instance) {
|
|
|
|
let camelized
|
|
|
|
let capitalized
|
2019-09-03 22:11:04 +00:00
|
|
|
let res
|
2019-09-02 20:43:26 +00:00
|
|
|
const local = (instance.type as any)[type]
|
2019-09-03 22:11:04 +00:00
|
|
|
if (local) {
|
|
|
|
res =
|
|
|
|
local[name] ||
|
|
|
|
local[(camelized = camelize(name))] ||
|
|
|
|
local[(capitalized = capitalize(camelized))]
|
|
|
|
}
|
|
|
|
if (!res) {
|
|
|
|
const global = instance.appContext[type]
|
|
|
|
res =
|
|
|
|
global[name] ||
|
|
|
|
global[camelized || (camelized = camelize(name))] ||
|
|
|
|
global[capitalized || capitalize(camelized)]
|
|
|
|
}
|
2019-09-02 20:43:26 +00:00
|
|
|
if (__DEV__ && !res) {
|
|
|
|
warn(`Failed to resolve ${type.slice(0, -1)}: ${name}`)
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
} else if (__DEV__) {
|
|
|
|
warn(
|
|
|
|
`resolve${capitalize(type.slice(0, -1))} ` +
|
|
|
|
`can only be used in render() or setup().`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|