perf(runtime-vapor): optimize `setDOMProp` on static tag + key (#294)

This commit is contained in:
edison 2024-12-01 17:04:42 +08:00 committed by GitHub
parent 842f94cc73
commit 0196e1a499
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 493 additions and 156 deletions

View File

@ -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(() => _setDOMProp(n0, "id", _ctx.count, true))
_renderEffect(() => _setDOMProp(n0, "id", _ctx.count))
return n0
}"
`;

View File

@ -7,7 +7,19 @@ const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["foo-bar"])
_renderEffect(() => _setAttr(n0, "foo-bar", _ctx.id, true))
_renderEffect(() => _setAttr(n0, "foo-bar", _ctx.id))
return n0
}"
`;
exports[`compiler v-bind > .attr modifier w/ innerHTML 1`] = `
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setAttr as _setAttr, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["innerHTML"])
_renderEffect(() => _setAttr(n0, "innerHTML", _ctx.foo))
return n0
}"
`;
@ -19,7 +31,43 @@ const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["foo-bar"])
_renderEffect(() => _setAttr(n0, "foo-bar", _ctx.fooBar, true))
_renderEffect(() => _setAttr(n0, "foo-bar", _ctx.fooBar))
return n0
}"
`;
exports[`compiler v-bind > .attr modifier w/ progress value 1`] = `
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setAttr as _setAttr, template as _template } from 'vue/vapor';
const t0 = _template("<progress></progress>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["value"])
_renderEffect(() => _setAttr(n0, "value", _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .attr modifier w/ textContent 1`] = `
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setAttr as _setAttr, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["textContent"])
_renderEffect(() => _setAttr(n0, "textContent", _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .attr modifier w/ value 1`] = `
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setAttr as _setAttr, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["value"])
_renderEffect(() => _setAttr(n0, "value", _ctx.foo))
return n0
}"
`;
@ -31,7 +79,7 @@ const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["fooBar"])
_renderEffect(() => _setDynamicProp(n0, "fooBar", _ctx.id, true))
_renderEffect(() => _setDynamicProp(n0, "fooBar", _ctx.id))
return n0
}"
`;
@ -56,19 +104,7 @@ const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["fooBar"])
_renderEffect(() => _setDynamicProp(n0, "fooBar", _ctx.fooBar, true))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier (shortband) w/ no expression 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(["fooBar"])
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.fooBar, true))
_renderEffect(() => _setDynamicProp(n0, "fooBar", _ctx.fooBar))
return n0
}"
`;
@ -80,7 +116,67 @@ const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["fooBar"])
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.id, true))
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.id))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier (shorthand) w/ innerHTML 1`] = `
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setHtml as _setHtml, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["innerHTML"])
_renderEffect(() => _setHtml(n0, _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier (shorthand) w/ no expression 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(["fooBar"])
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.fooBar))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier (shorthand) w/ progress value 1`] = `
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
const t0 = _template("<progress></progress>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["value"])
_renderEffect(() => _setDOMProp(n0, "value", _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier (shorthand) w/ textContent 1`] = `
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setText as _setText, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["textContent"])
_renderEffect(() => _setText(n0, _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier (shorthand) w/ value 1`] = `
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setValue as _setValue, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["value"])
_renderEffect(() => _setValue(n0, _ctx.foo))
return n0
}"
`;
@ -92,7 +188,7 @@ const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["fooBar"])
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.id, true))
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.id))
return n0
}"
`;
@ -109,6 +205,18 @@ export function render(_ctx) {
}"
`;
exports[`compiler v-bind > .prop modifier w/ innerHTML 1`] = `
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setHtml as _setHtml, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["innerHTML"])
_renderEffect(() => _setHtml(n0, _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier w/ no expression 1`] = `
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
@ -116,7 +224,91 @@ const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["fooBar"])
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.fooBar, true))
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.fooBar))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier w/ progress value 1`] = `
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
const t0 = _template("<progress></progress>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["value"])
_renderEffect(() => _setDOMProp(n0, "value", _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier w/ textContent 1`] = `
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setText as _setText, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["textContent"])
_renderEffect(() => _setText(n0, _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier w/ value 1`] = `
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setValue as _setValue, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["value"])
_renderEffect(() => _setValue(n0, _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > :innerHTML 1`] = `
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setHtml as _setHtml, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["innerHTML"])
_renderEffect(() => _setHtml(n0, _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > :textContext 1`] = `
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setText as _setText, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["textContent"])
_renderEffect(() => _setText(n0, _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > :value 1`] = `
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setValue as _setValue, template as _template } from 'vue/vapor';
const t0 = _template("<input>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["value"])
_renderEffect(() => _setValue(n0, _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > :value w/ progress 1`] = `
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
const t0 = _template("<progress></progress>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["value"])
_renderEffect(() => _setDynamicProp(n0, "value", _ctx.foo))
return n0
}"
`;
@ -128,11 +320,11 @@ 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))
_renderEffect(() => _setDOMProp(n0, "id", _ctx.id))
_renderEffect(() => _setDOMProp(n0, "title", _ctx.title))
_renderEffect(() => _setDOMProp(n0, "lang", _ctx.lang))
_renderEffect(() => _setDOMProp(n0, "dir", _ctx.dir))
_renderEffect(() => _setDOMProp(n0, "tabindex", _ctx.tabindex))
return n0
}"
`;
@ -144,11 +336,11 @@ 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))
_renderEffect(() => _setDOMProp(n0, "autofucus", _ctx.autofucus))
_renderEffect(() => _setDOMProp(n0, "dir", _ctx.dir))
_renderEffect(() => _setDOMProp(n0, "displaystyle", _ctx.displaystyle))
_renderEffect(() => _setDOMProp(n0, "mathcolor", _ctx.mathcolor))
_renderEffect(() => _setDOMProp(n0, "tabindex", _ctx.tabindex))
return n0
}"
`;
@ -160,9 +352,9 @@ 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))
_renderEffect(() => _setDOMProp(n0, "id", _ctx.id))
_renderEffect(() => _setDOMProp(n0, "lang", _ctx.lang))
_renderEffect(() => _setDOMProp(n0, "tabindex", _ctx.tabindex))
return n0
}"
`;
@ -214,7 +406,7 @@ const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["id"])
_renderEffect(() => _setDOMProp(n0, "id", _ctx.id, true))
_renderEffect(() => _setDOMProp(n0, "id", _ctx.id))
return n0
}"
`;
@ -250,7 +442,7 @@ const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["camel-case"])
_renderEffect(() => _setDynamicProp(n0, "camel-case", _ctx.camelCase, true))
_renderEffect(() => _setDynamicProp(n0, "camel-case", _ctx.camelCase))
return n0
}"
`;
@ -262,7 +454,7 @@ const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(["id"])
_renderEffect(() => _setDOMProp(n0, "id", _ctx.id, true))
_renderEffect(() => _setDOMProp(n0, "id", _ctx.id))
return n0
}"
`;

View File

@ -52,8 +52,8 @@ export function render(_ctx) {
const n0 = _createFor(() => (_ctx.items), (_ctx0) => {
const n2 = t0()
_setInheritAttrs(["item", "index"])
_renderEffect(() => _setDynamicProp(n2, "item", _ctx0[0].value, true))
_renderEffect(() => _setDynamicProp(n2, "index", _ctx0[1].value, true))
_renderEffect(() => _setDynamicProp(n2, "item", _ctx0[0].value))
_renderEffect(() => _setDynamicProp(n2, "index", _ctx0[1].value))
return n2
})
return n0

View File

@ -6,7 +6,7 @@ const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setDOMProp(n0, "id", _ctx.foo, true)
_setDOMProp(n0, "id", _ctx.foo)
_setInheritAttrs(["id"])
return n0
}"

View File

@ -74,7 +74,7 @@ describe('compiler v-bind', () => {
})
expect(code).matchSnapshot()
expect(code).contains('_setDOMProp(n0, "id", _ctx.id, true)')
expect(code).contains('_setDOMProp(n0, "id", _ctx.id)')
})
test('no expression', () => {
@ -104,7 +104,7 @@ describe('compiler v-bind', () => {
],
},
})
expect(code).contains('_setDOMProp(n0, "id", _ctx.id, true)')
expect(code).contains('_setDOMProp(n0, "id", _ctx.id)')
})
test('no expression (shorthand)', () => {
@ -126,9 +126,7 @@ describe('compiler v-bind', () => {
],
},
})
expect(code).contains(
'_setDynamicProp(n0, "camel-case", _ctx.camelCase, true)',
)
expect(code).contains('_setDynamicProp(n0, "camel-case", _ctx.camelCase)')
})
test('dynamic arg', () => {
@ -288,7 +286,7 @@ describe('compiler v-bind', () => {
})
expect(code).matchSnapshot()
expect(code).contains('_setDynamicProp(n0, "fooBar", _ctx.id, true)')
expect(code).contains('_setDynamicProp(n0, "fooBar", _ctx.id)')
})
test('.camel modifier w/ no expression', () => {
@ -312,7 +310,7 @@ describe('compiler v-bind', () => {
},
})
expect(code).contains('renderEffect')
expect(code).contains('_setDynamicProp(n0, "fooBar", _ctx.fooBar, true)')
expect(code).contains('_setDynamicProp(n0, "fooBar", _ctx.fooBar)')
})
test('.camel modifier w/ dynamic arg', () => {
@ -370,7 +368,7 @@ describe('compiler v-bind', () => {
},
})
expect(code).contains('renderEffect')
expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.id, true)')
expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.id)')
})
test('.prop modifier w/ no expression', () => {
@ -394,7 +392,7 @@ describe('compiler v-bind', () => {
},
})
expect(code).contains('renderEffect')
expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.fooBar, true)')
expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.fooBar)')
})
test('.prop modifier w/ dynamic arg', () => {
@ -451,10 +449,10 @@ describe('compiler v-bind', () => {
},
})
expect(code).contains('renderEffect')
expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.id, true)')
expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.id)')
})
test('.prop modifier (shortband) w/ no expression', () => {
test('.prop modifier (shorthand) w/ no expression', () => {
const { ir, code } = compileWithVBind(`<div .fooBar />`)
expect(code).matchSnapshot()
@ -475,7 +473,55 @@ describe('compiler v-bind', () => {
},
})
expect(code).contains('renderEffect')
expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.fooBar, true)')
expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.fooBar)')
})
test('.prop modifier w/ innerHTML', () => {
const { code } = compileWithVBind(`<div :innerHTML.prop="foo" />`)
expect(code).matchSnapshot()
expect(code).contains('_setHtml(n0, _ctx.foo)')
})
test('.prop modifier (shorthand) w/ innerHTML', () => {
const { code } = compileWithVBind(`<div .innerHTML="foo" />`)
expect(code).matchSnapshot()
expect(code).contains('_setHtml(n0, _ctx.foo)')
})
test('.prop modifier w/ textContent', () => {
const { code } = compileWithVBind(`<div :textContent.prop="foo" />`)
expect(code).matchSnapshot()
expect(code).contains('_setText(n0, _ctx.foo)')
})
test('.prop modifier (shorthand) w/ textContent', () => {
const { code } = compileWithVBind(`<div .textContent="foo" />`)
expect(code).matchSnapshot()
expect(code).contains('_setText(n0, _ctx.foo)')
})
test('.prop modifier w/ value', () => {
const { code } = compileWithVBind(`<div :value.prop="foo" />`)
expect(code).matchSnapshot()
expect(code).contains('_setValue(n0, _ctx.foo)')
})
test('.prop modifier (shorthand) w/ value', () => {
const { code } = compileWithVBind(`<div .value="foo" />`)
expect(code).matchSnapshot()
expect(code).contains('_setValue(n0, _ctx.foo)')
})
test('.prop modifier w/ progress value', () => {
const { code } = compileWithVBind(`<progress :value.prop="foo" />`)
expect(code).matchSnapshot()
expect(code).contains('_setDOMProp(n0, "value", _ctx.foo)')
})
test('.prop modifier (shorthand) w/ progress value', () => {
const { code } = compileWithVBind(`<progress .value="foo" />`)
expect(code).matchSnapshot()
expect(code).contains('_setDOMProp(n0, "value", _ctx.foo)')
})
test('.attr modifier', () => {
@ -499,7 +545,7 @@ describe('compiler v-bind', () => {
},
})
expect(code).contains('renderEffect')
expect(code).contains('_setAttr(n0, "foo-bar", _ctx.id, true)')
expect(code).contains('_setAttr(n0, "foo-bar", _ctx.id)')
})
test('.attr modifier w/ no expression', () => {
@ -524,7 +570,31 @@ describe('compiler v-bind', () => {
})
expect(code).contains('renderEffect')
expect(code).contains('_setAttr(n0, "foo-bar", _ctx.fooBar, true)')
expect(code).contains('_setAttr(n0, "foo-bar", _ctx.fooBar)')
})
test('.attr modifier w/ innerHTML', () => {
const { code } = compileWithVBind(`<div :innerHTML.attr="foo" />`)
expect(code).matchSnapshot()
expect(code).contains('_setAttr(n0, "innerHTML", _ctx.foo)')
})
test('.attr modifier w/ textContent', () => {
const { code } = compileWithVBind(`<div :textContent.attr="foo" />`)
expect(code).matchSnapshot()
expect(code).contains('_setAttr(n0, "textContent", _ctx.foo)')
})
test('.attr modifier w/ value', () => {
const { code } = compileWithVBind(`<div :value.attr="foo" />`)
expect(code).matchSnapshot()
expect(code).contains('_setAttr(n0, "value", _ctx.foo)')
})
test('.attr modifier w/ progress value', () => {
const { code } = compileWithVBind(`<progress :value.attr="foo" />`)
expect(code).matchSnapshot()
expect(code).contains('_setAttr(n0, "value", _ctx.foo)')
})
test('attributes must be set as attribute', () => {
@ -561,11 +631,11 @@ describe('compiler v-bind', () => {
`)
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)')
expect(code).contains('_setDOMProp(n0, "id", _ctx.id)')
expect(code).contains('_setDOMProp(n0, "title", _ctx.title)')
expect(code).contains('_setDOMProp(n0, "lang", _ctx.lang)')
expect(code).contains('_setDOMProp(n0, "dir", _ctx.dir)')
expect(code).contains('_setDOMProp(n0, "tabindex", _ctx.tabindex)')
})
test('SVG global attributes should set as dom prop', () => {
@ -574,9 +644,9 @@ describe('compiler v-bind', () => {
`)
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)')
expect(code).contains('_setDOMProp(n0, "id", _ctx.id)')
expect(code).contains('_setDOMProp(n0, "lang", _ctx.lang)')
expect(code).contains('_setDOMProp(n0, "tabindex", _ctx.tabindex)')
})
test('MathML global attributes should set as dom prop', () => {
@ -585,13 +655,43 @@ describe('compiler v-bind', () => {
`)
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)')
expect(code).contains('_setDOMProp(n0, "autofucus", _ctx.autofucus)')
expect(code).contains('_setDOMProp(n0, "dir", _ctx.dir)')
expect(code).contains('_setDOMProp(n0, "displaystyle", _ctx.displaystyle)')
expect(code).contains('_setDOMProp(n0, "mathcolor", _ctx.mathcolor)')
expect(code).contains('_setDOMProp(n0, "tabindex", _ctx.tabindex)')
})
test(':innerHTML', () => {
const { code } = compileWithVBind(`
<div :innerHTML="foo"/>
`)
expect(code).matchSnapshot()
expect(code).contains('_setHtml(n0, _ctx.foo)')
})
test(':textContext', () => {
const { code } = compileWithVBind(`
<div :textContent="foo"/>
`)
expect(code).matchSnapshot()
expect(code).contains('_setText(n0, _ctx.foo)')
})
test(':value', () => {
const { code } = compileWithVBind(`
<input :value="foo"/>
`)
expect(code).matchSnapshot()
expect(code).contains('_setValue(n0, _ctx.foo)')
})
test(':value w/ progress', () => {
const { code } = compileWithVBind(`
<progress :value="foo"/>
`)
expect(code).matchSnapshot()
expect(code).contains('_setDynamicProp(n0, "value", _ctx.foo)')
})
test('number value', () => {

View File

@ -23,6 +23,7 @@ import {
} from './utils'
import {
attributeCache,
canSetValueDirectly,
isHTMLGlobalAttr,
isHTMLTag,
isMathMLGlobalAttr,
@ -44,39 +45,7 @@ export function genSetProp(
tag,
} = oper
const keyName = key.content
const tagName = tag.toUpperCase()
const attrCacheKey = `${tagName}_${keyName}`
let helperName: VaporHelper
let omitKey = false
if (keyName === 'class') {
helperName = 'setClass'
omitKey = true
} else if (keyName === 'style') {
helperName = 'setStyle'
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'
}
const { helperName, omitKey } = getRuntimeHelper(tag, key.content, modifier)
return [
NEWLINE,
...genCall(
@ -84,7 +53,10 @@ export function genSetProp(
`n${oper.element}`,
omitKey ? false : genExpression(key, context),
genPropValue(values, context),
oper.root && 'true',
// only `setClass` and `setStyle` need merge inherit attr
oper.root && (helperName === 'setClass' || helperName === 'setStyle')
? 'true'
: undefined,
),
]
}
@ -196,3 +168,70 @@ export function genSetInheritAttrs(
if (value == null) return []
return [NEWLINE, ...genCall(vaporHelper('setInheritAttrs'), value)]
}
function getRuntimeHelper(
tag: string,
keyName: string,
modifier: '.' | '^' | undefined,
) {
const tagName = tag.toUpperCase()
let helperName: VaporHelper
let omitKey = false
if (modifier) {
if (modifier === '.') {
const helper = getSpecialHelper(keyName, tagName)
if (helper) {
helperName = helper.name
omitKey = helper.omitKey
} else {
helperName = 'setDOMProp'
omitKey = false
}
} else {
helperName = 'setAttr'
}
} else {
const attrCacheKey = `${tagName}_${keyName}`
const helper = getSpecialHelper(keyName, tagName)
if (helper) {
helperName = helper.name
omitKey = helper.omitKey
} else if (
attributeCache[attrCacheKey] === undefined
? (attributeCache[attrCacheKey] = shouldSetAsAttr(tagName, keyName))
: attributeCache[attrCacheKey]
) {
helperName = 'setAttr'
} else if (
(isHTMLTag(tag) && isHTMLGlobalAttr(keyName)) ||
(isSVGTag(tag) && isSvgGlobalAttr(keyName)) ||
(isMathMLTag(tag) && isMathMLGlobalAttr(keyName))
) {
helperName = 'setDOMProp'
} else {
helperName = 'setDynamicProp'
}
}
return { helperName, omitKey }
}
const specialHelpers: Record<string, { name: VaporHelper; omitKey: boolean }> =
{
class: { name: 'setClass', omitKey: true },
style: { name: 'setStyle', omitKey: true },
innerHTML: { name: 'setHtml', omitKey: true },
textContent: { name: 'setText', omitKey: true },
}
const getSpecialHelper = (
keyName: string,
tagName: string,
): { name: VaporHelper; omitKey: boolean } | null => {
// special case for 'value' property
if (keyName === 'value' && canSetValueDirectly(tagName)) {
return { name: 'setValue', omitKey: true }
}
return specialHelpers[keyName] || null
}

View File

@ -7,6 +7,7 @@ import {
setDynamicProps,
setHtml,
setText,
setValue,
} from '../../src/dom/prop'
import { setStyle } from '../../src/dom/style'
import {
@ -239,43 +240,32 @@ describe('patchProp', () => {
})
})
describe('setDOMProp', () => {
test('should set DOM property', () => {
const el = document.createElement('div')
setDOMProp(el, 'textContent', null)
expect(el.textContent).toBe('')
setDOMProp(el, 'textContent', 'foo')
expect(el.textContent).toBe('foo')
setDOMProp(el, 'innerHTML', null)
expect(el.innerHTML).toBe('')
setDOMProp(el, 'innerHTML', '<p>bar</p>')
expect(el.innerHTML).toBe('<p>bar</p>')
})
describe('setValue', () => {
test('should set value prop', () => {
const el = document.createElement('input')
setDOMProp(el, 'value', 'foo')
setValue(el, 'foo')
expect(el.value).toBe('foo')
setDOMProp(el, 'value', null)
setValue(el, null)
expect(el.value).toBe('')
expect(el.getAttribute('value')).toBe(null)
const obj = {}
setDOMProp(el, 'value', obj)
setValue(el, obj)
expect(el.value).toBe(obj.toString())
expect((el as any)._value).toBe(obj)
const option = document.createElement('option')
setDOMProp(option, 'textContent', 'foo')
setText(option, 'foo')
expect(option.value).toBe('foo')
expect(option.getAttribute('value')).toBe(null)
setDOMProp(option, 'value', 'bar')
setValue(option, 'bar')
expect(option.textContent).toBe('foo')
expect(option.value).toBe('bar')
expect(option.getAttribute('value')).toBe('bar')
})
})
describe('setDOMProp', () => {
test('should be boolean prop', () => {
const el = document.createElement('select')
setDOMProp(el, 'multiple', '')
@ -455,6 +445,8 @@ describe('patchProp', () => {
describe('setText', () => {
test('should set textContent', () => {
const el = document.createElement('div')
setText(el, null)
expect(el.textContent).toBe('')
setText(el, 'foo')
expect(el.textContent).toBe('foo')
setText(el, 'bar')
@ -465,6 +457,8 @@ describe('patchProp', () => {
describe('setHtml', () => {
test('should set innerHTML', () => {
const el = document.createElement('div')
setHtml(el, null)
expect(el.innerHTML).toBe('')
setHtml(el, '<p>foo</p>')
expect(el.innerHTML).toBe('<p>foo</p>')
setHtml(el, '<p>bar</p>')

View File

@ -1,5 +1,6 @@
import {
attributeCache,
canSetValueDirectly,
includeBooleanAttr,
isArray,
isFunction,
@ -50,43 +51,29 @@ export function setAttr(el: Element, key: string, value: any): void {
}
}
export function setValue(el: any, value: any): void {
const oldVal = recordPropMetadata(el, 'value', value)
if (value === oldVal) return
// store value as _value as well since
// non-string values will be stringified.
el._value = value
// #4956: <option> value will fallback to its text content so we need to
// compare against its attribute value instead.
const oldValue = el.tagName === 'OPTION' ? el.getAttribute('value') : el.value
const newValue = value == null ? '' : value
if (oldValue !== newValue) {
el.value = newValue
}
if (value == null) {
el.removeAttribute('value')
}
}
export function setDOMProp(el: any, key: string, value: any): void {
const oldVal = recordPropMetadata(el, key, value)
if (value === oldVal) return
if (key === 'innerHTML' || key === 'textContent') {
// TODO special checks
// if (prevChildren) {
// unmountChildren(prevChildren, parentComponent, parentSuspense)
// }
el[key] = value == null ? '' : value
return
}
const tag = el.tagName
if (
key === 'value' &&
tag !== 'PROGRESS' &&
// custom elements may use _value internally
!tag.includes('-')
) {
// store value as _value as well since
// non-string values will be stringified.
el._value = value
// #4956: <option> value will fallback to its text content so we need to
// compare against its attribute value instead.
const oldValue = tag === 'OPTION' ? el.getAttribute('value') : el.value
const newValue = value == null ? '' : value
if (oldValue !== newValue) {
el.value = newValue
}
if (value == null) {
el.removeAttribute(key)
}
return
}
let needRemove = false
if (value === '' || value == null) {
const type = typeof el[key]
@ -113,7 +100,7 @@ export function setDOMProp(el: any, key: string, value: any): void {
// do not warn if value is auto-coerced from nullish values
if (__DEV__ && !needRemove) {
warn(
`Failed setting prop "${key}" on <${tag.toLowerCase()}>: ` +
`Failed setting prop "${key}" on <${el.tagName.toLowerCase()}>: ` +
`value ${value} is invalid.`,
e,
)
@ -138,6 +125,22 @@ export function setDynamicProp(el: Element, key: string, value: any): void {
? ((key = key.slice(1)), false)
: shouldSetAsProp(el, key, value, isSVG)
) {
if (key === 'innerHTML') {
setHtml(el, value)
return
}
if (key === 'textContent') {
setText(el, value)
return
}
const tag = el.tagName
if (key === 'value' && canSetValueDirectly(tag)) {
setValue(el, value)
return
}
setDOMProp(el, key, value)
} else {
// TODO special case for <input v-model type="checkbox">
@ -220,7 +223,7 @@ export function setText(el: Node, ...values: any[]): void {
export function setHtml(el: Element, value: any): void {
const oldVal = recordPropMetadata(el, 'innerHTML', value)
if (value !== oldVal) {
el.innerHTML = value
el.innerHTML = value == null ? '' : value
}
}

View File

@ -113,6 +113,7 @@ export {
setHtml,
setClass,
setAttr,
setValue,
setDOMProp,
setDynamicProp,
setDynamicProps,

View File

@ -227,3 +227,11 @@ export function genCacheKey(source: string, options: any): string {
)
)
}
export function canSetValueDirectly(tagName: string): boolean {
return (
tagName !== 'PROGRESS' &&
// custom elements may use _value internally
!tagName.includes('-')
)
}