apiCreateApp.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. import {
  2. ConcreteComponent,
  3. Data,
  4. validateComponentName,
  5. Component
  6. } from './component'
  7. import { ComponentOptions, RuntimeCompilerOptions } from './componentOptions'
  8. import { ComponentPublicInstance } from './componentPublicInstance'
  9. import { Directive, validateDirectiveName } from './directives'
  10. import { RootRenderFunction } from './renderer'
  11. import { InjectionKey } from './apiInject'
  12. import { warn } from './warning'
  13. import { createVNode, cloneVNode, VNode } from './vnode'
  14. import { RootHydrateFunction } from './hydration'
  15. import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
  16. import { isFunction, NO, isObject } from '@vue/shared'
  17. import { version } from '.'
  18. import { installAppCompatProperties } from './compat/global'
  19. export interface App<HostElement = any> {
  20. version: string
  21. config: AppConfig
  22. use(plugin: Plugin, ...options: any[]): this
  23. mixin(mixin: ComponentOptions): this
  24. component(name: string): Component | undefined
  25. component(name: string, component: Component): this
  26. directive(name: string): Directive | undefined
  27. directive(name: string, directive: Directive): this
  28. mount(
  29. rootContainer: HostElement | string,
  30. isHydrate?: boolean,
  31. isSVG?: boolean
  32. ): ComponentPublicInstance
  33. unmount(): void
  34. provide<T>(key: InjectionKey<T> | string, value: T): this
  35. // internal, but we need to expose these for the server-renderer and devtools
  36. _uid: number
  37. _component: ConcreteComponent
  38. _props: Data | null
  39. _container: HostElement | null
  40. _context: AppContext
  41. /**
  42. * v2 compat only
  43. */
  44. filter?(name: string): Function | undefined
  45. filter?(name: string, filter: Function): this
  46. /**
  47. * @internal v3 compat only
  48. */
  49. _createRoot?(options: ComponentOptions): ComponentPublicInstance
  50. }
  51. export type OptionMergeFunction = (
  52. to: unknown,
  53. from: unknown,
  54. instance: any,
  55. key: string
  56. ) => any
  57. export interface AppConfig {
  58. // @private
  59. readonly isNativeTag?: (tag: string) => boolean
  60. performance: boolean
  61. optionMergeStrategies: Record<string, OptionMergeFunction>
  62. globalProperties: Record<string, any>
  63. errorHandler?: (
  64. err: unknown,
  65. instance: ComponentPublicInstance | null,
  66. info: string
  67. ) => void
  68. warnHandler?: (
  69. msg: string,
  70. instance: ComponentPublicInstance | null,
  71. trace: string
  72. ) => void
  73. /**
  74. * @deprecated use config.compilerOptions.isCustomElement
  75. */
  76. isCustomElement?: (tag: string) => boolean
  77. /**
  78. * Options to pass to @vue/compiler-dom.
  79. * Only supported in runtime compiler build.
  80. */
  81. compilerOptions: RuntimeCompilerOptions
  82. }
  83. export interface AppContext {
  84. app: App // for devtools
  85. config: AppConfig
  86. mixins: ComponentOptions[]
  87. components: Record<string, Component>
  88. directives: Record<string, Directive>
  89. provides: Record<string | symbol, any>
  90. /**
  91. * Flag for de-optimizing props normalization
  92. * @internal
  93. */
  94. deopt?: boolean
  95. /**
  96. * HMR only
  97. * @internal
  98. */
  99. reload?: () => void
  100. /**
  101. * v2 compat only
  102. * @internal
  103. */
  104. filters?: Record<string, Function>
  105. }
  106. type PluginInstallFunction = (app: App, ...options: any[]) => any
  107. export type Plugin =
  108. | PluginInstallFunction & { install?: PluginInstallFunction }
  109. | {
  110. install: PluginInstallFunction
  111. }
  112. export function createAppContext(): AppContext {
  113. return {
  114. app: null as any,
  115. config: {
  116. isNativeTag: NO,
  117. performance: false,
  118. globalProperties: {},
  119. optionMergeStrategies: {},
  120. errorHandler: undefined,
  121. warnHandler: undefined,
  122. compilerOptions: {}
  123. },
  124. mixins: [],
  125. components: {},
  126. directives: {},
  127. provides: Object.create(null)
  128. }
  129. }
  130. export type CreateAppFunction<HostElement> = (
  131. rootComponent: Component,
  132. rootProps?: Data | null
  133. ) => App<HostElement>
  134. let uid = 0
  135. export function createAppAPI<HostElement>(
  136. render: RootRenderFunction,
  137. hydrate?: RootHydrateFunction
  138. ): CreateAppFunction<HostElement> {
  139. return function createApp(rootComponent, rootProps = null) {
  140. if (rootProps != null && !isObject(rootProps)) {
  141. __DEV__ && warn(`root props passed to app.mount() must be an object.`)
  142. rootProps = null
  143. }
  144. const context = createAppContext()
  145. const installedPlugins = new Set()
  146. let isMounted = false
  147. const app: App = (context.app = {
  148. _uid: uid++,
  149. _component: rootComponent as ConcreteComponent,
  150. _props: rootProps,
  151. _container: null,
  152. _context: context,
  153. version,
  154. get config() {
  155. return context.config
  156. },
  157. set config(v) {
  158. if (__DEV__) {
  159. warn(
  160. `app.config cannot be replaced. Modify individual options instead.`
  161. )
  162. }
  163. },
  164. use(plugin: Plugin, ...options: any[]) {
  165. if (installedPlugins.has(plugin)) {
  166. __DEV__ && warn(`Plugin has already been applied to target app.`)
  167. } else if (plugin && isFunction(plugin.install)) {
  168. installedPlugins.add(plugin)
  169. plugin.install(app, ...options)
  170. } else if (isFunction(plugin)) {
  171. installedPlugins.add(plugin)
  172. plugin(app, ...options)
  173. } else if (__DEV__) {
  174. warn(
  175. `A plugin must either be a function or an object with an "install" ` +
  176. `function.`
  177. )
  178. }
  179. return app
  180. },
  181. mixin(mixin: ComponentOptions) {
  182. if (__FEATURE_OPTIONS_API__) {
  183. if (!context.mixins.includes(mixin)) {
  184. context.mixins.push(mixin)
  185. // global mixin with props/emits de-optimizes props/emits
  186. // normalization caching.
  187. if (mixin.props || mixin.emits) {
  188. context.deopt = true
  189. }
  190. } else if (__DEV__) {
  191. warn(
  192. 'Mixin has already been applied to target app' +
  193. (mixin.name ? `: ${mixin.name}` : '')
  194. )
  195. }
  196. } else if (__DEV__) {
  197. warn('Mixins are only available in builds supporting Options API')
  198. }
  199. return app
  200. },
  201. component(name: string, component?: Component): any {
  202. if (__DEV__) {
  203. validateComponentName(name, context.config)
  204. }
  205. if (!component) {
  206. return context.components[name]
  207. }
  208. if (__DEV__ && context.components[name]) {
  209. warn(`Component "${name}" has already been registered in target app.`)
  210. }
  211. context.components[name] = component
  212. return app
  213. },
  214. directive(name: string, directive?: Directive) {
  215. if (__DEV__) {
  216. validateDirectiveName(name)
  217. }
  218. if (!directive) {
  219. return context.directives[name] as any
  220. }
  221. if (__DEV__ && context.directives[name]) {
  222. warn(`Directive "${name}" has already been registered in target app.`)
  223. }
  224. context.directives[name] = directive
  225. return app
  226. },
  227. mount(
  228. rootContainer: HostElement,
  229. isHydrate?: boolean,
  230. isSVG?: boolean
  231. ): any {
  232. if (!isMounted) {
  233. const vnode = createVNode(
  234. rootComponent as ConcreteComponent,
  235. rootProps
  236. )
  237. // store app context on the root VNode.
  238. // this will be set on the root instance on initial mount.
  239. vnode.appContext = context
  240. // HMR root reload
  241. if (__DEV__) {
  242. context.reload = () => {
  243. render(cloneVNode(vnode), rootContainer, isSVG)
  244. }
  245. }
  246. if (isHydrate && hydrate) {
  247. hydrate(vnode as VNode<Node, Element>, rootContainer as any)
  248. } else {
  249. render(vnode, rootContainer, isSVG)
  250. }
  251. isMounted = true
  252. app._container = rootContainer
  253. // for devtools and telemetry
  254. ;(rootContainer as any).__vue_app__ = app
  255. if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
  256. devtoolsInitApp(app, version)
  257. }
  258. return vnode.component!.proxy
  259. } else if (__DEV__) {
  260. warn(
  261. `App has already been mounted.\n` +
  262. `If you want to remount the same app, move your app creation logic ` +
  263. `into a factory function and create fresh app instances for each ` +
  264. `mount - e.g. \`const createMyApp = () => createApp(App)\``
  265. )
  266. }
  267. },
  268. unmount() {
  269. if (isMounted) {
  270. render(null, app._container)
  271. if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
  272. devtoolsUnmountApp(app)
  273. }
  274. delete app._container.__vue_app__
  275. } else if (__DEV__) {
  276. warn(`Cannot unmount an app that is not mounted.`)
  277. }
  278. },
  279. provide(key, value) {
  280. if (__DEV__ && (key as string | symbol) in context.provides) {
  281. warn(
  282. `App already provides property with key "${String(key)}". ` +
  283. `It will be overwritten with the new value.`
  284. )
  285. }
  286. // TypeScript doesn't allow symbols as index type
  287. // https://github.com/Microsoft/TypeScript/issues/24587
  288. context.provides[key as string] = value
  289. return app
  290. }
  291. })
  292. if (__COMPAT__) {
  293. installAppCompatProperties(app, context, render)
  294. }
  295. return app
  296. }
  297. }