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) _delegate(n0, "click", () => _ctx.handleClick)
_setInheritAttrs(["id"]) _setInheritAttrs(["id"])
_renderEffect(() => _setText(n0, _ctx.count, "foo", _ctx.count, "foo", _ctx.count)) _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 return n0
}" }"
`; `;

View File

@ -7,7 +7,19 @@ const t0 = _template("<div></div>")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_setInheritAttrs(["foo-bar"]) _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 return n0
}" }"
`; `;
@ -19,7 +31,43 @@ const t0 = _template("<div></div>")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_setInheritAttrs(["foo-bar"]) _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 return n0
}" }"
`; `;
@ -31,7 +79,7 @@ const t0 = _template("<div></div>")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_setInheritAttrs(["fooBar"]) _setInheritAttrs(["fooBar"])
_renderEffect(() => _setDynamicProp(n0, "fooBar", _ctx.id, true)) _renderEffect(() => _setDynamicProp(n0, "fooBar", _ctx.id))
return n0 return n0
}" }"
`; `;
@ -56,19 +104,7 @@ const t0 = _template("<div></div>")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_setInheritAttrs(["fooBar"]) _setInheritAttrs(["fooBar"])
_renderEffect(() => _setDynamicProp(n0, "fooBar", _ctx.fooBar, true)) _renderEffect(() => _setDynamicProp(n0, "fooBar", _ctx.fooBar))
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))
return n0 return n0
}" }"
`; `;
@ -80,7 +116,67 @@ const t0 = _template("<div></div>")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_setInheritAttrs(["fooBar"]) _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 return n0
}" }"
`; `;
@ -92,7 +188,7 @@ const t0 = _template("<div></div>")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_setInheritAttrs(["fooBar"]) _setInheritAttrs(["fooBar"])
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.id, true)) _renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.id))
return n0 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`] = ` 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'; "import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
@ -116,7 +224,91 @@ const t0 = _template("<div></div>")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_setInheritAttrs(["fooBar"]) _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 return n0
}" }"
`; `;
@ -128,11 +320,11 @@ const t0 = _template("<div></div>")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_setInheritAttrs(["id", "title", "lang", "dir", "tabindex"]) _setInheritAttrs(["id", "title", "lang", "dir", "tabindex"])
_renderEffect(() => _setDOMProp(n0, "id", _ctx.id, true)) _renderEffect(() => _setDOMProp(n0, "id", _ctx.id))
_renderEffect(() => _setDOMProp(n0, "title", _ctx.title, true)) _renderEffect(() => _setDOMProp(n0, "title", _ctx.title))
_renderEffect(() => _setDOMProp(n0, "lang", _ctx.lang, true)) _renderEffect(() => _setDOMProp(n0, "lang", _ctx.lang))
_renderEffect(() => _setDOMProp(n0, "dir", _ctx.dir, true)) _renderEffect(() => _setDOMProp(n0, "dir", _ctx.dir))
_renderEffect(() => _setDOMProp(n0, "tabindex", _ctx.tabindex, true)) _renderEffect(() => _setDOMProp(n0, "tabindex", _ctx.tabindex))
return n0 return n0
}" }"
`; `;
@ -144,11 +336,11 @@ const t0 = _template("<math></math>")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_setInheritAttrs(["autofucus", "dir", "displaystyle", "mathcolor", "tabindex"]) _setInheritAttrs(["autofucus", "dir", "displaystyle", "mathcolor", "tabindex"])
_renderEffect(() => _setDOMProp(n0, "autofucus", _ctx.autofucus, true)) _renderEffect(() => _setDOMProp(n0, "autofucus", _ctx.autofucus))
_renderEffect(() => _setDOMProp(n0, "dir", _ctx.dir, true)) _renderEffect(() => _setDOMProp(n0, "dir", _ctx.dir))
_renderEffect(() => _setDOMProp(n0, "displaystyle", _ctx.displaystyle, true)) _renderEffect(() => _setDOMProp(n0, "displaystyle", _ctx.displaystyle))
_renderEffect(() => _setDOMProp(n0, "mathcolor", _ctx.mathcolor, true)) _renderEffect(() => _setDOMProp(n0, "mathcolor", _ctx.mathcolor))
_renderEffect(() => _setDOMProp(n0, "tabindex", _ctx.tabindex, true)) _renderEffect(() => _setDOMProp(n0, "tabindex", _ctx.tabindex))
return n0 return n0
}" }"
`; `;
@ -160,9 +352,9 @@ const t0 = _template("<svg></svg>")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_setInheritAttrs(["id", "lang", "tabindex"]) _setInheritAttrs(["id", "lang", "tabindex"])
_renderEffect(() => _setDOMProp(n0, "id", _ctx.id, true)) _renderEffect(() => _setDOMProp(n0, "id", _ctx.id))
_renderEffect(() => _setDOMProp(n0, "lang", _ctx.lang, true)) _renderEffect(() => _setDOMProp(n0, "lang", _ctx.lang))
_renderEffect(() => _setDOMProp(n0, "tabindex", _ctx.tabindex, true)) _renderEffect(() => _setDOMProp(n0, "tabindex", _ctx.tabindex))
return n0 return n0
}" }"
`; `;
@ -214,7 +406,7 @@ const t0 = _template("<div></div>")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_setInheritAttrs(["id"]) _setInheritAttrs(["id"])
_renderEffect(() => _setDOMProp(n0, "id", _ctx.id, true)) _renderEffect(() => _setDOMProp(n0, "id", _ctx.id))
return n0 return n0
}" }"
`; `;
@ -250,7 +442,7 @@ const t0 = _template("<div></div>")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_setInheritAttrs(["camel-case"]) _setInheritAttrs(["camel-case"])
_renderEffect(() => _setDynamicProp(n0, "camel-case", _ctx.camelCase, true)) _renderEffect(() => _setDynamicProp(n0, "camel-case", _ctx.camelCase))
return n0 return n0
}" }"
`; `;
@ -262,7 +454,7 @@ const t0 = _template("<div></div>")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_setInheritAttrs(["id"]) _setInheritAttrs(["id"])
_renderEffect(() => _setDOMProp(n0, "id", _ctx.id, true)) _renderEffect(() => _setDOMProp(n0, "id", _ctx.id))
return n0 return n0
}" }"
`; `;

View File

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

View File

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

View File

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

View File

@ -23,6 +23,7 @@ import {
} from './utils' } from './utils'
import { import {
attributeCache, attributeCache,
canSetValueDirectly,
isHTMLGlobalAttr, isHTMLGlobalAttr,
isHTMLTag, isHTMLTag,
isMathMLGlobalAttr, isMathMLGlobalAttr,
@ -44,39 +45,7 @@ export function genSetProp(
tag, tag,
} = oper } = oper
const keyName = key.content const { helperName, omitKey } = getRuntimeHelper(tag, key.content, modifier)
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'
}
return [ return [
NEWLINE, NEWLINE,
...genCall( ...genCall(
@ -84,7 +53,10 @@ export function genSetProp(
`n${oper.element}`, `n${oper.element}`,
omitKey ? false : genExpression(key, context), omitKey ? false : genExpression(key, context),
genPropValue(values, 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 [] if (value == null) return []
return [NEWLINE, ...genCall(vaporHelper('setInheritAttrs'), value)] 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, setDynamicProps,
setHtml, setHtml,
setText, setText,
setValue,
} from '../../src/dom/prop' } from '../../src/dom/prop'
import { setStyle } from '../../src/dom/style' import { setStyle } from '../../src/dom/style'
import { import {
@ -239,43 +240,32 @@ describe('patchProp', () => {
}) })
}) })
describe('setDOMProp', () => { describe('setValue', () => {
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>')
})
test('should set value prop', () => { test('should set value prop', () => {
const el = document.createElement('input') const el = document.createElement('input')
setDOMProp(el, 'value', 'foo') setValue(el, 'foo')
expect(el.value).toBe('foo') expect(el.value).toBe('foo')
setDOMProp(el, 'value', null) setValue(el, null)
expect(el.value).toBe('') expect(el.value).toBe('')
expect(el.getAttribute('value')).toBe(null) expect(el.getAttribute('value')).toBe(null)
const obj = {} const obj = {}
setDOMProp(el, 'value', obj) setValue(el, obj)
expect(el.value).toBe(obj.toString()) expect(el.value).toBe(obj.toString())
expect((el as any)._value).toBe(obj) expect((el as any)._value).toBe(obj)
const option = document.createElement('option') const option = document.createElement('option')
setDOMProp(option, 'textContent', 'foo') setText(option, 'foo')
expect(option.value).toBe('foo') expect(option.value).toBe('foo')
expect(option.getAttribute('value')).toBe(null) expect(option.getAttribute('value')).toBe(null)
setDOMProp(option, 'value', 'bar') setValue(option, 'bar')
expect(option.textContent).toBe('foo') expect(option.textContent).toBe('foo')
expect(option.value).toBe('bar') expect(option.value).toBe('bar')
expect(option.getAttribute('value')).toBe('bar') expect(option.getAttribute('value')).toBe('bar')
}) })
})
describe('setDOMProp', () => {
test('should be boolean prop', () => { test('should be boolean prop', () => {
const el = document.createElement('select') const el = document.createElement('select')
setDOMProp(el, 'multiple', '') setDOMProp(el, 'multiple', '')
@ -455,6 +445,8 @@ describe('patchProp', () => {
describe('setText', () => { describe('setText', () => {
test('should set textContent', () => { test('should set textContent', () => {
const el = document.createElement('div') const el = document.createElement('div')
setText(el, null)
expect(el.textContent).toBe('')
setText(el, 'foo') setText(el, 'foo')
expect(el.textContent).toBe('foo') expect(el.textContent).toBe('foo')
setText(el, 'bar') setText(el, 'bar')
@ -465,6 +457,8 @@ describe('patchProp', () => {
describe('setHtml', () => { describe('setHtml', () => {
test('should set innerHTML', () => { test('should set innerHTML', () => {
const el = document.createElement('div') const el = document.createElement('div')
setHtml(el, null)
expect(el.innerHTML).toBe('')
setHtml(el, '<p>foo</p>') setHtml(el, '<p>foo</p>')
expect(el.innerHTML).toBe('<p>foo</p>') expect(el.innerHTML).toBe('<p>foo</p>')
setHtml(el, '<p>bar</p>') setHtml(el, '<p>bar</p>')

View File

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

View File

@ -113,6 +113,7 @@ export {
setHtml, setHtml,
setClass, setClass,
setAttr, setAttr,
setValue,
setDOMProp, setDOMProp,
setDynamicProp, setDynamicProp,
setDynamicProps, 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('-')
)
}