global.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. import {
  2. TrackOpTypes,
  3. TriggerOpTypes,
  4. isReactive,
  5. reactive,
  6. track,
  7. trigger,
  8. } from '@vue/reactivity'
  9. import {
  10. NOOP,
  11. extend,
  12. invokeArrayFns,
  13. isArray,
  14. isFunction,
  15. isObject,
  16. isString,
  17. } from '@vue/shared'
  18. import { warn } from '../warning'
  19. import { cloneVNode, createVNode } from '../vnode'
  20. import type { ElementNamespace, RootRenderFunction } from '../renderer'
  21. import type {
  22. App,
  23. AppConfig,
  24. AppContext,
  25. CreateAppFunction,
  26. Plugin,
  27. } from '../apiCreateApp'
  28. import {
  29. type Component,
  30. type ComponentOptions,
  31. createComponentInstance,
  32. finishComponentSetup,
  33. isRuntimeOnly,
  34. setupComponent,
  35. } from '../component'
  36. import {
  37. type RenderFunction,
  38. internalOptionMergeStrats,
  39. mergeOptions,
  40. } from '../componentOptions'
  41. import type { ComponentPublicInstance } from '../componentPublicInstance'
  42. import { devtoolsInitApp, devtoolsUnmountApp } from '../devtools'
  43. import type { Directive } from '../directives'
  44. import { nextTick } from '../scheduler'
  45. import { version } from '..'
  46. import {
  47. type LegacyConfig,
  48. installLegacyConfigWarnings,
  49. installLegacyOptionMergeStrats,
  50. } from './globalConfig'
  51. import type { LegacyDirective } from './customDirective'
  52. import {
  53. DeprecationTypes,
  54. assertCompatEnabled,
  55. configureCompat,
  56. isCompatEnabled,
  57. softAssertCompatEnabled,
  58. warnDeprecation,
  59. } from './compatConfig'
  60. import type { LegacyPublicInstance } from './instance'
  61. /**
  62. * @deprecated the default `Vue` export has been removed in Vue 3. The type for
  63. * the default export is provided only for migration purposes. Please use
  64. * named imports instead - e.g. `import { createApp } from 'vue'`.
  65. */
  66. export type CompatVue = Pick<App, 'version' | 'component' | 'directive'> & {
  67. configureCompat: typeof configureCompat
  68. // no inference here since these types are not meant for actual use - they
  69. // are merely here to provide type checks for internal implementation and
  70. // information for migration.
  71. new (options?: ComponentOptions): LegacyPublicInstance
  72. version: string
  73. config: AppConfig & LegacyConfig
  74. nextTick: typeof nextTick
  75. use<Options extends unknown[]>(
  76. plugin: Plugin<Options>,
  77. ...options: Options
  78. ): CompatVue
  79. use<Options>(plugin: Plugin<Options>, options: Options): CompatVue
  80. mixin(mixin: ComponentOptions): CompatVue
  81. component(name: string): Component | undefined
  82. component(name: string, component: Component): CompatVue
  83. directive<T = any, V = any>(name: string): Directive<T, V> | undefined
  84. directive<T = any, V = any>(
  85. name: string,
  86. directive: Directive<T, V>,
  87. ): CompatVue
  88. compile(template: string): RenderFunction
  89. /**
  90. * @deprecated Vue 3 no longer supports extending constructors.
  91. */
  92. extend: (options?: ComponentOptions) => CompatVue
  93. /**
  94. * @deprecated Vue 3 no longer needs set() for adding new properties.
  95. */
  96. set(target: any, key: PropertyKey, value: any): void
  97. /**
  98. * @deprecated Vue 3 no longer needs delete() for property deletions.
  99. */
  100. delete(target: any, key: PropertyKey): void
  101. /**
  102. * @deprecated use `reactive` instead.
  103. */
  104. observable: typeof reactive
  105. /**
  106. * @deprecated filters have been removed from Vue 3.
  107. */
  108. filter(name: string, arg?: any): null
  109. /**
  110. * @internal
  111. */
  112. cid: number
  113. /**
  114. * @internal
  115. */
  116. options: ComponentOptions
  117. /**
  118. * @internal
  119. */
  120. util: any
  121. /**
  122. * @internal
  123. */
  124. super: CompatVue
  125. }
  126. export let isCopyingConfig = false
  127. // exported only for test
  128. export let singletonApp: App
  129. let singletonCtor: CompatVue
  130. // Legacy global Vue constructor
  131. export function createCompatVue(
  132. createApp: CreateAppFunction<Element>,
  133. createSingletonApp: CreateAppFunction<Element>,
  134. ): CompatVue {
  135. singletonApp = createSingletonApp({})
  136. const Vue: CompatVue = (singletonCtor = function Vue(
  137. options: ComponentOptions = {},
  138. ) {
  139. return createCompatApp(options, Vue)
  140. } as any)
  141. function createCompatApp(options: ComponentOptions = {}, Ctor: any) {
  142. assertCompatEnabled(DeprecationTypes.GLOBAL_MOUNT, null)
  143. const { data } = options
  144. if (
  145. data &&
  146. !isFunction(data) &&
  147. softAssertCompatEnabled(DeprecationTypes.OPTIONS_DATA_FN, null)
  148. ) {
  149. options.data = () => data
  150. }
  151. const app = createApp(options)
  152. if (Ctor !== Vue) {
  153. applySingletonPrototype(app, Ctor)
  154. }
  155. const vm = app._createRoot!(options)
  156. if (options.el) {
  157. return (vm as any).$mount(options.el)
  158. } else {
  159. return vm
  160. }
  161. }
  162. Vue.version = `2.6.14-compat:${__VERSION__}`
  163. Vue.config = singletonApp.config
  164. Vue.use = (plugin: Plugin, ...options: any[]) => {
  165. if (plugin && isFunction(plugin.install)) {
  166. plugin.install(Vue as any, ...options)
  167. } else if (isFunction(plugin)) {
  168. plugin(Vue as any, ...options)
  169. }
  170. return Vue
  171. }
  172. Vue.mixin = m => {
  173. singletonApp.mixin(m)
  174. return Vue
  175. }
  176. Vue.component = ((name: string, comp: Component) => {
  177. if (comp) {
  178. singletonApp.component(name, comp)
  179. return Vue
  180. } else {
  181. return singletonApp.component(name)
  182. }
  183. }) as any
  184. Vue.directive = ((name: string, dir: Directive | LegacyDirective) => {
  185. if (dir) {
  186. singletonApp.directive(name, dir as Directive)
  187. return Vue
  188. } else {
  189. return singletonApp.directive(name)
  190. }
  191. }) as any
  192. Vue.options = { _base: Vue }
  193. let cid = 1
  194. Vue.cid = cid
  195. Vue.nextTick = nextTick
  196. const extendCache = new WeakMap()
  197. function extendCtor(this: any, extendOptions: ComponentOptions = {}) {
  198. assertCompatEnabled(DeprecationTypes.GLOBAL_EXTEND, null)
  199. if (isFunction(extendOptions)) {
  200. extendOptions = extendOptions.options
  201. }
  202. if (extendCache.has(extendOptions)) {
  203. return extendCache.get(extendOptions)
  204. }
  205. const Super = this
  206. function SubVue(inlineOptions?: ComponentOptions) {
  207. if (!inlineOptions) {
  208. return createCompatApp(SubVue.options, SubVue)
  209. } else {
  210. return createCompatApp(
  211. mergeOptions(
  212. extend({}, SubVue.options),
  213. inlineOptions,
  214. internalOptionMergeStrats as any,
  215. ),
  216. SubVue,
  217. )
  218. }
  219. }
  220. SubVue.super = Super
  221. SubVue.prototype = Object.create(Vue.prototype)
  222. SubVue.prototype.constructor = SubVue
  223. // clone non-primitive base option values for edge case of mutating
  224. // extended options
  225. const mergeBase: any = {}
  226. for (const key in Super.options) {
  227. const superValue = Super.options[key]
  228. mergeBase[key] = isArray(superValue)
  229. ? superValue.slice()
  230. : isObject(superValue)
  231. ? extend(Object.create(null), superValue)
  232. : superValue
  233. }
  234. SubVue.options = mergeOptions(
  235. mergeBase,
  236. extendOptions,
  237. internalOptionMergeStrats as any,
  238. )
  239. SubVue.options._base = SubVue
  240. SubVue.extend = extendCtor.bind(SubVue)
  241. SubVue.mixin = Super.mixin
  242. SubVue.use = Super.use
  243. SubVue.cid = ++cid
  244. extendCache.set(extendOptions, SubVue)
  245. return SubVue
  246. }
  247. Vue.extend = extendCtor.bind(Vue) as any
  248. Vue.set = (target, key, value) => {
  249. assertCompatEnabled(DeprecationTypes.GLOBAL_SET, null)
  250. target[key] = value
  251. }
  252. Vue.delete = (target, key) => {
  253. assertCompatEnabled(DeprecationTypes.GLOBAL_DELETE, null)
  254. delete target[key]
  255. }
  256. Vue.observable = (target: any) => {
  257. assertCompatEnabled(DeprecationTypes.GLOBAL_OBSERVABLE, null)
  258. return reactive(target)
  259. }
  260. Vue.filter = ((name: string, filter?: any) => {
  261. if (filter) {
  262. singletonApp.filter!(name, filter)
  263. return Vue
  264. } else {
  265. return singletonApp.filter!(name)
  266. }
  267. }) as any
  268. // internal utils - these are technically internal but some plugins use it.
  269. const util = {
  270. warn: __DEV__ ? warn : NOOP,
  271. extend,
  272. mergeOptions: (parent: any, child: any, vm?: ComponentPublicInstance) =>
  273. mergeOptions(
  274. parent,
  275. child,
  276. vm ? undefined : (internalOptionMergeStrats as any),
  277. ),
  278. defineReactive,
  279. }
  280. Object.defineProperty(Vue, 'util', {
  281. get() {
  282. assertCompatEnabled(DeprecationTypes.GLOBAL_PRIVATE_UTIL, null)
  283. return util
  284. },
  285. })
  286. Vue.configureCompat = configureCompat
  287. return Vue
  288. }
  289. export function installAppCompatProperties(
  290. app: App,
  291. context: AppContext,
  292. render: RootRenderFunction<any>,
  293. ): void {
  294. installFilterMethod(app, context)
  295. installLegacyOptionMergeStrats(app.config)
  296. if (!singletonApp) {
  297. // this is the call of creating the singleton itself so the rest is
  298. // unnecessary
  299. return
  300. }
  301. installCompatMount(app, context, render)
  302. installLegacyAPIs(app)
  303. applySingletonAppMutations(app)
  304. if (__DEV__) installLegacyConfigWarnings(app.config)
  305. }
  306. function installFilterMethod(app: App, context: AppContext) {
  307. context.filters = {}
  308. app.filter = (name: string, filter?: Function): any => {
  309. assertCompatEnabled(DeprecationTypes.FILTERS, null)
  310. if (!filter) {
  311. return context.filters![name]
  312. }
  313. if (__DEV__ && context.filters![name]) {
  314. warn(`Filter "${name}" has already been registered.`)
  315. }
  316. context.filters![name] = filter
  317. return app
  318. }
  319. }
  320. function installLegacyAPIs(app: App) {
  321. // expose global API on app instance for legacy plugins
  322. Object.defineProperties(app, {
  323. // so that app.use() can work with legacy plugins that extend prototypes
  324. prototype: {
  325. get() {
  326. __DEV__ && warnDeprecation(DeprecationTypes.GLOBAL_PROTOTYPE, null)
  327. return app.config.globalProperties
  328. },
  329. },
  330. nextTick: { value: nextTick },
  331. extend: { value: singletonCtor.extend },
  332. set: { value: singletonCtor.set },
  333. delete: { value: singletonCtor.delete },
  334. observable: { value: singletonCtor.observable },
  335. util: {
  336. get() {
  337. return singletonCtor.util
  338. },
  339. },
  340. })
  341. }
  342. function applySingletonAppMutations(app: App) {
  343. // copy over asset registries and deopt flag
  344. app._context.mixins = [...singletonApp._context.mixins]
  345. ;['components', 'directives', 'filters'].forEach(key => {
  346. // @ts-expect-error
  347. app._context[key] = Object.create(singletonApp._context[key])
  348. })
  349. // copy over global config mutations
  350. isCopyingConfig = true
  351. for (const key in singletonApp.config) {
  352. if (key === 'isNativeTag') continue
  353. if (
  354. isRuntimeOnly() &&
  355. (key === 'isCustomElement' || key === 'compilerOptions')
  356. ) {
  357. continue
  358. }
  359. const val = singletonApp.config[key as keyof AppConfig]
  360. // @ts-expect-error
  361. app.config[key] = isObject(val) ? Object.create(val) : val
  362. // compat for runtime ignoredElements -> isCustomElement
  363. if (
  364. key === 'ignoredElements' &&
  365. isCompatEnabled(DeprecationTypes.CONFIG_IGNORED_ELEMENTS, null) &&
  366. !isRuntimeOnly() &&
  367. isArray(val)
  368. ) {
  369. app.config.compilerOptions.isCustomElement = tag => {
  370. return val.some(v => (isString(v) ? v === tag : v.test(tag)))
  371. }
  372. }
  373. }
  374. isCopyingConfig = false
  375. applySingletonPrototype(app, singletonCtor)
  376. }
  377. function applySingletonPrototype(app: App, Ctor: Function) {
  378. // copy prototype augmentations as config.globalProperties
  379. const enabled = isCompatEnabled(DeprecationTypes.GLOBAL_PROTOTYPE, null)
  380. if (enabled) {
  381. app.config.globalProperties = Object.create(Ctor.prototype)
  382. }
  383. let hasPrototypeAugmentations = false
  384. for (const key of Object.getOwnPropertyNames(Ctor.prototype)) {
  385. if (key !== 'constructor') {
  386. hasPrototypeAugmentations = true
  387. if (enabled) {
  388. Object.defineProperty(
  389. app.config.globalProperties,
  390. key,
  391. Object.getOwnPropertyDescriptor(Ctor.prototype, key)!,
  392. )
  393. }
  394. }
  395. }
  396. if (__DEV__ && hasPrototypeAugmentations) {
  397. warnDeprecation(DeprecationTypes.GLOBAL_PROTOTYPE, null)
  398. }
  399. }
  400. function installCompatMount(
  401. app: App,
  402. context: AppContext,
  403. render: RootRenderFunction,
  404. ) {
  405. let isMounted = false
  406. /**
  407. * Vue 2 supports the behavior of creating a component instance but not
  408. * mounting it, which is no longer possible in Vue 3 - this internal
  409. * function simulates that behavior.
  410. */
  411. app._createRoot = options => {
  412. const component = app._component as Component
  413. const vnode = createVNode(component, options.propsData || null)
  414. vnode.appContext = context
  415. const hasNoRender =
  416. !isFunction(component) && !component.render && !component.template
  417. const emptyRender = () => {}
  418. // create root instance
  419. const instance = createComponentInstance(vnode, null, null)
  420. // suppress "missing render fn" warning since it can't be determined
  421. // until $mount is called
  422. if (hasNoRender) {
  423. instance.render = emptyRender
  424. }
  425. setupComponent(instance)
  426. vnode.component = instance
  427. vnode.isCompatRoot = true
  428. // $mount & $destroy
  429. // these are defined on ctx and picked up by the $mount/$destroy
  430. // public property getters on the instance proxy.
  431. // Note: the following assumes DOM environment since the compat build
  432. // only targets web. It essentially includes logic for app.mount from
  433. // both runtime-core AND runtime-dom.
  434. instance.ctx._compat_mount = (selectorOrEl?: string | Element) => {
  435. if (isMounted) {
  436. __DEV__ && warn(`Root instance is already mounted.`)
  437. return
  438. }
  439. let container: Element
  440. if (typeof selectorOrEl === 'string') {
  441. // eslint-disable-next-line
  442. const result = document.querySelector(selectorOrEl)
  443. if (!result) {
  444. __DEV__ &&
  445. warn(
  446. `Failed to mount root instance: selector "${selectorOrEl}" returned null.`,
  447. )
  448. return
  449. }
  450. container = result
  451. } else {
  452. // eslint-disable-next-line
  453. container = selectorOrEl || document.createElement('div')
  454. }
  455. let namespace: ElementNamespace
  456. if (container instanceof SVGElement) namespace = 'svg'
  457. else if (
  458. typeof MathMLElement === 'function' &&
  459. container instanceof MathMLElement
  460. )
  461. namespace = 'mathml'
  462. // HMR root reload
  463. if (__DEV__) {
  464. context.reload = () => {
  465. const cloned = cloneVNode(vnode)
  466. // compat mode will use instance if not reset to null
  467. cloned.component = null
  468. render(cloned, container, namespace)
  469. }
  470. }
  471. // resolve in-DOM template if component did not provide render
  472. // and no setup/mixin render functions are provided (by checking
  473. // that the instance is still using the placeholder render fn)
  474. if (hasNoRender && instance.render === emptyRender) {
  475. // root directives check
  476. if (__DEV__) {
  477. for (let i = 0; i < container.attributes.length; i++) {
  478. const attr = container.attributes[i]
  479. if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
  480. warnDeprecation(DeprecationTypes.GLOBAL_MOUNT_CONTAINER, null)
  481. break
  482. }
  483. }
  484. }
  485. instance.render = null
  486. ;(component as ComponentOptions).template = container.innerHTML
  487. finishComponentSetup(instance, false, true /* skip options */)
  488. }
  489. // clear content before mounting
  490. container.textContent = ''
  491. // TODO hydration
  492. render(vnode, container, namespace)
  493. if (container instanceof Element) {
  494. container.removeAttribute('v-cloak')
  495. container.setAttribute('data-v-app', '')
  496. }
  497. isMounted = true
  498. app._container = container
  499. // for devtools and telemetry
  500. ;(container as any).__vue_app__ = app
  501. if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
  502. devtoolsInitApp(app, version)
  503. }
  504. return instance.proxy!
  505. }
  506. instance.ctx._compat_destroy = () => {
  507. if (isMounted) {
  508. render(null, app._container)
  509. if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
  510. devtoolsUnmountApp(app)
  511. }
  512. delete app._container.__vue_app__
  513. } else {
  514. const { bum, scope, um } = instance
  515. // beforeDestroy hooks
  516. if (bum) {
  517. invokeArrayFns(bum)
  518. }
  519. if (isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)) {
  520. instance.emit('hook:beforeDestroy')
  521. }
  522. // stop effects
  523. if (scope) {
  524. scope.stop()
  525. }
  526. // unmounted hook
  527. if (um) {
  528. invokeArrayFns(um)
  529. }
  530. if (isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)) {
  531. instance.emit('hook:destroyed')
  532. }
  533. }
  534. }
  535. return instance.proxy!
  536. }
  537. }
  538. const methodsToPatch = [
  539. 'push',
  540. 'pop',
  541. 'shift',
  542. 'unshift',
  543. 'splice',
  544. 'sort',
  545. 'reverse',
  546. ]
  547. const patched = new WeakSet<object>()
  548. function defineReactive(obj: any, key: string, val: any) {
  549. // it's possible for the original object to be mutated after being defined
  550. // and expecting reactivity... we are covering it here because this seems to
  551. // be a bit more common.
  552. if (isObject(val) && !isReactive(val) && !patched.has(val)) {
  553. const reactiveVal = reactive(val)
  554. if (isArray(val)) {
  555. methodsToPatch.forEach((m: any) => {
  556. val[m] = (...args: any[]) => {
  557. Array.prototype[m].apply(reactiveVal, args)
  558. }
  559. })
  560. } else {
  561. Object.keys(val).forEach(key => {
  562. try {
  563. defineReactiveSimple(val, key, val[key])
  564. } catch (e: any) {}
  565. })
  566. }
  567. }
  568. const i = obj.$
  569. if (i && obj === i.proxy) {
  570. // target is a Vue instance - define on instance.ctx
  571. defineReactiveSimple(i.ctx, key, val)
  572. i.accessCache = Object.create(null)
  573. } else if (isReactive(obj)) {
  574. obj[key] = val
  575. } else {
  576. defineReactiveSimple(obj, key, val)
  577. }
  578. }
  579. function defineReactiveSimple(obj: any, key: string, val: any) {
  580. val = isObject(val) ? reactive(val) : val
  581. Object.defineProperty(obj, key, {
  582. enumerable: true,
  583. configurable: true,
  584. get() {
  585. track(obj, TrackOpTypes.GET, key)
  586. return val
  587. },
  588. set(newVal) {
  589. val = isObject(newVal) ? reactive(newVal) : newVal
  590. trigger(obj, TriggerOpTypes.SET, key, newVal)
  591. },
  592. })
  593. }