apiApp.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import {
  2. ComponentOptions,
  3. Component,
  4. ComponentRenderProxy,
  5. Data,
  6. ComponentInstance,
  7. currentRenderingInstance,
  8. currentInstance
  9. } from './component'
  10. import { Directive } from './directives'
  11. import { HostNode, RootRenderFunction } from './createRenderer'
  12. import { InjectionKey } from './apiInject'
  13. import { isFunction, camelize, capitalize } from '@vue/shared'
  14. import { warn } from './warning'
  15. import { createVNode } from './vnode'
  16. export interface App {
  17. config: AppConfig
  18. use(plugin: Plugin, options?: any): this
  19. mixin(mixin: ComponentOptions): this
  20. component(name: string): Component | undefined
  21. component(name: string, component: Component): this
  22. directive(name: string): Directive | undefined
  23. directive(name: string, directive: Directive): this
  24. mount(
  25. rootComponent: Component,
  26. rootContainer: string | HostNode,
  27. rootProps?: Data
  28. ): ComponentRenderProxy
  29. provide<T>(key: InjectionKey<T> | string, value: T): void
  30. }
  31. export interface AppConfig {
  32. silent: boolean
  33. devtools: boolean
  34. performance: boolean
  35. errorHandler?: (
  36. err: Error,
  37. instance: ComponentRenderProxy | null,
  38. info: string
  39. ) => void
  40. warnHandler?: (
  41. msg: string,
  42. instance: ComponentRenderProxy | null,
  43. trace: string
  44. ) => void
  45. }
  46. export interface AppContext {
  47. config: AppConfig
  48. mixins: ComponentOptions[]
  49. components: Record<string, Component>
  50. directives: Record<string, Directive>
  51. provides: Record<string | symbol, any>
  52. }
  53. type PluginInstallFunction = (app: App) => any
  54. export type Plugin =
  55. | PluginInstallFunction
  56. | {
  57. install: PluginInstallFunction
  58. }
  59. export function createAppContext(): AppContext {
  60. return {
  61. config: {
  62. silent: false,
  63. devtools: true,
  64. performance: false,
  65. errorHandler: undefined,
  66. warnHandler: undefined
  67. },
  68. mixins: [],
  69. components: {},
  70. directives: {},
  71. provides: {}
  72. }
  73. }
  74. export function createAppAPI(render: RootRenderFunction): () => App {
  75. return function createApp(): App {
  76. const context = createAppContext()
  77. let isMounted = false
  78. const app: App = {
  79. get config() {
  80. return context.config
  81. },
  82. set config(v) {
  83. if (__DEV__) {
  84. warn(
  85. `app.config cannot be replaced. Modify individual options instead.`
  86. )
  87. }
  88. },
  89. use(plugin: Plugin) {
  90. if (isFunction(plugin)) {
  91. plugin(app)
  92. } else if (isFunction(plugin.install)) {
  93. plugin.install(app)
  94. } else if (__DEV__) {
  95. warn(
  96. `A plugin must either be a function or an object with an "install" ` +
  97. `function.`
  98. )
  99. }
  100. return app
  101. },
  102. mixin(mixin: ComponentOptions) {
  103. context.mixins.push(mixin)
  104. return app
  105. },
  106. component(name: string, component?: Component) {
  107. // TODO component name validation
  108. if (!component) {
  109. return context.components[name] as any
  110. } else {
  111. context.components[name] = component
  112. return app
  113. }
  114. },
  115. directive(name: string, directive?: Directive) {
  116. // TODO directive name validation
  117. if (!directive) {
  118. return context.directives[name] as any
  119. } else {
  120. context.directives[name] = directive
  121. return app
  122. }
  123. },
  124. mount(rootComponent, rootContainer, rootProps?: Data): any {
  125. if (!isMounted) {
  126. const vnode = createVNode(rootComponent, rootProps)
  127. // store app context on the root VNode.
  128. // this will be set on the root instance on initial mount.
  129. vnode.appContext = context
  130. render(vnode, rootContainer)
  131. isMounted = true
  132. return (vnode.component as ComponentInstance).renderProxy
  133. } else if (__DEV__) {
  134. warn(
  135. `App has already been mounted. Create a new app instance instead.`
  136. )
  137. }
  138. },
  139. provide(key, value) {
  140. if (__DEV__ && key in context.provides) {
  141. warn(
  142. `App already provides property with key "${key}". ` +
  143. `It will be overwritten with the new value.`
  144. )
  145. }
  146. context.provides[key as any] = value
  147. }
  148. }
  149. return app
  150. }
  151. }
  152. export function resolveAsset(type: 'components' | 'directives', name: string) {
  153. const instance = currentRenderingInstance || currentInstance
  154. if (instance) {
  155. let camelized
  156. let capitalized
  157. let res
  158. const local = (instance.type as any)[type]
  159. if (local) {
  160. res =
  161. local[name] ||
  162. local[(camelized = camelize(name))] ||
  163. local[(capitalized = capitalize(camelized))]
  164. }
  165. if (!res) {
  166. const global = instance.appContext[type]
  167. res =
  168. global[name] ||
  169. global[camelized || (camelized = camelize(name))] ||
  170. global[capitalized || capitalize(camelized)]
  171. }
  172. if (__DEV__ && !res) {
  173. warn(`Failed to resolve ${type.slice(0, -1)}: ${name}`)
  174. }
  175. return res
  176. } else if (__DEV__) {
  177. warn(
  178. `resolve${capitalize(type.slice(0, -1))} ` +
  179. `can only be used in render() or setup().`
  180. )
  181. }
  182. }