apiCreateApp.ts 11 KB

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