apiCreateApp.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. import {
  2. Component,
  3. Data,
  4. validateComponentName,
  5. PublicAPIComponent
  6. } from './component'
  7. import { ComponentOptions } from './componentOptions'
  8. import { ComponentPublicInstance } from './componentProxy'
  9. import { Directive, validateDirectiveName } from './directives'
  10. import { RootRenderFunction } from './renderer'
  11. import { InjectionKey } from './apiInject'
  12. import { isFunction, NO, isObject } from '@vue/shared'
  13. import { warn } from './warning'
  14. import { createVNode, cloneVNode, VNode } from './vnode'
  15. import { RootHydrateFunction } from './hydration'
  16. export interface App<HostElement = any> {
  17. config: AppConfig
  18. use(plugin: Plugin, ...options: any[]): this
  19. mixin(mixin: ComponentOptions): this
  20. component(name: string): PublicAPIComponent | undefined
  21. component(name: string, component: PublicAPIComponent): this
  22. directive(name: string): Directive | undefined
  23. directive(name: string, directive: Directive): this
  24. mount(
  25. rootContainer: HostElement | string,
  26. isHydrate?: boolean
  27. ): ComponentPublicInstance
  28. unmount(rootContainer: HostElement | string): void
  29. provide<T>(key: InjectionKey<T> | string, value: T): this
  30. // internal. We need to expose these for the server-renderer
  31. _component: Component
  32. _props: Data | null
  33. _container: HostElement | null
  34. _context: AppContext
  35. }
  36. export type OptionMergeFunction = (
  37. to: unknown,
  38. from: unknown,
  39. instance: any,
  40. key: string
  41. ) => any
  42. export interface AppConfig {
  43. // @private
  44. readonly isNativeTag?: (tag: string) => boolean
  45. devtools: boolean
  46. performance: boolean
  47. optionMergeStrategies: Record<string, OptionMergeFunction>
  48. globalProperties: Record<string, any>
  49. isCustomElement: (tag: string) => boolean
  50. errorHandler?: (
  51. err: unknown,
  52. instance: ComponentPublicInstance | null,
  53. info: string
  54. ) => void
  55. warnHandler?: (
  56. msg: string,
  57. instance: ComponentPublicInstance | null,
  58. trace: string
  59. ) => void
  60. }
  61. export interface AppContext {
  62. config: AppConfig
  63. mixins: ComponentOptions[]
  64. components: Record<string, PublicAPIComponent>
  65. directives: Record<string, Directive>
  66. provides: Record<string | symbol, any>
  67. reload?: () => void // HMR only
  68. }
  69. type PluginInstallFunction = (app: App, ...options: any[]) => any
  70. export type Plugin =
  71. | PluginInstallFunction & { install?: PluginInstallFunction }
  72. | {
  73. install: PluginInstallFunction
  74. }
  75. export function createAppContext(): AppContext {
  76. return {
  77. config: {
  78. isNativeTag: NO,
  79. devtools: true,
  80. performance: false,
  81. globalProperties: {},
  82. optionMergeStrategies: {},
  83. isCustomElement: NO,
  84. errorHandler: undefined,
  85. warnHandler: undefined
  86. },
  87. mixins: [],
  88. components: {},
  89. directives: {},
  90. provides: Object.create(null)
  91. }
  92. }
  93. export type CreateAppFunction<HostElement> = (
  94. rootComponent: PublicAPIComponent,
  95. rootProps?: Data | null
  96. ) => App<HostElement>
  97. export function createAppAPI<HostElement>(
  98. render: RootRenderFunction,
  99. hydrate?: RootHydrateFunction
  100. ): CreateAppFunction<HostElement> {
  101. return function createApp(rootComponent, rootProps = null) {
  102. if (rootProps != null && !isObject(rootProps)) {
  103. __DEV__ && warn(`root props passed to app.mount() must be an object.`)
  104. rootProps = null
  105. }
  106. const context = createAppContext()
  107. const installedPlugins = new Set()
  108. let isMounted = false
  109. const app: App = {
  110. _component: rootComponent as Component,
  111. _props: rootProps,
  112. _container: null,
  113. _context: context,
  114. get config() {
  115. return context.config
  116. },
  117. set config(v) {
  118. if (__DEV__) {
  119. warn(
  120. `app.config cannot be replaced. Modify individual options instead.`
  121. )
  122. }
  123. },
  124. use(plugin: Plugin, ...options: any[]) {
  125. if (installedPlugins.has(plugin)) {
  126. __DEV__ && warn(`Plugin has already been applied to target app.`)
  127. } else if (plugin && isFunction(plugin.install)) {
  128. installedPlugins.add(plugin)
  129. plugin.install(app, ...options)
  130. } else if (isFunction(plugin)) {
  131. installedPlugins.add(plugin)
  132. plugin(app, ...options)
  133. } else if (__DEV__) {
  134. warn(
  135. `A plugin must either be a function or an object with an "install" ` +
  136. `function.`
  137. )
  138. }
  139. return app
  140. },
  141. mixin(mixin: ComponentOptions) {
  142. if (__FEATURE_OPTIONS__) {
  143. if (!context.mixins.includes(mixin)) {
  144. context.mixins.push(mixin)
  145. } else if (__DEV__) {
  146. warn(
  147. 'Mixin has already been applied to target app' +
  148. (mixin.name ? `: ${mixin.name}` : '')
  149. )
  150. }
  151. } else if (__DEV__) {
  152. warn('Mixins are only available in builds supporting Options API')
  153. }
  154. return app
  155. },
  156. component(name: string, component?: PublicAPIComponent): any {
  157. if (__DEV__) {
  158. validateComponentName(name, context.config)
  159. }
  160. if (!component) {
  161. return context.components[name]
  162. }
  163. if (__DEV__ && context.components[name]) {
  164. warn(`Component "${name}" has already been registered in target app.`)
  165. }
  166. context.components[name] = component
  167. return app
  168. },
  169. directive(name: string, directive?: Directive) {
  170. if (__DEV__) {
  171. validateDirectiveName(name)
  172. }
  173. if (!directive) {
  174. return context.directives[name] as any
  175. }
  176. if (__DEV__ && context.directives[name]) {
  177. warn(`Directive "${name}" has already been registered in target app.`)
  178. }
  179. context.directives[name] = directive
  180. return app
  181. },
  182. mount(rootContainer: HostElement, isHydrate?: boolean): any {
  183. if (!isMounted) {
  184. const vnode = createVNode(rootComponent as Component, rootProps)
  185. // store app context on the root VNode.
  186. // this will be set on the root instance on initial mount.
  187. vnode.appContext = context
  188. // HMR root reload
  189. if (__DEV__) {
  190. context.reload = () => {
  191. render(cloneVNode(vnode), rootContainer)
  192. }
  193. }
  194. if (isHydrate && hydrate) {
  195. hydrate(vnode as VNode<Node, Element>, rootContainer as any)
  196. } else {
  197. render(vnode, rootContainer)
  198. }
  199. isMounted = true
  200. app._container = rootContainer
  201. return vnode.component!.proxy
  202. } else if (__DEV__) {
  203. warn(
  204. `App has already been mounted. Create a new app instance instead.`
  205. )
  206. }
  207. },
  208. unmount() {
  209. if (isMounted) {
  210. render(null, app._container)
  211. } else if (__DEV__) {
  212. warn(`Cannot unmount an app that is not mounted.`)
  213. }
  214. },
  215. provide(key, value) {
  216. if (__DEV__ && key in context.provides) {
  217. warn(
  218. `App already provides property with key "${String(key)}". ` +
  219. `It will be overwritten with the new value.`
  220. )
  221. }
  222. // TypeScript doesn't allow symbols as index type
  223. // https://github.com/Microsoft/TypeScript/issues/24587
  224. context.provides[key as string] = value
  225. return app
  226. }
  227. }
  228. return app
  229. }
  230. }