From c1e5cfe7d636d3f7d8b71df530d4065e779e8868 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 7 Apr 2021 17:36:56 -0400 Subject: [PATCH] wip: attr coersion compat --- .../runtime-core/src/compat/deprecations.ts | 59 ++++++++++++++----- packages/runtime-dom/src/modules/attrs.ts | 47 +++++++++++++++ 2 files changed, 92 insertions(+), 14 deletions(-) diff --git a/packages/runtime-core/src/compat/deprecations.ts b/packages/runtime-core/src/compat/deprecations.ts index ff43c44a..7173cbdc 100644 --- a/packages/runtime-core/src/compat/deprecations.ts +++ b/packages/runtime-core/src/compat/deprecations.ts @@ -29,10 +29,14 @@ export const enum DeprecationTypes { OPTIONS_BEFORE_DESTROY = 'OPTIONS_BEFORE_DESTROY', OPTIONS_DESTROYED = 'OPTIONS_DESTROYED', - V_ON_KEYCODE_MODIFIER = 'V_ON_KEYCODE_MODIFIER', - PROPS_DEFAULT_THIS = 'PROPS_DEFAULT_THIS', - CUSTOM_DIR = 'CUSTOM_DIR', WATCH_ARRAY = 'WATCH_ARRAY', + PROPS_DEFAULT_THIS = 'PROPS_DEFAULT_THIS', + + V_ON_KEYCODE_MODIFIER = 'V_ON_KEYCODE_MODIFIER', + CUSTOM_DIR = 'CUSTOM_DIR', + + ATTR_FALSE_VALUE = 'ATTR_FALSE_VALUE', + ATTR_ENUMERATED_COERSION = 'ATTR_ENUMERATED_COERSION', TRANSITION_CLASSES = 'TRANSITION_CLASSES', TRANSITION_GROUP_ROOT = 'TRANSITION_GROUP_ROOT' @@ -191,11 +195,16 @@ const deprecationData: Record = { message: `\`destroyed\` has been renamed to \`unmounted\`.` }, - [DeprecationTypes.V_ON_KEYCODE_MODIFIER]: { + [DeprecationTypes.WATCH_ARRAY]: { message: - `Using keyCode as v-on modifier is no longer supported. ` + - `Use kebab-case key name modifiers instead.`, - link: `https://v3.vuejs.org/guide/migration/keycode-modifiers.html` + `"watch" option or vm.$watch on an array value will no longer ` + + `trigger on array mutation unless the "deep" option is specified. ` + + `If current usage is intended, you can disable the compat behavior and ` + + `suppress this warning with:` + + `\n\n configureCompat({ ${ + DeprecationTypes.WATCH_ARRAY + }: { enabled: false }})\n`, + link: `https://v3.vuejs.org/guide/migration/watch.html` }, [DeprecationTypes.PROPS_DEFAULT_THIS]: { @@ -212,16 +221,38 @@ const deprecationData: Record = { link: `https://v3.vuejs.org/guide/migration/custom-directives.html` }, - [DeprecationTypes.WATCH_ARRAY]: { + [DeprecationTypes.V_ON_KEYCODE_MODIFIER]: { message: - `"watch" option or vm.$watch on an array value will no longer ` + - `trigger on array mutation unless the "deep" option is specified. ` + - `If current usage is intended, you can disable the compat behavior and ` + - `suppress this warning with:` + + `Using keyCode as v-on modifier is no longer supported. ` + + `Use kebab-case key name modifiers instead.`, + link: `https://v3.vuejs.org/guide/migration/keycode-modifiers.html` + }, + + [DeprecationTypes.ATTR_FALSE_VALUE]: { + message: (name: string) => + `Attribute "${name}" with v-bind value \`false\` will render ` + + `${name}="false" instead of removing it in Vue 3. To remove the attribute, ` + + `use \`null\` or \`undefined\` instead. If the usage is intended, ` + + `you can disable the compat behavior and suppress this warning with:` + `\n\n configureCompat({ ${ - DeprecationTypes.WATCH_ARRAY + DeprecationTypes.ATTR_FALSE_VALUE }: { enabled: false }})\n`, - link: `https://v3.vuejs.org/guide/migration/watch.html` + link: `https://v3.vuejs.org/guide/migration/attribute-coercion.html` + }, + + [DeprecationTypes.ATTR_ENUMERATED_COERSION]: { + message: (name: string, value: any, coerced: string) => + `Enumerated attribute "${name}" with v-bind value \`${value}\` will ` + + `${ + value === null ? `be removed` : `render the value as-is` + } instead of coercing the value to "${coerced}" in Vue 3. ` + + `Always use explicit "true" or "false" values for enumerated attributes. ` + + `If the usage is intended, ` + + `you can disable the compat behavior and suppress this warning with:` + + `\n\n configureCompat({ ${ + DeprecationTypes.ATTR_ENUMERATED_COERSION + }: { enabled: false }})\n`, + link: `https://v3.vuejs.org/guide/migration/attribute-coercion.html` }, [DeprecationTypes.TRANSITION_CLASSES]: { diff --git a/packages/runtime-dom/src/modules/attrs.ts b/packages/runtime-dom/src/modules/attrs.ts index 3a33d713..975f6cbe 100644 --- a/packages/runtime-dom/src/modules/attrs.ts +++ b/packages/runtime-dom/src/modules/attrs.ts @@ -15,6 +15,10 @@ export function patchAttr( el.setAttributeNS(xlinkNS, key, value) } } else { + if (__COMPAT__ && compatCoerceAttr(el, key, value)) { + return + } + // note we are only checking boolean attributes that don't have a // corresponding dom prop of the same name here. const isBoolean = isSpecialBooleanAttr(key) @@ -25,3 +29,46 @@ export function patchAttr( } } } + +// 2.x compat +import { makeMap, NOOP } from '@vue/shared' +import { compatUtils, DeprecationTypes } from '@vue/runtime-core' + +const isEnumeratedAttr = __COMPAT__ + ? /*#__PURE__*/ makeMap('contenteditable,draggable,spellcheck') + : NOOP + +export function compatCoerceAttr( + el: Element, + key: string, + value: unknown +): boolean { + if (isEnumeratedAttr(key)) { + const v2CocercedValue = + value === null + ? 'false' + : typeof value !== 'boolean' && value !== undefined + ? 'true' + : null + if ( + v2CocercedValue && + compatUtils.softAssertCompatEnabled( + DeprecationTypes.ATTR_ENUMERATED_COERSION, + key, + value, + v2CocercedValue + ) + ) { + el.setAttribute(key, v2CocercedValue) + return true + } + } else if ( + value === false && + !isSpecialBooleanAttr(key) && + compatUtils.softAssertCompatEnabled(DeprecationTypes.ATTR_FALSE_VALUE, key) + ) { + el.removeAttribute(key) + return true + } + return false +}