compatConfig.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. import { extend, hasOwn, isArray, isFunction } from '@vue/shared'
  2. import {
  3. type Component,
  4. type ComponentInternalInstance,
  5. type ComponentOptions,
  6. formatComponentName,
  7. getComponentName,
  8. getCurrentInstance,
  9. isRuntimeOnly,
  10. } from '../component'
  11. import { warn } from '../warning'
  12. export enum DeprecationTypes {
  13. GLOBAL_MOUNT = 'GLOBAL_MOUNT',
  14. GLOBAL_MOUNT_CONTAINER = 'GLOBAL_MOUNT_CONTAINER',
  15. GLOBAL_EXTEND = 'GLOBAL_EXTEND',
  16. GLOBAL_PROTOTYPE = 'GLOBAL_PROTOTYPE',
  17. GLOBAL_SET = 'GLOBAL_SET',
  18. GLOBAL_DELETE = 'GLOBAL_DELETE',
  19. GLOBAL_OBSERVABLE = 'GLOBAL_OBSERVABLE',
  20. GLOBAL_PRIVATE_UTIL = 'GLOBAL_PRIVATE_UTIL',
  21. CONFIG_SILENT = 'CONFIG_SILENT',
  22. CONFIG_DEVTOOLS = 'CONFIG_DEVTOOLS',
  23. CONFIG_KEY_CODES = 'CONFIG_KEY_CODES',
  24. CONFIG_PRODUCTION_TIP = 'CONFIG_PRODUCTION_TIP',
  25. CONFIG_IGNORED_ELEMENTS = 'CONFIG_IGNORED_ELEMENTS',
  26. CONFIG_WHITESPACE = 'CONFIG_WHITESPACE',
  27. CONFIG_OPTION_MERGE_STRATS = 'CONFIG_OPTION_MERGE_STRATS',
  28. INSTANCE_SET = 'INSTANCE_SET',
  29. INSTANCE_DELETE = 'INSTANCE_DELETE',
  30. INSTANCE_DESTROY = 'INSTANCE_DESTROY',
  31. INSTANCE_EVENT_EMITTER = 'INSTANCE_EVENT_EMITTER',
  32. INSTANCE_EVENT_HOOKS = 'INSTANCE_EVENT_HOOKS',
  33. INSTANCE_CHILDREN = 'INSTANCE_CHILDREN',
  34. INSTANCE_LISTENERS = 'INSTANCE_LISTENERS',
  35. INSTANCE_SCOPED_SLOTS = 'INSTANCE_SCOPED_SLOTS',
  36. INSTANCE_ATTRS_CLASS_STYLE = 'INSTANCE_ATTRS_CLASS_STYLE',
  37. OPTIONS_DATA_FN = 'OPTIONS_DATA_FN',
  38. OPTIONS_DATA_MERGE = 'OPTIONS_DATA_MERGE',
  39. OPTIONS_BEFORE_DESTROY = 'OPTIONS_BEFORE_DESTROY',
  40. OPTIONS_DESTROYED = 'OPTIONS_DESTROYED',
  41. WATCH_ARRAY = 'WATCH_ARRAY',
  42. PROPS_DEFAULT_THIS = 'PROPS_DEFAULT_THIS',
  43. V_ON_KEYCODE_MODIFIER = 'V_ON_KEYCODE_MODIFIER',
  44. CUSTOM_DIR = 'CUSTOM_DIR',
  45. ATTR_FALSE_VALUE = 'ATTR_FALSE_VALUE',
  46. ATTR_ENUMERATED_COERCION = 'ATTR_ENUMERATED_COERCION',
  47. TRANSITION_CLASSES = 'TRANSITION_CLASSES',
  48. TRANSITION_GROUP_ROOT = 'TRANSITION_GROUP_ROOT',
  49. COMPONENT_ASYNC = 'COMPONENT_ASYNC',
  50. COMPONENT_FUNCTIONAL = 'COMPONENT_FUNCTIONAL',
  51. COMPONENT_V_MODEL = 'COMPONENT_V_MODEL',
  52. RENDER_FUNCTION = 'RENDER_FUNCTION',
  53. FILTERS = 'FILTERS',
  54. PRIVATE_APIS = 'PRIVATE_APIS',
  55. }
  56. type DeprecationData = {
  57. message: string | ((...args: any[]) => string)
  58. link?: string
  59. }
  60. export const deprecationData: Record<DeprecationTypes, DeprecationData> = {
  61. [DeprecationTypes.GLOBAL_MOUNT]: {
  62. message:
  63. `The global app bootstrapping API has changed: vm.$mount() and the "el" ` +
  64. `option have been removed. Use createApp(RootComponent).mount() instead.`,
  65. link: `https://v3-migration.vuejs.org/breaking-changes/global-api.html#mounting-app-instance`,
  66. },
  67. [DeprecationTypes.GLOBAL_MOUNT_CONTAINER]: {
  68. message:
  69. `Vue detected directives on the mount container. ` +
  70. `In Vue 3, the container is no longer considered part of the template ` +
  71. `and will not be processed/replaced.`,
  72. link: `https://v3-migration.vuejs.org/breaking-changes/mount-changes.html`,
  73. },
  74. [DeprecationTypes.GLOBAL_EXTEND]: {
  75. message:
  76. `Vue.extend() has been removed in Vue 3. ` +
  77. `Use defineComponent() instead.`,
  78. link: `https://vuejs.org/api/general.html#definecomponent`,
  79. },
  80. [DeprecationTypes.GLOBAL_PROTOTYPE]: {
  81. message:
  82. `Vue.prototype is no longer available in Vue 3. ` +
  83. `Use app.config.globalProperties instead.`,
  84. link: `https://v3-migration.vuejs.org/breaking-changes/global-api.html#vue-prototype-replaced-by-config-globalproperties`,
  85. },
  86. [DeprecationTypes.GLOBAL_SET]: {
  87. message:
  88. `Vue.set() has been removed as it is no longer needed in Vue 3. ` +
  89. `Simply use native JavaScript mutations.`,
  90. },
  91. [DeprecationTypes.GLOBAL_DELETE]: {
  92. message:
  93. `Vue.delete() has been removed as it is no longer needed in Vue 3. ` +
  94. `Simply use native JavaScript mutations.`,
  95. },
  96. [DeprecationTypes.GLOBAL_OBSERVABLE]: {
  97. message:
  98. `Vue.observable() has been removed. ` +
  99. `Use \`import { reactive } from "vue"\` from Composition API instead.`,
  100. link: `https://vuejs.org/api/reactivity-core.html#reactive`,
  101. },
  102. [DeprecationTypes.GLOBAL_PRIVATE_UTIL]: {
  103. message:
  104. `Vue.util has been removed. Please refactor to avoid its usage ` +
  105. `since it was an internal API even in Vue 2.`,
  106. },
  107. [DeprecationTypes.CONFIG_SILENT]: {
  108. message:
  109. `config.silent has been removed because it is not good practice to ` +
  110. `intentionally suppress warnings. You can use your browser console's ` +
  111. `filter features to focus on relevant messages.`,
  112. },
  113. [DeprecationTypes.CONFIG_DEVTOOLS]: {
  114. message:
  115. `config.devtools has been removed. To enable devtools for ` +
  116. `production, configure the __VUE_PROD_DEVTOOLS__ compile-time flag.`,
  117. link: `https://github.com/vuejs/core/tree/main/packages/vue#bundler-build-feature-flags`,
  118. },
  119. [DeprecationTypes.CONFIG_KEY_CODES]: {
  120. message:
  121. `config.keyCodes has been removed. ` +
  122. `In Vue 3, you can directly use the kebab-case key names as v-on modifiers.`,
  123. link: `https://v3-migration.vuejs.org/breaking-changes/keycode-modifiers.html`,
  124. },
  125. [DeprecationTypes.CONFIG_PRODUCTION_TIP]: {
  126. message: `config.productionTip has been removed.`,
  127. link: `https://v3-migration.vuejs.org/breaking-changes/global-api.html#config-productiontip-removed`,
  128. },
  129. [DeprecationTypes.CONFIG_IGNORED_ELEMENTS]: {
  130. message: () => {
  131. let msg = `config.ignoredElements has been removed.`
  132. if (isRuntimeOnly()) {
  133. msg += ` Pass the "isCustomElement" option to @vue/compiler-dom instead.`
  134. } else {
  135. msg += ` Use config.isCustomElement instead.`
  136. }
  137. return msg
  138. },
  139. link: `https://v3-migration.vuejs.org/breaking-changes/global-api.html#config-ignoredelements-is-now-config-iscustomelement`,
  140. },
  141. [DeprecationTypes.CONFIG_WHITESPACE]: {
  142. // this warning is only relevant in the full build when using runtime
  143. // compilation, so it's put in the runtime compatConfig list.
  144. message:
  145. `Vue 3 compiler's whitespace option will default to "condense" instead of ` +
  146. `"preserve". To suppress this warning, provide an explicit value for ` +
  147. `\`config.compilerOptions.whitespace\`.`,
  148. },
  149. [DeprecationTypes.CONFIG_OPTION_MERGE_STRATS]: {
  150. message:
  151. `config.optionMergeStrategies no longer exposes internal strategies. ` +
  152. `Use custom merge functions instead.`,
  153. },
  154. [DeprecationTypes.INSTANCE_SET]: {
  155. message:
  156. `vm.$set() has been removed as it is no longer needed in Vue 3. ` +
  157. `Simply use native JavaScript mutations.`,
  158. },
  159. [DeprecationTypes.INSTANCE_DELETE]: {
  160. message:
  161. `vm.$delete() has been removed as it is no longer needed in Vue 3. ` +
  162. `Simply use native JavaScript mutations.`,
  163. },
  164. [DeprecationTypes.INSTANCE_DESTROY]: {
  165. message: `vm.$destroy() has been removed. Use app.unmount() instead.`,
  166. link: `https://vuejs.org/api/application.html#app-unmount`,
  167. },
  168. [DeprecationTypes.INSTANCE_EVENT_EMITTER]: {
  169. message:
  170. `vm.$on/$once/$off() have been removed. ` +
  171. `Use an external event emitter library instead.`,
  172. link: `https://v3-migration.vuejs.org/breaking-changes/events-api.html`,
  173. },
  174. [DeprecationTypes.INSTANCE_EVENT_HOOKS]: {
  175. message: event =>
  176. `"${event}" lifecycle events are no longer supported. From templates, ` +
  177. `use the "vue:" prefix instead of "hook:". For example, @${event} ` +
  178. `should be changed to @vue:${event.slice(5)}. ` +
  179. `From JavaScript, use Composition API to dynamically register lifecycle ` +
  180. `hooks.`,
  181. link: `https://v3-migration.vuejs.org/breaking-changes/vnode-lifecycle-events.html`,
  182. },
  183. [DeprecationTypes.INSTANCE_CHILDREN]: {
  184. message:
  185. `vm.$children has been removed. Consider refactoring your logic ` +
  186. `to avoid relying on direct access to child components.`,
  187. link: `https://v3-migration.vuejs.org/breaking-changes/children.html`,
  188. },
  189. [DeprecationTypes.INSTANCE_LISTENERS]: {
  190. message:
  191. `vm.$listeners has been removed. In Vue 3, parent v-on listeners are ` +
  192. `included in vm.$attrs and it is no longer necessary to separately use ` +
  193. `v-on="$listeners" if you are already using v-bind="$attrs". ` +
  194. `(Note: the Vue 3 behavior only applies if this compat config is disabled)`,
  195. link: `https://v3-migration.vuejs.org/breaking-changes/listeners-removed.html`,
  196. },
  197. [DeprecationTypes.INSTANCE_SCOPED_SLOTS]: {
  198. message: `vm.$scopedSlots has been removed. Use vm.$slots instead.`,
  199. link: `https://v3-migration.vuejs.org/breaking-changes/slots-unification.html`,
  200. },
  201. [DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE]: {
  202. message: componentName =>
  203. `Component <${
  204. componentName || 'Anonymous'
  205. }> has \`inheritAttrs: false\` but is ` +
  206. `relying on class/style fallthrough from parent. In Vue 3, class/style ` +
  207. `are now included in $attrs and will no longer fallthrough when ` +
  208. `inheritAttrs is false. If you are already using v-bind="$attrs" on ` +
  209. `component root it should render the same end result. ` +
  210. `If you are binding $attrs to a non-root element and expecting ` +
  211. `class/style to fallthrough on root, you will need to now manually bind ` +
  212. `them on root via :class="$attrs.class".`,
  213. link: `https://v3-migration.vuejs.org/breaking-changes/attrs-includes-class-style.html`,
  214. },
  215. [DeprecationTypes.OPTIONS_DATA_FN]: {
  216. message:
  217. `The "data" option can no longer be a plain object. ` +
  218. `Always use a function.`,
  219. link: `https://v3-migration.vuejs.org/breaking-changes/data-option.html`,
  220. },
  221. [DeprecationTypes.OPTIONS_DATA_MERGE]: {
  222. message: (key: string) =>
  223. `Detected conflicting key "${key}" when merging data option values. ` +
  224. `In Vue 3, data keys are merged shallowly and will override one another.`,
  225. link: `https://v3-migration.vuejs.org/breaking-changes/data-option.html#mixin-merge-behavior-change`,
  226. },
  227. [DeprecationTypes.OPTIONS_BEFORE_DESTROY]: {
  228. message: `\`beforeDestroy\` has been renamed to \`beforeUnmount\`.`,
  229. },
  230. [DeprecationTypes.OPTIONS_DESTROYED]: {
  231. message: `\`destroyed\` has been renamed to \`unmounted\`.`,
  232. },
  233. [DeprecationTypes.WATCH_ARRAY]: {
  234. message:
  235. `"watch" option or vm.$watch on an array value will no longer ` +
  236. `trigger on array mutation unless the "deep" option is specified. ` +
  237. `If current usage is intended, you can disable the compat behavior and ` +
  238. `suppress this warning with:` +
  239. `\n\n configureCompat({ ${DeprecationTypes.WATCH_ARRAY}: false })\n`,
  240. link: `https://v3-migration.vuejs.org/breaking-changes/watch.html`,
  241. },
  242. [DeprecationTypes.PROPS_DEFAULT_THIS]: {
  243. message: (key: string) =>
  244. `props default value function no longer has access to "this". The compat ` +
  245. `build only offers access to this.$options.` +
  246. `(found in prop "${key}")`,
  247. link: `https://v3-migration.vuejs.org/breaking-changes/props-default-this.html`,
  248. },
  249. [DeprecationTypes.CUSTOM_DIR]: {
  250. message: (legacyHook: string, newHook: string) =>
  251. `Custom directive hook "${legacyHook}" has been removed. ` +
  252. `Use "${newHook}" instead.`,
  253. link: `https://v3-migration.vuejs.org/breaking-changes/custom-directives.html`,
  254. },
  255. [DeprecationTypes.V_ON_KEYCODE_MODIFIER]: {
  256. message:
  257. `Using keyCode as v-on modifier is no longer supported. ` +
  258. `Use kebab-case key name modifiers instead.`,
  259. link: `https://v3-migration.vuejs.org/breaking-changes/keycode-modifiers.html`,
  260. },
  261. [DeprecationTypes.ATTR_FALSE_VALUE]: {
  262. message: (name: string) =>
  263. `Attribute "${name}" with v-bind value \`false\` will render ` +
  264. `${name}="false" instead of removing it in Vue 3. To remove the attribute, ` +
  265. `use \`null\` or \`undefined\` instead. If the usage is intended, ` +
  266. `you can disable the compat behavior and suppress this warning with:` +
  267. `\n\n configureCompat({ ${DeprecationTypes.ATTR_FALSE_VALUE}: false })\n`,
  268. link: `https://v3-migration.vuejs.org/breaking-changes/attribute-coercion.html`,
  269. },
  270. [DeprecationTypes.ATTR_ENUMERATED_COERCION]: {
  271. message: (name: string, value: any, coerced: string) =>
  272. `Enumerated attribute "${name}" with v-bind value \`${value}\` will ` +
  273. `${
  274. value === null ? `be removed` : `render the value as-is`
  275. } instead of coercing the value to "${coerced}" in Vue 3. ` +
  276. `Always use explicit "true" or "false" values for enumerated attributes. ` +
  277. `If the usage is intended, ` +
  278. `you can disable the compat behavior and suppress this warning with:` +
  279. `\n\n configureCompat({ ${DeprecationTypes.ATTR_ENUMERATED_COERCION}: false })\n`,
  280. link: `https://v3-migration.vuejs.org/breaking-changes/attribute-coercion.html`,
  281. },
  282. [DeprecationTypes.TRANSITION_CLASSES]: {
  283. message: ``, // this feature cannot be runtime-detected
  284. },
  285. [DeprecationTypes.TRANSITION_GROUP_ROOT]: {
  286. message:
  287. `<TransitionGroup> no longer renders a root <span> element by ` +
  288. `default if no "tag" prop is specified. If you do not rely on the span ` +
  289. `for styling, you can disable the compat behavior and suppress this ` +
  290. `warning with:` +
  291. `\n\n configureCompat({ ${DeprecationTypes.TRANSITION_GROUP_ROOT}: false })\n`,
  292. link: `https://v3-migration.vuejs.org/breaking-changes/transition-group.html`,
  293. },
  294. [DeprecationTypes.COMPONENT_ASYNC]: {
  295. message: (comp: any) => {
  296. const name = getComponentName(comp)
  297. return (
  298. `Async component${
  299. name ? ` <${name}>` : `s`
  300. } should be explicitly created via \`defineAsyncComponent()\` ` +
  301. `in Vue 3. Plain functions will be treated as functional components in ` +
  302. `non-compat build. If you have already migrated all async component ` +
  303. `usage and intend to use plain functions for functional components, ` +
  304. `you can disable the compat behavior and suppress this ` +
  305. `warning with:` +
  306. `\n\n configureCompat({ ${DeprecationTypes.COMPONENT_ASYNC}: false })\n`
  307. )
  308. },
  309. link: `https://v3-migration.vuejs.org/breaking-changes/async-components.html`,
  310. },
  311. [DeprecationTypes.COMPONENT_FUNCTIONAL]: {
  312. message: (comp: any) => {
  313. const name = getComponentName(comp)
  314. return (
  315. `Functional component${
  316. name ? ` <${name}>` : `s`
  317. } should be defined as a plain function in Vue 3. The "functional" ` +
  318. `option has been removed. NOTE: Before migrating to use plain ` +
  319. `functions for functional components, first make sure that all async ` +
  320. `components usage have been migrated and its compat behavior has ` +
  321. `been disabled.`
  322. )
  323. },
  324. link: `https://v3-migration.vuejs.org/breaking-changes/functional-components.html`,
  325. },
  326. [DeprecationTypes.COMPONENT_V_MODEL]: {
  327. message: (comp: ComponentOptions) => {
  328. const configMsg =
  329. `opt-in to ` +
  330. `Vue 3 behavior on a per-component basis with \`compatConfig: { ${DeprecationTypes.COMPONENT_V_MODEL}: false }\`.`
  331. if (
  332. comp.props &&
  333. (isArray(comp.props)
  334. ? comp.props.includes('modelValue')
  335. : hasOwn(comp.props, 'modelValue'))
  336. ) {
  337. return (
  338. `Component declares "modelValue" prop, which is Vue 3 usage, but ` +
  339. `is running under Vue 2 compat v-model behavior. You can ${configMsg}`
  340. )
  341. }
  342. return (
  343. `v-model usage on component has changed in Vue 3. Component that expects ` +
  344. `to work with v-model should now use the "modelValue" prop and emit the ` +
  345. `"update:modelValue" event. You can update the usage and then ${configMsg}`
  346. )
  347. },
  348. link: `https://v3-migration.vuejs.org/breaking-changes/v-model.html`,
  349. },
  350. [DeprecationTypes.RENDER_FUNCTION]: {
  351. message:
  352. `Vue 3's render function API has changed. ` +
  353. `You can opt-in to the new API with:` +
  354. `\n\n configureCompat({ ${DeprecationTypes.RENDER_FUNCTION}: false })\n` +
  355. `\n (This can also be done per-component via the "compatConfig" option.)`,
  356. link: `https://v3-migration.vuejs.org/breaking-changes/render-function-api.html`,
  357. },
  358. [DeprecationTypes.FILTERS]: {
  359. message:
  360. `filters have been removed in Vue 3. ` +
  361. `The "|" symbol will be treated as native JavaScript bitwise OR operator. ` +
  362. `Use method calls or computed properties instead.`,
  363. link: `https://v3-migration.vuejs.org/breaking-changes/filters.html`,
  364. },
  365. [DeprecationTypes.PRIVATE_APIS]: {
  366. message: name =>
  367. `"${name}" is a Vue 2 private API that no longer exists in Vue 3. ` +
  368. `If you are seeing this warning only due to a dependency, you can ` +
  369. `suppress this warning via { PRIVATE_APIS: 'suppress-warning' }.`,
  370. },
  371. }
  372. const instanceWarned: Record<string, true> = Object.create(null)
  373. const warnCount: Record<string, number> = Object.create(null)
  374. // test only
  375. let warningEnabled = true
  376. export function toggleDeprecationWarning(flag: boolean): void {
  377. warningEnabled = flag
  378. }
  379. export function warnDeprecation(
  380. key: DeprecationTypes,
  381. instance: ComponentInternalInstance | null,
  382. ...args: any[]
  383. ): void {
  384. if (!__DEV__) {
  385. return
  386. }
  387. if (__TEST__ && !warningEnabled) {
  388. return
  389. }
  390. instance = instance || getCurrentInstance()
  391. // check user config
  392. const config = getCompatConfigForKey(key, instance)
  393. if (config === 'suppress-warning') {
  394. return
  395. }
  396. const dupKey = key + args.join('')
  397. let compId: string | number | null =
  398. instance && formatComponentName(instance, instance.type)
  399. if (compId === 'Anonymous' && instance) {
  400. compId = instance.uid
  401. }
  402. // skip if the same warning is emitted for the same component type
  403. const componentDupKey = dupKey + compId
  404. if (!__TEST__ && componentDupKey in instanceWarned) {
  405. return
  406. }
  407. instanceWarned[componentDupKey] = true
  408. // same warning, but different component. skip the long message and just
  409. // log the key and count.
  410. if (!__TEST__ && dupKey in warnCount) {
  411. warn(`(deprecation ${key}) (${++warnCount[dupKey] + 1})`)
  412. return
  413. }
  414. warnCount[dupKey] = 0
  415. const { message, link } = deprecationData[key]
  416. warn(
  417. `(deprecation ${key}) ${
  418. typeof message === 'function' ? message(...args) : message
  419. }${link ? `\n Details: ${link}` : ``}`,
  420. )
  421. if (!isCompatEnabled(key, instance, true)) {
  422. console.error(
  423. `^ The above deprecation's compat behavior is disabled and will likely ` +
  424. `lead to runtime errors.`,
  425. )
  426. }
  427. }
  428. export type CompatConfig = Partial<
  429. Record<DeprecationTypes, boolean | 'suppress-warning'>
  430. > & {
  431. MODE?: 2 | 3 | ((comp: Component | null) => 2 | 3)
  432. }
  433. export const globalCompatConfig: CompatConfig = {
  434. MODE: 2,
  435. }
  436. export function configureCompat(config: CompatConfig): void {
  437. if (__DEV__) {
  438. validateCompatConfig(config)
  439. }
  440. extend(globalCompatConfig, config)
  441. }
  442. const seenConfigObjects = /*@__PURE__*/ new WeakSet<CompatConfig>()
  443. const warnedInvalidKeys: Record<string, boolean> = {}
  444. // dev only
  445. export function validateCompatConfig(
  446. config: CompatConfig,
  447. instance?: ComponentInternalInstance,
  448. ): void {
  449. if (seenConfigObjects.has(config)) {
  450. return
  451. }
  452. seenConfigObjects.add(config)
  453. for (const key of Object.keys(config)) {
  454. if (
  455. key !== 'MODE' &&
  456. !(key in deprecationData) &&
  457. !(key in warnedInvalidKeys)
  458. ) {
  459. if (key.startsWith('COMPILER_')) {
  460. if (isRuntimeOnly()) {
  461. warn(
  462. `Deprecation config "${key}" is compiler-specific and you are ` +
  463. `running a runtime-only build of Vue. This deprecation should be ` +
  464. `configured via compiler options in your build setup instead.\n` +
  465. `Details: https://v3-migration.vuejs.org/breaking-changes/migration-build.html`,
  466. )
  467. }
  468. } else {
  469. warn(`Invalid deprecation config "${key}".`)
  470. }
  471. warnedInvalidKeys[key] = true
  472. }
  473. }
  474. if (instance && config[DeprecationTypes.OPTIONS_DATA_MERGE] != null) {
  475. warn(
  476. `Deprecation config "${DeprecationTypes.OPTIONS_DATA_MERGE}" can only be configured globally.`,
  477. )
  478. }
  479. }
  480. export function getCompatConfigForKey(
  481. key: DeprecationTypes | 'MODE',
  482. instance: ComponentInternalInstance | null,
  483. ): CompatConfig[DeprecationTypes | 'MODE'] {
  484. const instanceConfig =
  485. instance && (instance.type as ComponentOptions).compatConfig
  486. if (instanceConfig && key in instanceConfig) {
  487. return instanceConfig[key]
  488. }
  489. return globalCompatConfig[key]
  490. }
  491. export function isCompatEnabled(
  492. key: DeprecationTypes,
  493. instance: ComponentInternalInstance | null,
  494. enableForBuiltIn = false,
  495. ): boolean {
  496. // skip compat for built-in components
  497. if (!enableForBuiltIn && instance && instance.type.__isBuiltIn) {
  498. return false
  499. }
  500. const rawMode = getCompatConfigForKey('MODE', instance) || 2
  501. const val = getCompatConfigForKey(key, instance)
  502. const mode = isFunction(rawMode)
  503. ? rawMode(instance && instance.type)
  504. : rawMode
  505. if (mode === 2) {
  506. return val !== false
  507. } else {
  508. return val === true || val === 'suppress-warning'
  509. }
  510. }
  511. /**
  512. * Use this for features that are completely removed in non-compat build.
  513. */
  514. export function assertCompatEnabled(
  515. key: DeprecationTypes,
  516. instance: ComponentInternalInstance | null,
  517. ...args: any[]
  518. ): void {
  519. if (!isCompatEnabled(key, instance)) {
  520. throw new Error(`${key} compat has been disabled.`)
  521. } else if (__DEV__) {
  522. warnDeprecation(key, instance, ...args)
  523. }
  524. }
  525. /**
  526. * Use this for features where legacy usage is still possible, but will likely
  527. * lead to runtime error if compat is disabled. (warn in all cases)
  528. */
  529. export function softAssertCompatEnabled(
  530. key: DeprecationTypes,
  531. instance: ComponentInternalInstance | null,
  532. ...args: any[]
  533. ): boolean {
  534. if (__DEV__) {
  535. warnDeprecation(key, instance, ...args)
  536. }
  537. return isCompatEnabled(key, instance)
  538. }
  539. /**
  540. * Use this for features with the same syntax but with mutually exclusive
  541. * behavior in 2 vs 3. Only warn if compat is enabled.
  542. * e.g. render function
  543. */
  544. export function checkCompatEnabled(
  545. key: DeprecationTypes,
  546. instance: ComponentInternalInstance | null,
  547. ...args: any[]
  548. ): boolean {
  549. const enabled = isCompatEnabled(key, instance)
  550. if (__DEV__ && enabled) {
  551. warnDeprecation(key, instance, ...args)
  552. }
  553. return enabled
  554. }
  555. // run tests in v3 mode by default
  556. if (__TEST__) {
  557. configureCompat({
  558. MODE: 3,
  559. })
  560. }