apiCreateVaporApp.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import { NO, isFunction, isObject } from '@vue/shared'
  2. import {
  3. type Component,
  4. type ComponentInternalInstance,
  5. createComponentInstance,
  6. validateComponentName,
  7. } from './component'
  8. import { warn } from './warning'
  9. import { type Directive, version } from '.'
  10. import { render, setupComponent, unmountComponent } from './apiRender'
  11. import type { InjectionKey } from './apiInject'
  12. import type { RawProps } from './componentProps'
  13. import { validateDirectiveName } from './directives'
  14. export function createVaporApp(
  15. rootComponent: Component,
  16. rootProps: RawProps | null = null,
  17. ): App {
  18. if (rootProps != null && !isObject(rootProps) && !isFunction(rootProps)) {
  19. __DEV__ &&
  20. warn(`root props passed to app.mount() must be an object or function.`)
  21. rootProps = null
  22. }
  23. const context = createAppContext()
  24. const installedPlugins = new WeakSet()
  25. let instance: ComponentInternalInstance
  26. const app: App = {
  27. _context: context,
  28. version,
  29. get config() {
  30. return context.config
  31. },
  32. set config(v) {
  33. if (__DEV__) {
  34. warn(
  35. `app.config cannot be replaced. Modify individual options instead.`,
  36. )
  37. }
  38. },
  39. use(plugin: Plugin, ...options: any[]) {
  40. if (installedPlugins.has(plugin)) {
  41. __DEV__ && warn(`Plugin has already been applied to target app.`)
  42. } else if (plugin && isFunction(plugin.install)) {
  43. installedPlugins.add(plugin)
  44. plugin.install(app, ...options)
  45. } else if (isFunction(plugin)) {
  46. installedPlugins.add(plugin)
  47. plugin(app, ...options)
  48. } else if (__DEV__) {
  49. warn(
  50. `A plugin must either be a function or an object with an "install" ` +
  51. `function.`,
  52. )
  53. }
  54. return app
  55. },
  56. component(name: string, component?: Component): any {
  57. if (__DEV__) {
  58. validateComponentName(name, context.config)
  59. }
  60. if (!component) {
  61. return context.components[name]
  62. }
  63. if (__DEV__ && context.components[name]) {
  64. warn(`Component "${name}" has already been registered in target app.`)
  65. }
  66. context.components[name] = component
  67. return app
  68. },
  69. directive(name: string, directive?: Directive) {
  70. if (__DEV__) {
  71. validateDirectiveName(name)
  72. }
  73. if (!directive) {
  74. return context.directives[name] as any
  75. }
  76. if (__DEV__ && context.directives[name]) {
  77. warn(`Directive "${name}" has already been registered in target app.`)
  78. }
  79. context.directives[name] = directive
  80. return app
  81. },
  82. mount(rootContainer): any {
  83. if (!instance) {
  84. instance = createComponentInstance(
  85. rootComponent,
  86. rootProps,
  87. null,
  88. null,
  89. false,
  90. context,
  91. )
  92. setupComponent(instance)
  93. render(instance, rootContainer)
  94. return instance
  95. } else if (__DEV__) {
  96. warn(
  97. `App has already been mounted.\n` +
  98. `If you want to remount the same app, move your app creation logic ` +
  99. `into a factory function and create fresh app instances for each ` +
  100. `mount - e.g. \`const createMyApp = () => createApp(App)\``,
  101. )
  102. }
  103. },
  104. unmount() {
  105. if (instance) {
  106. unmountComponent(instance)
  107. } else if (__DEV__) {
  108. warn(`Cannot unmount an app that is not mounted.`)
  109. }
  110. },
  111. provide(key, value) {
  112. if (__DEV__ && (key as string | symbol) in context.provides) {
  113. warn(
  114. `App already provides property with key "${String(key)}". ` +
  115. `It will be overwritten with the new value.`,
  116. )
  117. }
  118. context.provides[key as string | symbol] = value
  119. return app
  120. },
  121. runWithContext(fn) {
  122. const lastApp = currentApp
  123. currentApp = app
  124. try {
  125. return fn()
  126. } finally {
  127. currentApp = lastApp
  128. }
  129. },
  130. }
  131. return app
  132. }
  133. export function createAppContext(): AppContext {
  134. return {
  135. app: null as any,
  136. config: {
  137. isNativeTag: NO,
  138. errorHandler: undefined,
  139. warnHandler: undefined,
  140. globalProperties: {},
  141. },
  142. provides: Object.create(null),
  143. components: {},
  144. directives: {},
  145. }
  146. }
  147. type PluginInstallFunction<Options = any[]> = Options extends unknown[]
  148. ? (app: App, ...options: Options) => any
  149. : (app: App, options: Options) => any
  150. export type ObjectPlugin<Options = any[]> = {
  151. install: PluginInstallFunction<Options>
  152. }
  153. export type FunctionPlugin<Options = any[]> = PluginInstallFunction<Options> &
  154. Partial<ObjectPlugin<Options>>
  155. export type Plugin<Options = any[]> =
  156. | FunctionPlugin<Options>
  157. | ObjectPlugin<Options>
  158. export interface App {
  159. version: string
  160. config: AppConfig
  161. use<Options extends unknown[]>(
  162. plugin: Plugin<Options>,
  163. ...options: Options
  164. ): this
  165. use<Options>(plugin: Plugin<Options>, options: Options): this
  166. component(name: string): Component | undefined
  167. component<T extends Component>(name: string, component: T): this
  168. directive<T = any, V = any>(name: string): Directive<T, V> | undefined
  169. directive<T = any, V = any>(name: string, directive: Directive<T, V>): this
  170. mount(
  171. rootContainer: ParentNode | string,
  172. isHydrate?: boolean,
  173. ): ComponentInternalInstance
  174. unmount(): void
  175. provide<T>(key: string | InjectionKey<T>, value: T): App
  176. runWithContext<T>(fn: () => T): T
  177. _context: AppContext
  178. }
  179. export interface AppConfig {
  180. // @private
  181. readonly isNativeTag: (tag: string) => boolean
  182. errorHandler?: (
  183. err: unknown,
  184. instance: ComponentInternalInstance | null,
  185. info: string,
  186. ) => void
  187. warnHandler?: (
  188. msg: string,
  189. instance: ComponentInternalInstance | null,
  190. trace: string,
  191. ) => void
  192. globalProperties: ComponentCustomProperties & Record<string, any>
  193. }
  194. export interface AppContext {
  195. app: App // for devtools
  196. config: AppConfig
  197. provides: Record<string | symbol, any>
  198. /**
  199. * Resolved component registry, only for components with mixins or extends
  200. * @internal
  201. */
  202. components: Record<string, Component>
  203. /**
  204. * Resolved directive registry, only for components with mixins or extends
  205. * @internal
  206. */
  207. directives: Record<string, Directive>
  208. }
  209. /**
  210. * @internal Used to identify the current app when using `inject()` within
  211. * `app.runWithContext()`.
  212. */
  213. export let currentApp: App | null = null
  214. /**
  215. * Custom properties added to component instances in any way and can be accessed through `this`
  216. *
  217. * @example
  218. * Here is an example of adding a property `$router` to every component instance:
  219. * ```ts
  220. * import { createApp } from 'vue'
  221. * import { Router, createRouter } from 'vue-router'
  222. *
  223. * declare module '@vue/runtime-core' {
  224. * interface ComponentCustomProperties {
  225. * $router: Router
  226. * }
  227. * }
  228. *
  229. * // effectively adding the router to every component instance
  230. * const app = createApp({})
  231. * const router = createRouter()
  232. * app.config.globalProperties.$router = router
  233. *
  234. * const vm = app.mount('#app')
  235. * // we can access the router from the instance
  236. * vm.$router.push('/')
  237. * ```
  238. */
  239. export interface ComponentCustomProperties {}