2024-04-19 16:49:21 +08:00
|
|
|
import { isFunction, isObject } from '@vue/shared'
|
2024-03-16 18:54:36 +08:00
|
|
|
import {
|
|
|
|
type Component,
|
|
|
|
type ComponentInternalInstance,
|
|
|
|
createComponentInstance,
|
|
|
|
} from './component'
|
|
|
|
import { warn } from './warning'
|
|
|
|
import { version } from '.'
|
|
|
|
import { render, setupComponent, unmountComponent } from './apiRender'
|
2024-03-22 23:41:16 +08:00
|
|
|
import type { InjectionKey } from './apiInject'
|
2024-03-16 18:54:36 +08:00
|
|
|
import type { RawProps } from './componentProps'
|
|
|
|
|
|
|
|
export function createVaporApp(
|
|
|
|
rootComponent: Component,
|
|
|
|
rootProps: RawProps | null = null,
|
|
|
|
): App {
|
2024-04-19 16:49:21 +08:00
|
|
|
if (rootProps != null && !isObject(rootProps) && !isFunction(rootProps)) {
|
|
|
|
__DEV__ &&
|
|
|
|
warn(`root props passed to app.mount() must be an object or function.`)
|
2024-03-16 18:54:36 +08:00
|
|
|
rootProps = null
|
|
|
|
}
|
|
|
|
|
|
|
|
const context = createAppContext()
|
|
|
|
let instance: ComponentInternalInstance
|
|
|
|
|
|
|
|
const app: App = {
|
2024-03-22 23:41:16 +08:00
|
|
|
_context: context,
|
|
|
|
|
2024-03-16 18:54:36 +08:00
|
|
|
version,
|
|
|
|
|
|
|
|
get config() {
|
|
|
|
return context.config
|
|
|
|
},
|
|
|
|
|
|
|
|
set config(v) {
|
|
|
|
if (__DEV__) {
|
|
|
|
warn(
|
|
|
|
`app.config cannot be replaced. Modify individual options instead.`,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
mount(rootContainer): any {
|
|
|
|
if (!instance) {
|
2024-03-24 20:29:00 +08:00
|
|
|
instance = createComponentInstance(
|
|
|
|
rootComponent,
|
|
|
|
rootProps,
|
|
|
|
null,
|
|
|
|
null,
|
2024-05-17 20:44:58 +08:00
|
|
|
false,
|
2024-03-24 20:29:00 +08:00
|
|
|
context,
|
|
|
|
)
|
2024-03-16 18:54:36 +08:00
|
|
|
setupComponent(instance)
|
|
|
|
render(instance, rootContainer)
|
|
|
|
return instance
|
|
|
|
} 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 (instance) {
|
|
|
|
unmountComponent(instance)
|
|
|
|
} else if (__DEV__) {
|
|
|
|
warn(`Cannot unmount an app that is not mounted.`)
|
|
|
|
}
|
|
|
|
},
|
2024-03-22 23:41:16 +08:00
|
|
|
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
|
|
|
|
},
|
|
|
|
runWithContext(fn) {
|
|
|
|
const lastApp = currentApp
|
|
|
|
currentApp = app
|
|
|
|
try {
|
|
|
|
return fn()
|
|
|
|
} finally {
|
|
|
|
currentApp = lastApp
|
|
|
|
}
|
|
|
|
},
|
2024-03-16 18:54:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return app
|
|
|
|
}
|
|
|
|
|
2024-03-22 23:41:16 +08:00
|
|
|
export function createAppContext(): AppContext {
|
2024-03-16 18:54:36 +08:00
|
|
|
return {
|
|
|
|
app: null as any,
|
|
|
|
config: {
|
|
|
|
errorHandler: undefined,
|
|
|
|
warnHandler: undefined,
|
2024-05-17 20:38:57 +08:00
|
|
|
globalProperties: {},
|
2024-03-16 18:54:36 +08:00
|
|
|
},
|
2024-03-22 23:41:16 +08:00
|
|
|
provides: Object.create(null),
|
2024-03-16 18:54:36 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface App {
|
|
|
|
version: string
|
|
|
|
config: AppConfig
|
|
|
|
|
|
|
|
mount(
|
|
|
|
rootContainer: ParentNode | string,
|
|
|
|
isHydrate?: boolean,
|
|
|
|
): ComponentInternalInstance
|
|
|
|
unmount(): void
|
2024-03-22 23:41:16 +08:00
|
|
|
provide<T>(key: string | InjectionKey<T>, value: T): App
|
|
|
|
runWithContext<T>(fn: () => T): T
|
|
|
|
|
|
|
|
_context: AppContext
|
2024-03-16 18:54:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface AppConfig {
|
|
|
|
errorHandler?: (
|
|
|
|
err: unknown,
|
|
|
|
instance: ComponentInternalInstance | null,
|
|
|
|
info: string,
|
|
|
|
) => void
|
|
|
|
warnHandler?: (
|
|
|
|
msg: string,
|
|
|
|
instance: ComponentInternalInstance | null,
|
|
|
|
trace: string,
|
|
|
|
) => void
|
2024-05-17 20:38:57 +08:00
|
|
|
globalProperties: ComponentCustomProperties & Record<string, any>
|
2024-03-16 18:54:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface AppContext {
|
|
|
|
app: App // for devtools
|
|
|
|
config: AppConfig
|
2024-03-22 23:41:16 +08:00
|
|
|
provides: Record<string | symbol, any>
|
2024-03-16 18:54:36 +08:00
|
|
|
}
|
2024-03-22 23:41:16 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @internal Used to identify the current app when using `inject()` within
|
|
|
|
* `app.runWithContext()`.
|
|
|
|
*/
|
|
|
|
export let currentApp: App | null = null
|
2024-05-17 20:38:57 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Custom properties added to component instances in any way and can be accessed through `this`
|
|
|
|
*
|
|
|
|
* @example
|
|
|
|
* Here is an example of adding a property `$router` to every component instance:
|
|
|
|
* ```ts
|
|
|
|
* import { createApp } from 'vue'
|
|
|
|
* import { Router, createRouter } from 'vue-router'
|
|
|
|
*
|
|
|
|
* declare module '@vue/runtime-core' {
|
|
|
|
* interface ComponentCustomProperties {
|
|
|
|
* $router: Router
|
|
|
|
* }
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* // effectively adding the router to every component instance
|
|
|
|
* const app = createApp({})
|
|
|
|
* const router = createRouter()
|
|
|
|
* app.config.globalProperties.$router = router
|
|
|
|
*
|
|
|
|
* const vm = app.mount('#app')
|
|
|
|
* // we can access the router from the instance
|
|
|
|
* vm.$router.push('/')
|
|
|
|
* ```
|
|
|
|
*/
|
|
|
|
export interface ComponentCustomProperties {}
|