mirror of https://github.com/vuejs/core.git
perf(runtime-vapor): use `setAttr` or `setDOMProp` instead of `setDynamicProp` when possible (#291)
Co-authored-by: Doctor Wu <doctorwu@moego.pet>
This commit is contained in:
parent
9a2158d2f0
commit
e61cedf3fd
|
@ -151,7 +151,7 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
|
|||
`;
|
||||
|
||||
exports[`compile > directives > v-pre > should not affect siblings after it 1`] = `
|
||||
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createTextNode as _createTextNode, insert as _insert, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
|
||||
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createTextNode as _createTextNode, insert as _insert, renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
|
||||
const t0 = _template("<div :id=\\"foo\\"><Comp></Comp>{{ bar }}</div>")
|
||||
const t1 = _template("<div></div>")
|
||||
|
||||
|
@ -162,7 +162,7 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
|
|||
const n1 = _createComponent(_component_Comp)
|
||||
const n2 = _createTextNode(() => [_ctx.bar])
|
||||
_insert([n1, n2], n3)
|
||||
_renderEffect(() => _setDynamicProp(n3, "id", _ctx.foo))
|
||||
_renderEffect(() => _setDOMProp(n3, "id", _ctx.foo))
|
||||
return [n0, n3]
|
||||
}"
|
||||
`;
|
||||
|
@ -177,7 +177,7 @@ export function render(_ctx) {
|
|||
`;
|
||||
|
||||
exports[`compile > dynamic root nodes and interpolation 1`] = `
|
||||
"import { delegate as _delegate, setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setText as _setText, setDynamicProp as _setDynamicProp, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
|
||||
"import { delegate as _delegate, setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setText as _setText, setDOMProp as _setDOMProp, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
|
||||
const t0 = _template("<button></button>")
|
||||
_delegateEvents("click")
|
||||
|
||||
|
@ -186,7 +186,7 @@ export function render(_ctx) {
|
|||
_delegate(n0, "click", () => _ctx.handleClick)
|
||||
_setInheritAttrs(["id"])
|
||||
_renderEffect(() => _setText(n0, _ctx.count, "foo", _ctx.count, "foo", _ctx.count))
|
||||
_renderEffect(() => _setDynamicProp(n0, "id", _ctx.count, true))
|
||||
_renderEffect(() => _setDOMProp(n0, "id", _ctx.count, true))
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -121,14 +121,100 @@ export function render(_ctx) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler v-bind > HTML global attributes should set as dom prop 1`] = `
|
||||
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
|
||||
const t0 = _template("<div></div>")
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = t0()
|
||||
_setInheritAttrs(["id", "title", "lang", "dir", "tabindex"])
|
||||
_renderEffect(() => _setDOMProp(n0, "id", _ctx.id, true))
|
||||
_renderEffect(() => _setDOMProp(n0, "title", _ctx.title, true))
|
||||
_renderEffect(() => _setDOMProp(n0, "lang", _ctx.lang, true))
|
||||
_renderEffect(() => _setDOMProp(n0, "dir", _ctx.dir, true))
|
||||
_renderEffect(() => _setDOMProp(n0, "tabindex", _ctx.tabindex, true))
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler v-bind > MathML global attributes should set as dom prop 1`] = `
|
||||
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
|
||||
const t0 = _template("<math></math>")
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = t0()
|
||||
_setInheritAttrs(["autofucus", "dir", "displaystyle", "mathcolor", "tabindex"])
|
||||
_renderEffect(() => _setDOMProp(n0, "autofucus", _ctx.autofucus, true))
|
||||
_renderEffect(() => _setDOMProp(n0, "dir", _ctx.dir, true))
|
||||
_renderEffect(() => _setDOMProp(n0, "displaystyle", _ctx.displaystyle, true))
|
||||
_renderEffect(() => _setDOMProp(n0, "mathcolor", _ctx.mathcolor, true))
|
||||
_renderEffect(() => _setDOMProp(n0, "tabindex", _ctx.tabindex, true))
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler v-bind > SVG global attributes should set as dom prop 1`] = `
|
||||
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
|
||||
const t0 = _template("<svg></svg>")
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = t0()
|
||||
_setInheritAttrs(["id", "lang", "tabindex"])
|
||||
_renderEffect(() => _setDOMProp(n0, "id", _ctx.id, true))
|
||||
_renderEffect(() => _setDOMProp(n0, "lang", _ctx.lang, true))
|
||||
_renderEffect(() => _setDOMProp(n0, "tabindex", _ctx.tabindex, true))
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler v-bind > attributes must be set as attribute 1`] = `
|
||||
"import { renderEffect as _renderEffect, setAttr as _setAttr, template as _template } from 'vue/vapor';
|
||||
const t0 = _template("<div></div>")
|
||||
const t1 = _template("<input>")
|
||||
const t2 = _template("<textarea></textarea>")
|
||||
const t3 = _template("<img>")
|
||||
const t4 = _template("<video></video>")
|
||||
const t5 = _template("<canvas></canvas>")
|
||||
const t6 = _template("<source>")
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = t0()
|
||||
const n1 = t1()
|
||||
const n2 = t2()
|
||||
const n3 = t3()
|
||||
const n4 = t4()
|
||||
const n5 = t5()
|
||||
const n6 = t6()
|
||||
_renderEffect(() => _setAttr(n0, "spellcheck", _ctx.spellcheck))
|
||||
_renderEffect(() => _setAttr(n0, "draggable", _ctx.draggable))
|
||||
_renderEffect(() => _setAttr(n0, "translate", _ctx.translate))
|
||||
_renderEffect(() => _setAttr(n0, "form", _ctx.form))
|
||||
_renderEffect(() => _setAttr(n1, "list", _ctx.list))
|
||||
_renderEffect(() => _setAttr(n2, "type", _ctx.type))
|
||||
_renderEffect(() => {
|
||||
_setAttr(n3, "width", _ctx.width)
|
||||
_setAttr(n4, "width", _ctx.width)
|
||||
_setAttr(n5, "width", _ctx.width)
|
||||
_setAttr(n6, "width", _ctx.width)
|
||||
})
|
||||
_renderEffect(() => {
|
||||
_setAttr(n3, "height", _ctx.height)
|
||||
_setAttr(n4, "height", _ctx.height)
|
||||
_setAttr(n5, "height", _ctx.height)
|
||||
_setAttr(n6, "height", _ctx.height)
|
||||
})
|
||||
return [n0, n1, n2, n3, n4, n5, n6]
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler v-bind > basic 1`] = `
|
||||
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
|
||||
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
|
||||
const t0 = _template("<div></div>")
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = t0()
|
||||
_setInheritAttrs(["id"])
|
||||
_renderEffect(() => _setDynamicProp(n0, "id", _ctx.id, true))
|
||||
_renderEffect(() => _setDOMProp(n0, "id", _ctx.id, true))
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
@ -170,13 +256,13 @@ export function render(_ctx) {
|
|||
`;
|
||||
|
||||
exports[`compiler v-bind > no expression 1`] = `
|
||||
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
|
||||
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
|
||||
const t0 = _template("<div></div>")
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = t0()
|
||||
_setInheritAttrs(["id"])
|
||||
_renderEffect(() => _setDynamicProp(n0, "id", _ctx.id, true))
|
||||
_renderEffect(() => _setDOMProp(n0, "id", _ctx.id, true))
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`compiler: v-once > as root node 1`] = `
|
||||
"import { setDynamicProp as _setDynamicProp, setInheritAttrs as _setInheritAttrs, template as _template } from 'vue/vapor';
|
||||
"import { setDOMProp as _setDOMProp, setInheritAttrs as _setInheritAttrs, template as _template } from 'vue/vapor';
|
||||
const t0 = _template("<div></div>")
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = t0()
|
||||
_setDynamicProp(n0, "id", _ctx.foo, true)
|
||||
_setDOMProp(n0, "id", _ctx.foo, true)
|
||||
_setInheritAttrs(["id"])
|
||||
return n0
|
||||
}"
|
||||
|
@ -52,13 +52,13 @@ export function render(_ctx) {
|
|||
`;
|
||||
|
||||
exports[`compiler: v-once > on nested plain element 1`] = `
|
||||
"import { setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
|
||||
"import { setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
|
||||
const t0 = _template("<div><div></div></div>")
|
||||
|
||||
export function render(_ctx) {
|
||||
const n1 = t0()
|
||||
const n0 = n1.firstChild
|
||||
_setDynamicProp(n0, "id", _ctx.foo)
|
||||
_setDOMProp(n0, "id", _ctx.foo)
|
||||
return n1
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -74,7 +74,7 @@ describe('compiler v-bind', () => {
|
|||
})
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(code).contains('_setDynamicProp(n0, "id", _ctx.id, true)')
|
||||
expect(code).contains('_setDOMProp(n0, "id", _ctx.id, true)')
|
||||
})
|
||||
|
||||
test('no expression', () => {
|
||||
|
@ -104,7 +104,7 @@ describe('compiler v-bind', () => {
|
|||
],
|
||||
},
|
||||
})
|
||||
expect(code).contains('_setDynamicProp(n0, "id", _ctx.id, true)')
|
||||
expect(code).contains('_setDOMProp(n0, "id", _ctx.id, true)')
|
||||
})
|
||||
|
||||
test('no expression (shorthand)', () => {
|
||||
|
@ -527,6 +527,73 @@ describe('compiler v-bind', () => {
|
|||
expect(code).contains('_setAttr(n0, "foo-bar", _ctx.fooBar, true)')
|
||||
})
|
||||
|
||||
test('attributes must be set as attribute', () => {
|
||||
const { code } = compileWithVBind(`
|
||||
<div :spellcheck :draggable :translate :form />
|
||||
<input :list="list" />
|
||||
<textarea :type="type" />
|
||||
<img :width="width" :height="height"/>
|
||||
<video :width="width" :height="height"/>
|
||||
<canvas :width="width" :height="height"/>
|
||||
<source :width="width" :height="height"/>
|
||||
`)
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(code).contains('_setAttr(n0, "spellcheck", _ctx.spellcheck)')
|
||||
expect(code).contains('_setAttr(n0, "draggable", _ctx.draggable)')
|
||||
expect(code).contains('_setAttr(n0, "translate", _ctx.translate)')
|
||||
expect(code).contains('_setAttr(n0, "form", _ctx.form)')
|
||||
expect(code).contains('_setAttr(n1, "list", _ctx.list)')
|
||||
expect(code).contains('_setAttr(n2, "type", _ctx.type)')
|
||||
expect(code).contains('_setAttr(n3, "width", _ctx.width)')
|
||||
expect(code).contains('_setAttr(n3, "height", _ctx.height)')
|
||||
expect(code).contains('_setAttr(n4, "width", _ctx.width)')
|
||||
expect(code).contains('_setAttr(n4, "height", _ctx.height)')
|
||||
expect(code).contains('_setAttr(n5, "width", _ctx.width)')
|
||||
expect(code).contains('_setAttr(n5, "height", _ctx.height)')
|
||||
expect(code).contains('_setAttr(n6, "width", _ctx.width)')
|
||||
expect(code).contains('_setAttr(n6, "height", _ctx.height)')
|
||||
})
|
||||
|
||||
test('HTML global attributes should set as dom prop', () => {
|
||||
const { code } = compileWithVBind(`
|
||||
<div :id="id" :title="title" :lang="lang" :dir="dir" :tabindex="tabindex" />
|
||||
`)
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(code).contains('_setDOMProp(n0, "id", _ctx.id, true)')
|
||||
expect(code).contains('_setDOMProp(n0, "title", _ctx.title, true)')
|
||||
expect(code).contains('_setDOMProp(n0, "lang", _ctx.lang, true)')
|
||||
expect(code).contains('_setDOMProp(n0, "dir", _ctx.dir, true)')
|
||||
expect(code).contains('_setDOMProp(n0, "tabindex", _ctx.tabindex, true)')
|
||||
})
|
||||
|
||||
test('SVG global attributes should set as dom prop', () => {
|
||||
const { code } = compileWithVBind(`
|
||||
<svg :id="id" :lang="lang" :tabindex="tabindex" />
|
||||
`)
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(code).contains('_setDOMProp(n0, "id", _ctx.id, true)')
|
||||
expect(code).contains('_setDOMProp(n0, "lang", _ctx.lang, true)')
|
||||
expect(code).contains('_setDOMProp(n0, "tabindex", _ctx.tabindex, true)')
|
||||
})
|
||||
|
||||
test('MathML global attributes should set as dom prop', () => {
|
||||
const { code } = compileWithVBind(`
|
||||
<math :autofucus :dir :displaystyle :mathcolor :tabindex/>
|
||||
`)
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(code).contains('_setDOMProp(n0, "autofucus", _ctx.autofucus, true)')
|
||||
expect(code).contains('_setDOMProp(n0, "dir", _ctx.dir, true)')
|
||||
expect(code).contains(
|
||||
'_setDOMProp(n0, "displaystyle", _ctx.displaystyle, true)',
|
||||
)
|
||||
expect(code).contains('_setDOMProp(n0, "mathcolor", _ctx.mathcolor, true)')
|
||||
expect(code).contains('_setDOMProp(n0, "tabindex", _ctx.tabindex, true)')
|
||||
})
|
||||
|
||||
test('number value', () => {
|
||||
const { code } = compileWithVBind(`<Comp :depth="0" />`)
|
||||
expect(code).matchSnapshot()
|
||||
|
|
|
@ -21,7 +21,17 @@ import {
|
|||
genCall,
|
||||
genMulti,
|
||||
} from './utils'
|
||||
import { toHandlerKey } from '@vue/shared'
|
||||
import {
|
||||
attributeCache,
|
||||
isHTMLGlobalAttr,
|
||||
isHTMLTag,
|
||||
isMathMLGlobalAttr,
|
||||
isMathMLTag,
|
||||
isSVGTag,
|
||||
isSvgGlobalAttr,
|
||||
shouldSetAsAttr,
|
||||
toHandlerKey,
|
||||
} from '@vue/shared'
|
||||
|
||||
// only the static key prop will reach here
|
||||
export function genSetProp(
|
||||
|
@ -31,9 +41,12 @@ export function genSetProp(
|
|||
const { vaporHelper } = context
|
||||
const {
|
||||
prop: { key, values, modifier },
|
||||
tag,
|
||||
} = oper
|
||||
|
||||
const keyName = key.content
|
||||
const tagName = tag.toUpperCase()
|
||||
const attrCacheKey = `${tagName}_${keyName}`
|
||||
|
||||
let helperName: VaporHelper
|
||||
let omitKey = false
|
||||
|
@ -45,6 +58,21 @@ export function genSetProp(
|
|||
omitKey = true
|
||||
} else if (modifier) {
|
||||
helperName = modifier === '.' ? 'setDOMProp' : 'setAttr'
|
||||
} else if (
|
||||
attributeCache[attrCacheKey] === undefined
|
||||
? (attributeCache[attrCacheKey] = shouldSetAsAttr(
|
||||
tag.toUpperCase(),
|
||||
keyName,
|
||||
))
|
||||
: attributeCache[attrCacheKey]
|
||||
) {
|
||||
helperName = 'setAttr'
|
||||
} else if (
|
||||
(isHTMLTag(tag) && isHTMLGlobalAttr(keyName)) ||
|
||||
(isSVGTag(tag) && isSvgGlobalAttr(keyName)) ||
|
||||
(isMathMLTag(tag) && isMathMLGlobalAttr(keyName))
|
||||
) {
|
||||
helperName = 'setDOMProp'
|
||||
} else {
|
||||
helperName = 'setDynamicProp'
|
||||
}
|
||||
|
|
|
@ -94,6 +94,7 @@ export interface SetPropIRNode extends BaseIRNode {
|
|||
element: number
|
||||
prop: IRProp
|
||||
root: boolean
|
||||
tag: string
|
||||
}
|
||||
|
||||
export interface SetDynamicPropsIRNode extends BaseIRNode {
|
||||
|
|
|
@ -210,6 +210,7 @@ function transformNativeElement(
|
|||
element: context.reference(),
|
||||
prop,
|
||||
root: singleRoot,
|
||||
tag,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
attributeCache,
|
||||
includeBooleanAttr,
|
||||
isArray,
|
||||
isFunction,
|
||||
|
@ -7,6 +8,7 @@ import {
|
|||
isString,
|
||||
normalizeClass,
|
||||
normalizeStyle,
|
||||
shouldSetAsAttr,
|
||||
toDisplayString,
|
||||
} from '@vue/shared'
|
||||
import { warn } from '../warning'
|
||||
|
@ -242,45 +244,15 @@ function shouldSetAsProp(
|
|||
return false
|
||||
}
|
||||
|
||||
// these are enumerated attrs, however their corresponding DOM properties
|
||||
// are actually booleans - this leads to setting it with a string "false"
|
||||
// value leading it to be coerced to `true`, so we need to always treat
|
||||
// them as attributes.
|
||||
// Note that `contentEditable` doesn't have this problem: its DOM
|
||||
// property is also enumerated string values.
|
||||
if (key === 'spellcheck' || key === 'draggable' || key === 'translate') {
|
||||
const attrCacheKey = `${el.tagName}_${key}`
|
||||
if (
|
||||
attributeCache[attrCacheKey] === undefined
|
||||
? (attributeCache[attrCacheKey] = shouldSetAsAttr(el.tagName, key))
|
||||
: attributeCache[attrCacheKey]
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
// #1787, #2840 form property on form elements is readonly and must be set as
|
||||
// attribute.
|
||||
if (key === 'form') {
|
||||
return false
|
||||
}
|
||||
|
||||
// #1526 <input list> must be set as attribute
|
||||
if (key === 'list' && el.tagName === 'INPUT') {
|
||||
return false
|
||||
}
|
||||
|
||||
// #2766 <textarea type> must be set as attribute
|
||||
if (key === 'type' && el.tagName === 'TEXTAREA') {
|
||||
return false
|
||||
}
|
||||
|
||||
// #8780 the width or height of embedded tags must be set as attribute
|
||||
if (key === 'width' || key === 'height') {
|
||||
const tag = el.tagName
|
||||
if (
|
||||
tag === 'IMG' ||
|
||||
tag === 'VIDEO' ||
|
||||
tag === 'CANVAS' ||
|
||||
tag === 'SOURCE'
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// native onclick with string value, must be set as attribute
|
||||
if (isNativeOn(key) && isString(value)) {
|
||||
return false
|
||||
|
|
|
@ -78,6 +78,16 @@ export const isKnownHtmlAttr: (key: string) => boolean = /*@__PURE__*/ makeMap(
|
|||
`value,width,wrap`,
|
||||
)
|
||||
|
||||
/**
|
||||
* Generated from https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes
|
||||
*/
|
||||
export const isHTMLGlobalAttr: (key: string) => boolean = /*@__PURE__*/ makeMap(
|
||||
`accesskey,anchor,autocapitalize,autocorrect,autofocus,class,contenteditable,` +
|
||||
`dir,draggable,enterkeyhint,exportparts,hidden,id,inert,inputmode,is,` +
|
||||
`itemid,itemprop,itemref,itemscope,itemtype,lang,nonce,part,popover,role,slot,` +
|
||||
`spellcheck,style,tabindex,title,translate,virtualkeyboardpolicy,writingsuggestions`,
|
||||
)
|
||||
|
||||
/**
|
||||
* Generated from https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute
|
||||
*/
|
||||
|
@ -123,6 +133,14 @@ export const isKnownSvgAttr: (key: string) => boolean = /*@__PURE__*/ makeMap(
|
|||
`xml:space,y,y1,y2,yChannelSelector,z,zoomAndPan`,
|
||||
)
|
||||
|
||||
/**
|
||||
* Generated from https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute#generic_attributes
|
||||
*/
|
||||
export const isSvgGlobalAttr: (key: string) => boolean = /*@__PURE__*/ makeMap(
|
||||
`id,class,style,lang,tabindex,xml:base,xml:lang,xml:space,requiredExtensions,` +
|
||||
`requiredFeatures,systemLanguage`,
|
||||
)
|
||||
|
||||
/**
|
||||
* Generated from https://developer.mozilla.org/en-US/docs/Web/MathML/Attribute
|
||||
*/
|
||||
|
@ -142,6 +160,15 @@ export const isKnownMathMLAttr: (key: string) => boolean =
|
|||
`voffset,width,widths,xlink:href,xlink:show,xlink:type,xmlns`,
|
||||
)
|
||||
|
||||
/**
|
||||
* Generated from https://developer.mozilla.org/en-US/docs/Web/MathML/Global_Attributes
|
||||
*/
|
||||
export const isMathMLGlobalAttr: (key: string) => boolean =
|
||||
/*@__PURE__*/ makeMap(
|
||||
`autofucus,class,dir,displaystyle,id,mathbackground,mathcolor,mathsize,nonce,scriptlevel,` +
|
||||
`style,tabindex`,
|
||||
)
|
||||
|
||||
/**
|
||||
* Shared between server-renderer and runtime-core hydration logic
|
||||
*/
|
||||
|
@ -152,3 +179,54 @@ export function isRenderableAttrValue(value: unknown): boolean {
|
|||
const type = typeof value
|
||||
return type === 'string' || type === 'number' || type === 'boolean'
|
||||
}
|
||||
|
||||
/**
|
||||
* cache seen attributes which must be set as attribute
|
||||
*/
|
||||
export const attributeCache: Record<string, boolean> = Object.create(null)
|
||||
|
||||
/*
|
||||
* The following attributes must be set as attribute
|
||||
*/
|
||||
export function shouldSetAsAttr(tagName: string, key: string): boolean {
|
||||
// these are enumerated attrs, however their corresponding DOM properties
|
||||
// are actually booleans - this leads to setting it with a string "false"
|
||||
// value leading it to be coerced to `true`, so we need to always treat
|
||||
// them as attributes.
|
||||
// Note that `contentEditable` doesn't have this problem: its DOM
|
||||
// property is also enumerated string values.
|
||||
if (key === 'spellcheck' || key === 'draggable' || key === 'translate') {
|
||||
return true
|
||||
}
|
||||
|
||||
// #1787, #2840 form property on form elements is readonly and must be set as
|
||||
// attribute.
|
||||
if (key === 'form') {
|
||||
return true
|
||||
}
|
||||
|
||||
// #1526 <input list> must be set as attribute
|
||||
if (key === 'list' && tagName === 'INPUT') {
|
||||
return true
|
||||
}
|
||||
|
||||
// #2766 <textarea type> must be set as attribute
|
||||
if (key === 'type' && tagName === 'TEXTAREA') {
|
||||
return true
|
||||
}
|
||||
|
||||
// #8780 the width or height of embedded tags must be set as attribute
|
||||
if (key === 'width' || key === 'height') {
|
||||
const tag = tagName
|
||||
if (
|
||||
tag === 'IMG' ||
|
||||
tag === 'VIDEO' ||
|
||||
tag === 'CANVAS' ||
|
||||
tag === 'SOURCE'
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue