vue3-yuanma/packages/runtime-core/src/apiCreateApp.ts

359 lines
9.9 KiB
TypeScript
Raw Normal View History

import {
ConcreteComponent,
Data,
validateComponentName,
Component,
ComponentInternalInstance,
getExposeProxy
} from './component'
import {
ComponentOptions,
MergedComponentOptions,
RuntimeCompilerOptions
} from './componentOptions'
import { ComponentPublicInstance } from './componentPublicInstance'
import { Directive, validateDirectiveName } from './directives'
2019-11-03 00:18:35 +08:00
import { RootRenderFunction } from './renderer'
2019-09-03 04:09:34 +08:00
import { InjectionKey } from './apiInject'
import { warn } from './warning'
2020-02-14 12:31:03 +08:00
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'
2019-09-03 04:09:34 +08:00
2019-09-07 04:58:32 +08:00
export interface App<HostElement = any> {
version: string
2019-09-03 04:09:34 +08:00
config: AppConfig
use(plugin: Plugin, ...options: any[]): this
2019-09-03 04:09:34 +08:00
mixin(mixin: ComponentOptions): this
component(name: string): Component | undefined
component(name: string, component: Component): this
2019-09-03 04:09:34 +08:00
directive(name: string): Directive | undefined
directive(name: string, directive: Directive): this
2020-02-14 12:31:03 +08:00
mount(
rootContainer: HostElement | string,
isHydrate?: boolean,
isSVG?: boolean
2020-02-14 12:31:03 +08:00
): 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
2021-04-05 23:54:35 +08:00
/**
2021-04-20 00:08:26 +08:00
* v2 compat only
*/
filter?(name: string): Function | undefined
filter?(name: string, filter: Function): this
/**
* @internal v3 compat only
2021-04-05 23:54:35 +08:00
*/
_createRoot?(options: ComponentOptions): ComponentPublicInstance
2019-09-03 04:09:34 +08:00
}
export type OptionMergeFunction = (to: unknown, from: unknown) => any
2019-09-03 04:09:34 +08:00
export interface AppConfig {
// @private
readonly isNativeTag?: (tag: string) => boolean
2019-09-03 04:09:34 +08:00
performance: boolean
optionMergeStrategies: Record<string, OptionMergeFunction>
globalProperties: Record<string, any>
2019-09-03 04:09:34 +08:00
errorHandler?: (
err: unknown,
2019-09-07 00:58:31 +08:00
instance: ComponentPublicInstance | null,
2019-09-03 04:09:34 +08:00
info: string
) => void
warnHandler?: (
msg: string,
2019-09-07 00:58:31 +08:00
instance: ComponentPublicInstance | null,
2019-09-03 04:09:34 +08:00
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
2019-09-03 04:09:34 +08:00
}
export interface AppContext {
app: App // for devtools
2019-09-03 04:09:34 +08:00
config: AppConfig
mixins: ComponentOptions[]
components: Record<string, Component>
2019-09-03 04:09:34 +08:00
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
2021-04-20 00:08:26 +08:00
/**
* v2 compat only
* @internal
*/
filters?: Record<string, Function>
2019-09-03 04:09:34 +08:00
}
type PluginInstallFunction = (app: App, ...options: any[]) => any
2019-09-03 04:09:34 +08:00
2019-09-04 06:11:04 +08:00
export type Plugin =
2021-07-20 06:24:18 +08:00
| (PluginInstallFunction & { install?: PluginInstallFunction })
2019-09-03 04:09:34 +08:00
| {
install: PluginInstallFunction
}
export function createAppContext(): AppContext {
return {
app: null as any,
2019-09-03 04:09:34 +08:00
config: {
isNativeTag: NO,
2019-09-03 04:09:34 +08:00
performance: false,
globalProperties: {},
optionMergeStrategies: {},
2019-09-03 04:09:34 +08:00
errorHandler: undefined,
warnHandler: undefined,
2021-04-29 00:36:08 +08:00
compilerOptions: {}
2019-09-03 04:09:34 +08:00
},
mixins: [],
components: {},
directives: {},
provides: Object.create(null),
optionsCache: new WeakMap(),
propsCache: new WeakMap(),
emitsCache: new WeakMap()
2019-09-03 04:09:34 +08:00
}
}
export type CreateAppFunction<HostElement> = (
rootComponent: Component,
rootProps?: Data | null
) => App<HostElement>
let uid = 0
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
}
2019-09-03 04:09:34 +08:00
const context = createAppContext()
const installedPlugins = new Set()
2019-09-03 04:09:34 +08:00
2019-09-04 06:11:04 +08:00
let isMounted = false
const app: App = (context.app = {
_uid: uid++,
_component: rootComponent as ConcreteComponent,
_props: rootProps,
_container: null,
_context: context,
_instance: null,
version,
2019-09-03 04:09:34 +08:00
get config() {
return context.config
},
set config(v) {
2019-09-03 04:16:08 +08:00
if (__DEV__) {
warn(
`app.config cannot be replaced. Modify individual options instead.`
)
}
2019-09-03 04:09:34 +08:00
},
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)
2019-09-03 04:09:34 +08:00
} 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__) {
2020-02-16 00:40:09 +08:00
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-29 04:22:03 +08:00
} else if (__DEV__) {
2020-02-16 00:40:09 +08:00
warn('Mixins are only available in builds supporting Options API')
2019-10-29 04:22:03 +08:00
}
2019-09-03 04:09:34 +08:00
return app
},
component(name: string, component?: Component): any {
if (__DEV__) {
validateComponentName(name, context.config)
}
2019-09-03 04:09:34 +08:00
if (!component) {
return context.components[name]
2019-09-03 04:09:34 +08:00
}
if (__DEV__ && context.components[name]) {
warn(`Component "${name}" has already been registered in target app.`)
}
context.components[name] = component
return app
2019-09-03 04:09:34 +08:00
},
directive(name: string, directive?: Directive) {
if (__DEV__) {
validateDirectiveName(name)
}
2019-09-03 04:09:34 +08:00
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
2019-09-03 04:09:34 +08:00
},
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
2019-09-04 06:11:04 +08:00
if (!isMounted) {
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
2019-09-04 06:11:04 +08:00
// store app context on the root VNode.
// this will be set on the root instance on initial mount.
vnode.appContext = context
2019-12-23 01:25:04 +08:00
// HMR root reload
if (__DEV__) {
2019-12-23 01:25:04 +08:00
context.reload = () => {
render(cloneVNode(vnode), rootContainer, isSVG)
2019-12-23 01:25:04 +08:00
}
}
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
2020-02-14 12:31:03 +08:00
} else {
render(vnode, rootContainer, isSVG)
2020-02-14 12:31:03 +08:00
}
2019-09-04 06:11:04 +08:00
isMounted = true
app._container = rootContainer
// for devtools and telemetry
;(rootContainer as any).__vue_app__ = app
2020-07-17 06:18:52 +08:00
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
app._instance = vnode.component
devtoolsInitApp(app, version)
}
2020-07-17 06:18:52 +08:00
return getExposeProxy(vnode.component!) || vnode.component!.proxy
2019-09-04 06:11:04 +08:00
} 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)\``
2019-09-04 06:11:04 +08:00
)
}
2019-09-03 04:09:34 +08:00
},
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.`)
}
},
2019-09-03 04:09:34 +08:00
provide(key, value) {
2020-08-21 05:48:28 +08:00
if (__DEV__ && (key as string | symbol) in context.provides) {
2019-09-03 04:09:34 +08:00
warn(
`App already provides property with key "${String(key)}". ` +
2019-09-03 04:09:34 +08:00
`It will be overwritten with the new value.`
)
}
2019-10-09 00:43:13 +08:00
// TypeScript doesn't allow symbols as index type
// https://github.com/Microsoft/TypeScript/issues/24587
context.provides[key as string] = value
return app
2019-09-03 04:09:34 +08:00
}
})
2020-07-17 06:18:52 +08:00
2021-04-05 23:54:35 +08:00
if (__COMPAT__) {
installAppCompatProperties(app, context, render)
2021-04-05 23:54:35 +08:00
}
2019-09-03 04:09:34 +08:00
return app
}
}