diff --git a/packages/runtime-dom/src/modules/attrs.ts b/packages/runtime-dom/src/modules/attrs.ts index 5610d327f4f..2a6c5cc769a 100644 --- a/packages/runtime-dom/src/modules/attrs.ts +++ b/packages/runtime-dom/src/modules/attrs.ts @@ -1,4 +1,6 @@ -// TODO explain why we are no longer checking boolean/enumerated here +import { isSpecialBooleanAttr } from '@vue/shared' + +const xlinkNS = 'http://www.w3.org/1999/xlink' export function patchAttr( el: Element, @@ -7,12 +9,19 @@ export function patchAttr( isSVG: boolean ) { if (isSVG && key.indexOf('xlink:') === 0) { - // TODO handle xlink - } else if (value == null) { - el.removeAttribute(key) + if (value == null) { + el.removeAttributeNS(xlinkNS, key) + } else { + el.setAttributeNS(xlinkNS, key, value) + } } else { - // TODO in dev mode, warn against incorrect values for boolean or - // enumerated attributes - el.setAttribute(key, value) + // note we are only checking boolean attributes that don't have a + // correspoding dom prop of the same name here. + const isBoolean = isSpecialBooleanAttr(key) + if (value == null || (isBoolean && value === false)) { + el.removeAttribute(key) + } else { + el.setAttribute(key, isBoolean ? '' : value) + } } } diff --git a/packages/server-renderer/src/renderProps.ts b/packages/server-renderer/src/renderProps.ts index 97cb53c3c85..ce0abf2b548 100644 --- a/packages/server-renderer/src/renderProps.ts +++ b/packages/server-renderer/src/renderProps.ts @@ -30,7 +30,9 @@ export function renderProps( ? key : propsToAttrMap[key] || key.toLowerCase() if (isBooleanAttr(attrKey)) { - ret += ` ${attrKey}=""` + if (value !== false) { + ret += ` ${attrKey}` + } } else if (isSSRSafeAttrName(attrKey)) { ret += ` ${attrKey}="${escape(value)}"` } diff --git a/packages/shared/src/domAttrConfig.ts b/packages/shared/src/domAttrConfig.ts index db8fc321dbd..0187ea28911 100644 --- a/packages/shared/src/domAttrConfig.ts +++ b/packages/shared/src/domAttrConfig.ts @@ -1,18 +1,23 @@ import { makeMap } from './makeMap' -// TODO validate this list! -// on the client, most of these probably has corresponding prop -// or, like allowFullscreen on iframe, although case is different, the attr -// affects the property properly... -// Basically, we can skip this check on the client -// but they are still needed during SSR to produce correct initial markup -export const isBooleanAttr = makeMap( - 'allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,' + - 'default,defaultchecked,defaultmuted,defaultselected,defer,disabled,' + - 'enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,' + - 'muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,' + - 'required,reversed,scoped,seamless,selected,sortable,translate,' + - 'truespeed,typemustmatch,visible' +// On the client we only need to offer special cases for boolean attributes that +// have different names from their corresponding dom properties: +// - itemscope -> N/A +// - allowfullscreen -> allowFullscreen +// - formnovalidate -> formNoValidate +// - ismap -> isMap +// - nomodule -> noModule +// - novalidate -> noValidate +// - readonly -> readOnly +const specialBooleanAttrs = `itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly` +export const isSpecialBooleanAttr = /*#__PURE__*/ makeMap(specialBooleanAttrs) + +// The full list is needed during SSR to produce the correct initial markup. +export const isBooleanAttr = /*#__PURE__*/ makeMap( + specialBooleanAttrs + + `,async,autofocus,autoplay,controls,default,defer,disabled,hidden,ismap,` + + `loop,nomodule,open,required,reversed,scoped,seamless,` + + `checked,muted,multiple,selected` ) const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u0020]/ @@ -37,7 +42,7 @@ export const propsToAttrMap: Record = { } // CSS properties that accept plain numbers -export const isNoUnitNumericStyleProp = makeMap( +export const isNoUnitNumericStyleProp = /*#__PURE__*/ makeMap( `animation-iteration-count,border-image-outset,border-image-slice,` + `border-image-width,box-flex,box-flex-group,box-ordinal-group,column-count,` + `columns,flex,flex-grow,flex-positive,flex-shrink,flex-negative,flex-order,` +