apiCreateApp.ts 9.3 KB

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