global.ts 14 KB

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