From 1bb9a0f8c7fc6b30d30c5d4cc8f9e8048679dcc3 Mon Sep 17 00:00:00 2001 From: edison Date: Wed, 11 Dec 2024 14:02:34 +0800 Subject: [PATCH] refactor: drop recordPropMetadata + merge renderEffect (#301) --- .../__snapshots__/compile.spec.ts.snap | 25 +- .../compiler-vapor/__tests__/compile.spec.ts | 4 +- .../transformChildren.spec.ts.snap | 9 +- .../transformElement.spec.ts.snap | 29 +- .../transformTemplateRef.spec.ts.snap | 2 +- .../__snapshots__/vBind.spec.ts.snap | 247 +++++++++++------- .../__snapshots__/vFor.spec.ts.snap | 20 +- .../__snapshots__/vHtml.spec.ts.snap | 10 +- .../transforms/__snapshots__/vIf.spec.ts.snap | 15 +- .../__snapshots__/vModel.spec.ts.snap | 5 +- .../transforms/__snapshots__/vOn.spec.ts.snap | 18 +- .../__snapshots__/vText.spec.ts.snap | 10 +- .../transforms/transformElement.spec.ts | 10 +- .../__tests__/transforms/vBind.spec.ts | 212 +++++++++++---- packages/compiler-vapor/src/generate.ts | 11 +- .../src/generators/expression.ts | 11 +- .../compiler-vapor/src/generators/html.ts | 17 +- .../src/generators/operation.ts | 77 +++++- .../compiler-vapor/src/generators/prop.ts | 136 +++++++++- .../compiler-vapor/src/generators/text.ts | 16 +- .../compiler-vapor/src/generators/utils.ts | 9 +- packages/compiler-vapor/src/ir/index.ts | 5 + packages/compiler-vapor/src/transform.ts | 36 ++- .../__tests__/apiSetupContext.spec.ts | 8 +- .../runtime-vapor/__tests__/dom/prop.spec.ts | 106 ++++---- packages/runtime-vapor/src/componentAttrs.ts | 3 +- .../runtime-vapor/src/componentFallback.ts | 9 +- .../runtime-vapor/src/componentMetadata.ts | 7 - packages/runtime-vapor/src/dom/prop.ts | 84 +++--- packages/runtime-vapor/src/dom/style.ts | 15 +- 30 files changed, 772 insertions(+), 394 deletions(-) diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap index 09e8c4cca..843b58e00 100644 --- a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap @@ -1,12 +1,13 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compile > bindings 1`] = ` -"import { renderEffect as _renderEffect, setText as _setText, template as _template } from 'vue/vapor'; +"import { setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx, $props, $emit, $attrs, $slots) { const n0 = t0() - _renderEffect(() => _setText(n0, "count is ", _ctx.count, ".")) + let _count + _renderEffect(() => _count !== _ctx.count && _setText(n0, "count is ", (_count = _ctx.count), ".")) return n0 }" `; @@ -151,7 +152,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, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor'; +"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createTextNode as _createTextNode, insert as _insert, setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
{{ bar }}
") const t1 = _template("
") @@ -162,7 +163,8 @@ export function render(_ctx, $props, $emit, $attrs, $slots) { const n1 = _createComponent(_component_Comp) const n2 = _createTextNode(() => [_ctx.bar]) _insert([n1, n2], n3) - _renderEffect(() => _setDOMProp(n3, "id", _ctx.foo)) + let _foo + _renderEffect(() => _foo !== _ctx.foo && _setDOMProp(n3, "id", (_foo = _ctx.foo))) return [n0, n3] }" `; @@ -177,7 +179,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, setDOMProp as _setDOMProp, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor'; +"import { delegate as _delegate, setInheritAttrs as _setInheritAttrs, setText as _setText, setDOMProp as _setDOMProp, renderEffect as _renderEffect, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor'; const t0 = _template("") _delegateEvents("click") @@ -185,8 +187,14 @@ export function render(_ctx) { const n0 = t0() _delegate(n0, "click", () => _ctx.handleClick) _setInheritAttrs(["id"]) - _renderEffect(() => _setText(n0, _ctx.count, "foo", _ctx.count, "foo", _ctx.count)) - _renderEffect(() => _setDOMProp(n0, "id", _ctx.count)) + let _count + _renderEffect(() => { + if(_count !== _ctx.count) { + _setText(n0, _ctx.count, "foo", _ctx.count, "foo", _ctx.count) + _setDOMProp(n0, "id", _ctx.count) + _count = _ctx.count + } + }) return n0 }" `; @@ -202,7 +210,8 @@ exports[`compile > expression parsing > v-bind 1`] = ` "((_ctx) => { const n0 = t0() _setInheritAttrs(true) - _renderEffect(() => _setDynamicProps(n0, [{ [key.value+1]: _unref(foo)[key.value+1]() }], true)) + let _key_value, _foo, _key_value_foo + _renderEffect(() => (_key_value !== key.value || _foo !== _unref(foo)) && (_key_value_foo = _setDynamicProps(n0, _key_value_foo, [{ [key.value+1]: _unref(foo)[key.value+1]() }], true))) return n0 })()" `; diff --git a/packages/compiler-vapor/__tests__/compile.spec.ts b/packages/compiler-vapor/__tests__/compile.spec.ts index 2aeb777da..9b8770d59 100644 --- a/packages/compiler-vapor/__tests__/compile.spec.ts +++ b/packages/compiler-vapor/__tests__/compile.spec.ts @@ -194,7 +194,9 @@ describe('compile', () => { }) expect(code).matchSnapshot() expect(code).contains('key.value+1') - expect(code).contains('_unref(foo)[key.value+1]()') + expect(code).contains( + '(_key_value !== key.value || _foo !== _unref(foo)) && (_key_value_foo = _setDynamicProps(n0, _key_value_foo, [{ [key.value+1]: _unref(foo)[key.value+1]() }], true))', + ) }) // TODO: add more test for expression parsing (v-on, v-slot, v-for) diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap index 951a534ec..db2dbaa57 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compiler: children transform > children & sibling references 1`] = ` -"import { next as _next, createTextNode as _createTextNode, insert as _insert, renderEffect as _renderEffect, setText as _setText, template as _template } from 'vue/vapor'; +"import { next as _next, createTextNode as _createTextNode, insert as _insert, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("

") export function render(_ctx) { @@ -11,8 +11,11 @@ export function render(_ctx) { const n2 = n3.nextSibling const n1 = _createTextNode(() => [_ctx.second, " ", _ctx.third, " "]) _insert(n1, n4, n3) - _renderEffect(() => _setText(n0, _ctx.first)) - _renderEffect(() => _setText(n2, _ctx.forth)) + let _first, _forth + _renderEffect(() => { + _first !== _ctx.first && _setText(n0, (_first = _ctx.first)) + _forth !== _ctx.forth && _setText(n2, (_forth = _ctx.forth)) + }) return n4 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap index 79b8fffbb..2b5ab3680 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap @@ -297,13 +297,14 @@ export function render(_ctx) { `; exports[`compiler: element transform > props merging: class 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setClass as _setClass, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setClass as _setClass, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["class"]) - _renderEffect(() => _setClass(n0, ["foo", { bar: _ctx.isBar }], true)) + let _isBar + _renderEffect(() => _isBar !== _ctx.isBar && _setClass(n0, ["foo", { bar: (_isBar = _ctx.isBar) }], true)) return n0 }" `; @@ -326,7 +327,7 @@ export function render(_ctx) { `; exports[`compiler: element transform > props merging: style 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setStyle as _setStyle, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setStyle as _setStyle, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { @@ -349,55 +350,59 @@ export function render(_ctx) { `; exports[`compiler: element transform > v-bind="obj" 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(true) - _renderEffect(() => _setDynamicProps(n0, [_ctx.obj], true)) + let _obj + _renderEffect(() => _obj !== _ctx.obj && (_obj = _setDynamicProps(n0, _obj, [_ctx.obj], true))) return n0 }" `; exports[`compiler: element transform > v-bind="obj" after static prop 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(true) - _renderEffect(() => _setDynamicProps(n0, [{ id: "foo" }, _ctx.obj], true)) + let _obj + _renderEffect(() => _obj !== _ctx.obj && (_obj = _setDynamicProps(n0, _obj, [{ id: "foo" }, _ctx.obj], true))) return n0 }" `; exports[`compiler: element transform > v-bind="obj" before static prop 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(true) - _renderEffect(() => _setDynamicProps(n0, [_ctx.obj, { id: "foo" }], true)) + let _obj + _renderEffect(() => _obj !== _ctx.obj && (_obj = _setDynamicProps(n0, _obj, [_ctx.obj, { id: "foo" }], true))) return n0 }" `; exports[`compiler: element transform > v-bind="obj" between static props 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(true) - _renderEffect(() => _setDynamicProps(n0, [{ id: "foo" }, _ctx.obj, { class: "bar" }], true)) + let _obj + _renderEffect(() => _obj !== _ctx.obj && (_obj = _setDynamicProps(n0, _obj, [{ id: "foo" }, _ctx.obj, { class: "bar" }], true))) return n0 }" `; exports[`compiler: element transform > v-on="obj" 1`] = ` -"import { renderEffect as _renderEffect, setDynamicEvents as _setDynamicEvents, template as _template } from 'vue/vapor'; +"import { setDynamicEvents as _setDynamicEvents, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap index a60019925..efada2de9 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compiler: template ref transform > dynamic ref 1`] = ` -"import { renderEffect as _renderEffect, setRef as _setRef, template as _template } from 'vue/vapor'; +"import { setRef as _setRef, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap index 4630eadfb..f40057f38 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap @@ -1,366 +1,401 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compiler v-bind > .attr modifier 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setAttr as _setAttr, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setAttr as _setAttr, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["foo-bar"]) - _renderEffect(() => _setAttr(n0, "foo-bar", _ctx.id)) + let _id + _renderEffect(() => _id !== _ctx.id && _setAttr(n0, "foo-bar", (_id = _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'; +"import { setInheritAttrs as _setInheritAttrs, setAttr as _setAttr, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["innerHTML"]) - _renderEffect(() => _setAttr(n0, "innerHTML", _ctx.foo)) + let _foo + _renderEffect(() => _foo !== _ctx.foo && _setAttr(n0, "innerHTML", (_foo = _ctx.foo))) return n0 }" `; exports[`compiler v-bind > .attr modifier w/ no expression 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setAttr as _setAttr, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setAttr as _setAttr, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["foo-bar"]) - _renderEffect(() => _setAttr(n0, "foo-bar", _ctx.fooBar)) + let _fooBar + _renderEffect(() => _fooBar !== _ctx.fooBar && _setAttr(n0, "foo-bar", (_fooBar = _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'; +"import { setInheritAttrs as _setInheritAttrs, setAttr as _setAttr, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["value"]) - _renderEffect(() => _setAttr(n0, "value", _ctx.foo)) + let _foo + _renderEffect(() => _foo !== _ctx.foo && _setAttr(n0, "value", (_foo = _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'; +"import { setInheritAttrs as _setInheritAttrs, setAttr as _setAttr, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["textContent"]) - _renderEffect(() => _setAttr(n0, "textContent", _ctx.foo)) + let _foo + _renderEffect(() => _foo !== _ctx.foo && _setAttr(n0, "textContent", (_foo = _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'; +"import { setInheritAttrs as _setInheritAttrs, setAttr as _setAttr, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["value"]) - _renderEffect(() => _setAttr(n0, "value", _ctx.foo)) + let _foo + _renderEffect(() => _foo !== _ctx.foo && _setAttr(n0, "value", (_foo = _ctx.foo))) return n0 }" `; exports[`compiler v-bind > .camel modifier 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setDynamicProp as _setDynamicProp, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["fooBar"]) - _renderEffect(() => _setDynamicProp(n0, "fooBar", _ctx.id)) + let _id + _renderEffect(() => _id !== _ctx.id && (_id = _setDynamicProp(n0, "fooBar", _id, _ctx.id))) return n0 }" `; exports[`compiler v-bind > .camel modifier w/ dynamic arg 1`] = ` "import { camelize as _camelize } from 'vue'; -import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor'; +import { setInheritAttrs as _setInheritAttrs, setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(true) - _renderEffect(() => _setDynamicProps(n0, [{ [_camelize(_ctx.foo)]: _ctx.id }], true)) + let _foo, _id, _foo_id + _renderEffect(() => (_foo !== _ctx.foo || _id !== _ctx.id) && (_foo_id = _setDynamicProps(n0, _foo_id, [{ [_camelize(_ctx.foo)]: _ctx.id }], true))) return n0 }" `; exports[`compiler v-bind > .camel modifier w/ no expression 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setDynamicProp as _setDynamicProp, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["fooBar"]) - _renderEffect(() => _setDynamicProp(n0, "fooBar", _ctx.fooBar)) + let _fooBar + _renderEffect(() => _fooBar !== _ctx.fooBar && (_fooBar = _setDynamicProp(n0, "fooBar", _fooBar, _ctx.fooBar))) return n0 }" `; exports[`compiler v-bind > .prop modifier (shorthand) 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["fooBar"]) - _renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.id)) + let _id + _renderEffect(() => _id !== _ctx.id && _setDOMProp(n0, "fooBar", (_id = _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'; +"import { setInheritAttrs as _setInheritAttrs, setHtml as _setHtml, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["innerHTML"]) - _renderEffect(() => _setHtml(n0, _ctx.foo)) + let _foo + _renderEffect(() => _foo !== _ctx.foo && _setHtml(n0, (_foo = _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'; +"import { setInheritAttrs as _setInheritAttrs, setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["fooBar"]) - _renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.fooBar)) + let _fooBar + _renderEffect(() => _fooBar !== _ctx.fooBar && _setDOMProp(n0, "fooBar", (_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'; +"import { setInheritAttrs as _setInheritAttrs, setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["value"]) - _renderEffect(() => _setDOMProp(n0, "value", _ctx.foo)) + let _foo + _renderEffect(() => _foo !== _ctx.foo && _setDOMProp(n0, "value", (_foo = _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'; +"import { setInheritAttrs as _setInheritAttrs, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["textContent"]) - _renderEffect(() => _setText(n0, _ctx.foo)) + let _foo + _renderEffect(() => _foo !== _ctx.foo && _setText(n0, (_foo = _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'; +"import { setInheritAttrs as _setInheritAttrs, setValue as _setValue, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["value"]) - _renderEffect(() => _setValue(n0, _ctx.foo)) + let _foo + _renderEffect(() => _foo !== _ctx.foo && _setValue(n0, (_foo = _ctx.foo))) return n0 }" `; exports[`compiler v-bind > .prop modifier 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["fooBar"]) - _renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.id)) + let _id + _renderEffect(() => _id !== _ctx.id && _setDOMProp(n0, "fooBar", (_id = _ctx.id))) return n0 }" `; exports[`compiler v-bind > .prop modifier w/ dynamic arg 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(true) - _renderEffect(() => _setDynamicProps(n0, [{ ["." + _ctx.fooBar]: _ctx.id }], true)) + let _fooBar, _id, _fooBar_id + _renderEffect(() => (_fooBar !== _ctx.fooBar || _id !== _ctx.id) && (_fooBar_id = _setDynamicProps(n0, _fooBar_id, [{ ["." + _ctx.fooBar]: _ctx.id }], true))) return n0 }" `; 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'; +"import { setInheritAttrs as _setInheritAttrs, setHtml as _setHtml, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["innerHTML"]) - _renderEffect(() => _setHtml(n0, _ctx.foo)) + let _foo + _renderEffect(() => _foo !== _ctx.foo && _setHtml(n0, (_foo = _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'; +"import { setInheritAttrs as _setInheritAttrs, setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["fooBar"]) - _renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.fooBar)) + let _fooBar + _renderEffect(() => _fooBar !== _ctx.fooBar && _setDOMProp(n0, "fooBar", (_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'; +"import { setInheritAttrs as _setInheritAttrs, setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["value"]) - _renderEffect(() => _setDOMProp(n0, "value", _ctx.foo)) + let _foo + _renderEffect(() => _foo !== _ctx.foo && _setDOMProp(n0, "value", (_foo = _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'; +"import { setInheritAttrs as _setInheritAttrs, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["textContent"]) - _renderEffect(() => _setText(n0, _ctx.foo)) + let _foo + _renderEffect(() => _foo !== _ctx.foo && _setText(n0, (_foo = _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'; +"import { setInheritAttrs as _setInheritAttrs, setValue as _setValue, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["value"]) - _renderEffect(() => _setValue(n0, _ctx.foo)) + let _foo + _renderEffect(() => _foo !== _ctx.foo && _setValue(n0, (_foo = _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'; +"import { setInheritAttrs as _setInheritAttrs, setHtml as _setHtml, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["innerHTML"]) - _renderEffect(() => _setHtml(n0, _ctx.foo)) + let _foo + _renderEffect(() => _foo !== _ctx.foo && _setHtml(n0, (_foo = _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'; +"import { setInheritAttrs as _setInheritAttrs, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["textContent"]) - _renderEffect(() => _setText(n0, _ctx.foo)) + let _foo + _renderEffect(() => _foo !== _ctx.foo && _setText(n0, (_foo = _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'; +"import { setInheritAttrs as _setInheritAttrs, setValue as _setValue, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["value"]) - _renderEffect(() => _setValue(n0, _ctx.foo)) + let _foo + _renderEffect(() => _foo !== _ctx.foo && _setValue(n0, (_foo = _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'; +"import { setInheritAttrs as _setInheritAttrs, setDynamicProp as _setDynamicProp, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["value"]) - _renderEffect(() => _setDynamicProp(n0, "value", _ctx.foo)) + let _foo + _renderEffect(() => _foo !== _ctx.foo && (_foo = _setDynamicProp(n0, "value", _foo, _ctx.foo))) return n0 }" `; 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'; +"import { setInheritAttrs as _setInheritAttrs, setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["id", "title", "lang", "dir", "tabindex"]) - _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)) + let _id, _title, _lang, _dir, _tabindex + _renderEffect(() => { + _id !== _ctx.id && _setDOMProp(n0, "id", (_id = _ctx.id)) + _title !== _ctx.title && _setDOMProp(n0, "title", (_title = _ctx.title)) + _lang !== _ctx.lang && _setDOMProp(n0, "lang", (_lang = _ctx.lang)) + _dir !== _ctx.dir && _setDOMProp(n0, "dir", (_dir = _ctx.dir)) + _tabindex !== _ctx.tabindex && _setDOMProp(n0, "tabindex", (_tabindex = _ctx.tabindex)) + }) 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'; +"import { setInheritAttrs as _setInheritAttrs, setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["autofucus", "dir", "displaystyle", "mathcolor", "tabindex"]) - _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)) + let _autofucus, _dir, _displaystyle, _mathcolor, _tabindex + _renderEffect(() => { + _autofucus !== _ctx.autofucus && _setDOMProp(n0, "autofucus", (_autofucus = _ctx.autofucus)) + _dir !== _ctx.dir && _setDOMProp(n0, "dir", (_dir = _ctx.dir)) + _displaystyle !== _ctx.displaystyle && _setDOMProp(n0, "displaystyle", (_displaystyle = _ctx.displaystyle)) + _mathcolor !== _ctx.mathcolor && _setDOMProp(n0, "mathcolor", (_mathcolor = _ctx.mathcolor)) + _tabindex !== _ctx.tabindex && _setDOMProp(n0, "tabindex", (_tabindex = _ctx.tabindex)) + }) 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'; +"import { setInheritAttrs as _setInheritAttrs, setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["id", "lang", "tabindex"]) - _renderEffect(() => _setDOMProp(n0, "id", _ctx.id)) - _renderEffect(() => _setDOMProp(n0, "lang", _ctx.lang)) - _renderEffect(() => _setDOMProp(n0, "tabindex", _ctx.tabindex)) + let _id, _lang, _tabindex + _renderEffect(() => { + _id !== _ctx.id && _setDOMProp(n0, "id", (_id = _ctx.id)) + _lang !== _ctx.lang && _setDOMProp(n0, "lang", (_lang = _ctx.lang)) + _tabindex !== _ctx.tabindex && _setDOMProp(n0, "tabindex", (_tabindex = _ctx.tabindex)) + }) 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'; +"import { setAttr as _setAttr, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") const t1 = _template("") const t2 = _template("") @@ -377,84 +412,106 @@ export function render(_ctx) { 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) - }) + let _spellcheck, _draggable, _translate, _form, _list, _type, _width, _height _renderEffect(() => { - _setAttr(n3, "height", _ctx.height) - _setAttr(n4, "height", _ctx.height) - _setAttr(n5, "height", _ctx.height) - _setAttr(n6, "height", _ctx.height) + _spellcheck !== _ctx.spellcheck && _setAttr(n0, "spellcheck", (_spellcheck = _ctx.spellcheck)) + _draggable !== _ctx.draggable && _setAttr(n0, "draggable", (_draggable = _ctx.draggable)) + _translate !== _ctx.translate && _setAttr(n0, "translate", (_translate = _ctx.translate)) + _form !== _ctx.form && _setAttr(n0, "form", (_form = _ctx.form)) + _list !== _ctx.list && _setAttr(n1, "list", (_list = _ctx.list)) + _type !== _ctx.type && _setAttr(n2, "type", (_type = _ctx.type)) + if(_width !== _ctx.width) { + _setAttr(n3, "width", _ctx.width) + _setAttr(n4, "width", _ctx.width) + _setAttr(n5, "width", _ctx.width) + _setAttr(n6, "width", _ctx.width) + _width = _ctx.width + } + if(_height !== _ctx.height) { + _setAttr(n3, "height", _ctx.height) + _setAttr(n4, "height", _ctx.height) + _setAttr(n5, "height", _ctx.height) + _setAttr(n6, "height", _ctx.height) + _height = _ctx.height + } }) return [n0, n1, n2, n3, n4, n5, n6] }" `; exports[`compiler v-bind > basic 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["id"]) - _renderEffect(() => _setDOMProp(n0, "id", _ctx.id)) + let _id + _renderEffect(() => _id !== _ctx.id && _setDOMProp(n0, "id", (_id = _ctx.id))) return n0 }" `; +exports[`compiler v-bind > bind member expression 1`] = ` +"import { setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; +const t0 = _template("
") + +export function render(_ctx) { + const n0 = t0() + let _obj + _renderEffect(() => _obj !== _ctx.obj.count.bar && _setDOMProp(n0, "id", (_obj = _ctx.obj.count.bar))) + return [n0, n1] +}" +`; + exports[`compiler v-bind > dynamic arg 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(true) - _renderEffect(() => _setDynamicProps(n0, [{ [_ctx.id]: _ctx.id, [_ctx.title]: _ctx.title }], true)) + let _id, _title, _id_title + _renderEffect(() => (_id !== _ctx.id || _title !== _ctx.title) && (_id_title = _setDynamicProps(n0, _id_title, [{ [_ctx.id]: _ctx.id, [_ctx.title]: _ctx.title }], true))) return n0 }" `; exports[`compiler v-bind > dynamic arg w/ static attribute 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(true) - _renderEffect(() => _setDynamicProps(n0, [{ [_ctx.id]: _ctx.id, foo: "bar", checked: "" }], true)) + let _id + _renderEffect(() => _id !== _ctx.id && (_id = _setDynamicProps(n0, _id, [{ [_ctx.id]: _ctx.id, foo: "bar", checked: "" }], true))) return n0 }" `; exports[`compiler v-bind > no expression (shorthand) 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setDynamicProp as _setDynamicProp, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["camel-case"]) - _renderEffect(() => _setDynamicProp(n0, "camel-case", _ctx.camelCase)) + let _camelCase + _renderEffect(() => _camelCase !== _ctx.camelCase && (_camelCase = _setDynamicProp(n0, "camel-case", _camelCase, _ctx.camelCase))) return n0 }" `; exports[`compiler v-bind > no expression 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _setInheritAttrs(["id"]) - _renderEffect(() => _setDOMProp(n0, "id", _ctx.id)) + let _id + _renderEffect(() => _id !== _ctx.id && _setDOMProp(n0, "id", (_id = _ctx.id))) return n0 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap index a82221728..580b41046 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compiler: v-for > array de-structured value 1`] = ` -"import { renderEffect as _renderEffect, setText as _setText, withDestructure as _withDestructure, createFor as _createFor, template as _template } from 'vue/vapor'; +"import { setText as _setText, renderEffect as _renderEffect, withDestructure as _withDestructure, createFor as _createFor, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { @@ -15,7 +15,7 @@ export function render(_ctx) { `; exports[`compiler: v-for > basic v-for 1`] = ` -"import { delegate as _delegate, renderEffect as _renderEffect, setText as _setText, createFor as _createFor, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor'; +"import { delegate as _delegate, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor'; const t0 = _template("
") _delegateEvents("click") @@ -31,7 +31,7 @@ export function render(_ctx) { `; exports[`compiler: v-for > function params w/ prefixIdentifiers: false 1`] = ` -"import { renderEffect as _renderEffect, setText as _setText, createFor as _createFor, template as _template } from 'vue/vapor'; +"import { setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { @@ -45,15 +45,17 @@ export function render(_ctx) { `; exports[`compiler: v-for > multi effect 1`] = ` -"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp, createFor as _createFor, template as _template } from 'vue/vapor'; +"import { setInheritAttrs as _setInheritAttrs, setDynamicProp as _setDynamicProp, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = _createFor(() => (_ctx.items), (_ctx0) => { const n2 = t0() _setInheritAttrs(["item", "index"]) - _renderEffect(() => _setDynamicProp(n2, "item", _ctx0[0].value)) - _renderEffect(() => _setDynamicProp(n2, "index", _ctx0[1].value)) + _renderEffect(() => { + _setDynamicProp(n2, "item", _ctx0[0].value) + _setDynamicProp(n2, "index", _ctx0[1].value) + }) return n2 }) return n0 @@ -61,7 +63,7 @@ export function render(_ctx) { `; exports[`compiler: v-for > nested v-for 1`] = ` -"import { renderEffect as _renderEffect, setText as _setText, createFor as _createFor, insert as _insert, template as _template } from 'vue/vapor'; +"import { setText as _setText, renderEffect as _renderEffect, createFor as _createFor, insert as _insert, template as _template } from 'vue/vapor'; const t0 = _template("") const t1 = _template("
") @@ -81,7 +83,7 @@ export function render(_ctx) { `; exports[`compiler: v-for > object de-structured value 1`] = ` -"import { renderEffect as _renderEffect, setText as _setText, withDestructure as _withDestructure, createFor as _createFor, template as _template } from 'vue/vapor'; +"import { setText as _setText, renderEffect as _renderEffect, withDestructure as _withDestructure, createFor as _createFor, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { @@ -95,7 +97,7 @@ export function render(_ctx) { `; exports[`compiler: v-for > v-for aliases w/ complex expressions 1`] = ` -"import { renderEffect as _renderEffect, setText as _setText, withDestructure as _withDestructure, createFor as _createFor, template as _template } from 'vue/vapor'; +"import { setText as _setText, renderEffect as _renderEffect, withDestructure as _withDestructure, createFor as _createFor, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vHtml.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vHtml.spec.ts.snap index a5ee792e2..34b621bc6 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vHtml.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vHtml.spec.ts.snap @@ -1,23 +1,25 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`v-html > should convert v-html to innerHTML 1`] = ` -"import { renderEffect as _renderEffect, setHtml as _setHtml, template as _template } from 'vue/vapor'; +"import { setHtml as _setHtml, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx, $props, $emit, $attrs, $slots) { const n0 = t0() - _renderEffect(() => _setHtml(n0, _ctx.code)) + let _code + _renderEffect(() => _code !== _ctx.code && _setHtml(n0, (_code = _ctx.code))) return n0 }" `; exports[`v-html > should raise error and ignore children when v-html is present 1`] = ` -"import { renderEffect as _renderEffect, setHtml as _setHtml, template as _template } from 'vue/vapor'; +"import { setHtml as _setHtml, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() - _renderEffect(() => _setHtml(n0, _ctx.test)) + let _test + _renderEffect(() => _test !== _ctx.test && _setHtml(n0, (_test = _ctx.test))) return n0 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap index 3d92452a1..42cb0a4d2 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap @@ -1,13 +1,14 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compiler: v-if > basic v-if 1`] = ` -"import { renderEffect as _renderEffect, setText as _setText, createIf as _createIf, template as _template } from 'vue/vapor'; +"import { setText as _setText, renderEffect as _renderEffect, createIf as _createIf, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = _createIf(() => (_ctx.ok), () => { const n2 = t0() - _renderEffect(() => _setText(n2, _ctx.msg)) + let _msg + _renderEffect(() => _msg !== _ctx.msg && _setText(n2, (_msg = _ctx.msg))) return n2 }) return n0 @@ -15,7 +16,7 @@ export function render(_ctx) { `; exports[`compiler: v-if > comment between branches 1`] = ` -"import { createIf as _createIf, renderEffect as _renderEffect, setText as _setText, template as _template } from 'vue/vapor'; +"import { createIf as _createIf, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") const t1 = _template("") const t2 = _template("

") @@ -37,7 +38,8 @@ export function render(_ctx) { const n11 = t4() return [n10, n11] })) - _renderEffect(() => _setText(n13, _ctx.text)) + let _text + _renderEffect(() => _text !== _ctx.text && _setText(n13, (_text = _ctx.text))) return [n0, n13] }" `; @@ -60,7 +62,7 @@ export function render(_ctx) { `; exports[`compiler: v-if > template v-if 1`] = ` -"import { renderEffect as _renderEffect, setText as _setText, createIf as _createIf, template as _template } from 'vue/vapor'; +"import { setText as _setText, renderEffect as _renderEffect, createIf as _createIf, template as _template } from 'vue/vapor'; const t0 = _template("
") const t1 = _template("hello") const t2 = _template("

") @@ -70,7 +72,8 @@ export function render(_ctx) { const n2 = t0() const n3 = t1() const n4 = t2() - _renderEffect(() => _setText(n4, _ctx.msg)) + let _msg + _renderEffect(() => _msg !== _ctx.msg && _setText(n4, (_msg = _ctx.msg))) return [n2, n3, n4] }) return n0 diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap index 04ceb3c5a..0b3851662 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap @@ -246,7 +246,7 @@ export function render(_ctx) { `; exports[`compiler: vModel transform > should support w/ dynamic v-bind 1`] = ` -"import { vModelDynamic as _vModelDynamic, withDirectives as _withDirectives, delegate as _delegate, setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor'; +"import { vModelDynamic as _vModelDynamic, withDirectives as _withDirectives, delegate as _delegate, setInheritAttrs as _setInheritAttrs, setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("") export function render(_ctx) { @@ -254,7 +254,8 @@ export function render(_ctx) { _withDirectives(n0, [[_vModelDynamic, () => _ctx.model]]) _delegate(n0, "update:modelValue", () => $event => (_ctx.model = $event)) _setInheritAttrs(true) - _renderEffect(() => _setDynamicProps(n0, [_ctx.obj], true)) + let _obj + _renderEffect(() => _obj !== _ctx.obj && (_obj = _setDynamicProps(n0, _obj, [_ctx.obj], true))) return n0 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap index cbd195b83..8657e0611 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap @@ -13,12 +13,13 @@ export function render(_ctx) { `; exports[`v-on > dynamic arg 1`] = ` -"import { renderEffect as _renderEffect, on as _on, template as _template } from 'vue/vapor'; +"import { on as _on, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _renderEffect(() => { + _on(n0, _ctx.event, () => _ctx.handler, { effect: true }) @@ -28,12 +29,13 @@ export function render(_ctx) { `; exports[`v-on > dynamic arg with complex exp prefixing 1`] = ` -"import { renderEffect as _renderEffect, on as _on, template as _template } from 'vue/vapor'; +"import { on as _on, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _renderEffect(() => { + _on(n0, _ctx.event(_ctx.foo), () => _ctx.handler, { effect: true }) @@ -43,12 +45,13 @@ export function render(_ctx) { `; exports[`v-on > dynamic arg with prefixing 1`] = ` -"import { renderEffect as _renderEffect, on as _on, template as _template } from 'vue/vapor'; +"import { on as _on, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _renderEffect(() => { + _on(n0, _ctx.event, () => _ctx.handler, { effect: true }) @@ -372,12 +375,13 @@ export function render(_ctx) { `; exports[`v-on > should transform click.middle 2`] = ` -"import { renderEffect as _renderEffect, on as _on, template as _template } from 'vue/vapor'; +"import { on as _on, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _renderEffect(() => { + _on(n0, (_ctx.event) === "click" ? "mouseup" : (_ctx.event), () => _ctx.test, { modifiers: ["middle"], effect: true @@ -402,12 +406,13 @@ export function render(_ctx) { `; exports[`v-on > should transform click.right 2`] = ` -"import { renderEffect as _renderEffect, on as _on, template as _template } from 'vue/vapor'; +"import { on as _on, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _renderEffect(() => { + _on(n0, (_ctx.event) === "click" ? "contextmenu" : (_ctx.event), () => _ctx.test, { modifiers: ["right"], keys: ["right"], @@ -431,12 +436,13 @@ export function render(_ctx) { `; exports[`v-on > should wrap both for dynamic key event w/ left/right modifiers 1`] = ` -"import { renderEffect as _renderEffect, on as _on, template as _template } from 'vue/vapor'; +"import { on as _on, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() _renderEffect(() => { + _on(n0, _ctx.e, () => _ctx.test, { modifiers: ["left"], keys: ["left"], diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vText.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vText.spec.ts.snap index 622966ba8..1b98a024c 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vText.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vText.spec.ts.snap @@ -1,23 +1,25 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`v-text > should convert v-text to textContent 1`] = ` -"import { renderEffect as _renderEffect, setText as _setText, template as _template } from 'vue/vapor'; +"import { setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx, $props, $emit, $attrs, $slots) { const n0 = t0() - _renderEffect(() => _setText(n0, _ctx.str)) + let _str + _renderEffect(() => _str !== _ctx.str && _setText(n0, (_str = _ctx.str))) return n0 }" `; exports[`v-text > should raise error and ignore children when v-text is present 1`] = ` -"import { renderEffect as _renderEffect, setText as _setText, template as _template } from 'vue/vapor'; +"import { setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() - _renderEffect(() => _setText(n0, _ctx.test)) + let _test + _renderEffect(() => _test !== _ctx.test && _setText(n0, (_test = _ctx.test))) return n0 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts index 2b5f89464..631beac52 100644 --- a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts @@ -588,7 +588,9 @@ describe('compiler: element transform', () => { ], }, ]) - expect(code).contains('_setDynamicProps(n0, [_ctx.obj], true)') + expect(code).contains( + '_obj !== _ctx.obj && (_obj = _setDynamicProps(n0, _obj, [_ctx.obj], true))', + ) }) test('v-bind="obj" after static prop', () => { @@ -625,7 +627,7 @@ describe('compiler: element transform', () => { }, ]) expect(code).contains( - '_setDynamicProps(n0, [{ id: "foo" }, _ctx.obj], true)', + '_obj !== _ctx.obj && (_obj = _setDynamicProps(n0, _obj, [{ id: "foo" }, _ctx.obj], true))', ) }) @@ -653,7 +655,7 @@ describe('compiler: element transform', () => { }, ]) expect(code).contains( - '_setDynamicProps(n0, [_ctx.obj, { id: "foo" }], true)', + '_obj !== _ctx.obj && (_obj = _setDynamicProps(n0, _obj, [_ctx.obj, { id: "foo" }], true))', ) }) @@ -682,7 +684,7 @@ describe('compiler: element transform', () => { }, ]) expect(code).contains( - '_setDynamicProps(n0, [{ id: "foo" }, _ctx.obj, { class: "bar" }], true)', + '_obj !== _ctx.obj && (_obj = _setDynamicProps(n0, _obj, [{ id: "foo" }, _ctx.obj, { class: "bar" }], true))', ) }) diff --git a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts b/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts index efaa9257c..87c7752aa 100644 --- a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts @@ -74,7 +74,9 @@ describe('compiler v-bind', () => { }) expect(code).matchSnapshot() - expect(code).contains('_setDOMProp(n0, "id", _ctx.id)') + expect(code).contains( + '_id !== _ctx.id && _setDOMProp(n0, "id", (_id = _ctx.id))', + ) }) test('no expression', () => { @@ -104,7 +106,9 @@ describe('compiler v-bind', () => { ], }, }) - expect(code).contains('_setDOMProp(n0, "id", _ctx.id)') + expect(code).contains( + '_id !== _ctx.id && _setDOMProp(n0, "id", (_id = _ctx.id))', + ) }) test('no expression (shorthand)', () => { @@ -126,7 +130,9 @@ describe('compiler v-bind', () => { ], }, }) - expect(code).contains('_setDynamicProp(n0, "camel-case", _ctx.camelCase)') + expect(code).contains( + '_camelCase !== _ctx.camelCase && (_camelCase = _setDynamicProp(n0, "camel-case", _camelCase, _ctx.camelCase))', + ) }) test('dynamic arg', () => { @@ -171,7 +177,7 @@ describe('compiler v-bind', () => { ], }) expect(code).contains( - '_setDynamicProps(n0, [{ [_ctx.id]: _ctx.id, [_ctx.title]: _ctx.title }], true)', + '(_id !== _ctx.id || _title !== _ctx.title) && (_id_title = _setDynamicProps(n0, _id_title, [{ [_ctx.id]: _ctx.id, [_ctx.title]: _ctx.title }], true))', ) }) @@ -224,7 +230,7 @@ describe('compiler v-bind', () => { ], }) expect(code).contains( - '_setDynamicProps(n0, [{ [_ctx.id]: _ctx.id, foo: "bar", checked: "" }], true)', + '_id !== _ctx.id && (_id = _setDynamicProps(n0, _id, [{ [_ctx.id]: _ctx.id, foo: "bar", checked: "" }], true))', ) }) @@ -286,7 +292,9 @@ describe('compiler v-bind', () => { }) expect(code).matchSnapshot() - expect(code).contains('_setDynamicProp(n0, "fooBar", _ctx.id)') + expect(code).contains( + '_id !== _ctx.id && (_id = _setDynamicProp(n0, "fooBar", _id, _ctx.id))', + ) }) test('.camel modifier w/ no expression', () => { @@ -310,7 +318,9 @@ describe('compiler v-bind', () => { }, }) expect(code).contains('renderEffect') - expect(code).contains('_setDynamicProp(n0, "fooBar", _ctx.fooBar)') + expect(code).contains( + '_fooBar !== _ctx.fooBar && (_fooBar = _setDynamicProp(n0, "fooBar", _fooBar, _ctx.fooBar))', + ) }) test('.camel modifier w/ dynamic arg', () => { @@ -341,7 +351,7 @@ describe('compiler v-bind', () => { expect(code).matchSnapshot() expect(code).contains('renderEffect') expect(code).contains( - `_setDynamicProps(n0, [{ [_camelize(_ctx.foo)]: _ctx.id }], true)`, + `(_foo !== _ctx.foo || _id !== _ctx.id) && (_foo_id = _setDynamicProps(n0, _foo_id, [{ [_camelize(_ctx.foo)]: _ctx.id }], true))`, ) }) @@ -368,7 +378,9 @@ describe('compiler v-bind', () => { }, }) expect(code).contains('renderEffect') - expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.id)') + expect(code).contains( + '_id !== _ctx.id && _setDOMProp(n0, "fooBar", (_id = _ctx.id))', + ) }) test('.prop modifier w/ no expression', () => { @@ -392,7 +404,9 @@ describe('compiler v-bind', () => { }, }) expect(code).contains('renderEffect') - expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.fooBar)') + expect(code).contains( + '_fooBar !== _ctx.fooBar && _setDOMProp(n0, "fooBar", (_fooBar = _ctx.fooBar))', + ) }) test('.prop modifier w/ dynamic arg', () => { @@ -422,7 +436,7 @@ describe('compiler v-bind', () => { }) expect(code).contains('renderEffect') expect(code).contains( - `_setDynamicProps(n0, [{ ["." + _ctx.fooBar]: _ctx.id }], true)`, + `(_fooBar !== _ctx.fooBar || _id !== _ctx.id) && (_fooBar_id = _setDynamicProps(n0, _fooBar_id, [{ ["." + _ctx.fooBar]: _ctx.id }], true))`, ) }) @@ -449,7 +463,9 @@ describe('compiler v-bind', () => { }, }) expect(code).contains('renderEffect') - expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.id)') + expect(code).contains( + '_id !== _ctx.id && _setDOMProp(n0, "fooBar", (_id = _ctx.id))', + ) }) test('.prop modifier (shorthand) w/ no expression', () => { @@ -473,55 +489,73 @@ describe('compiler v-bind', () => { }, }) expect(code).contains('renderEffect') - expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.fooBar)') + expect(code).contains( + '_fooBar !== _ctx.fooBar && _setDOMProp(n0, "fooBar", (_fooBar = _ctx.fooBar))', + ) }) test('.prop modifier w/ innerHTML', () => { const { code } = compileWithVBind(`
`) expect(code).matchSnapshot() - expect(code).contains('_setHtml(n0, _ctx.foo)') + expect(code).contains( + '_foo !== _ctx.foo && _setHtml(n0, (_foo = _ctx.foo))', + ) }) test('.prop modifier (shorthand) w/ innerHTML', () => { const { code } = compileWithVBind(`
`) expect(code).matchSnapshot() - expect(code).contains('_setHtml(n0, _ctx.foo)') + expect(code).contains( + '_foo !== _ctx.foo && _setHtml(n0, (_foo = _ctx.foo))', + ) }) test('.prop modifier w/ textContent', () => { const { code } = compileWithVBind(`
`) expect(code).matchSnapshot() - expect(code).contains('_setText(n0, _ctx.foo)') + expect(code).contains( + '_foo !== _ctx.foo && _setText(n0, (_foo = _ctx.foo))', + ) }) test('.prop modifier (shorthand) w/ textContent', () => { const { code } = compileWithVBind(`
`) expect(code).matchSnapshot() - expect(code).contains('_setText(n0, _ctx.foo)') + expect(code).contains( + '_foo !== _ctx.foo && _setText(n0, (_foo = _ctx.foo))', + ) }) test('.prop modifier w/ value', () => { const { code } = compileWithVBind(`
`) expect(code).matchSnapshot() - expect(code).contains('_setValue(n0, _ctx.foo)') + expect(code).contains( + '_foo !== _ctx.foo && _setValue(n0, (_foo = _ctx.foo))', + ) }) test('.prop modifier (shorthand) w/ value', () => { const { code } = compileWithVBind(`
`) expect(code).matchSnapshot() - expect(code).contains('_setValue(n0, _ctx.foo)') + expect(code).contains( + '_foo !== _ctx.foo && _setValue(n0, (_foo = _ctx.foo))', + ) }) test('.prop modifier w/ progress value', () => { const { code } = compileWithVBind(``) expect(code).matchSnapshot() - expect(code).contains('_setDOMProp(n0, "value", _ctx.foo)') + expect(code).contains( + '_foo !== _ctx.foo && _setDOMProp(n0, "value", (_foo = _ctx.foo))', + ) }) test('.prop modifier (shorthand) w/ progress value', () => { const { code } = compileWithVBind(``) expect(code).matchSnapshot() - expect(code).contains('_setDOMProp(n0, "value", _ctx.foo)') + expect(code).contains( + '_foo !== _ctx.foo && _setDOMProp(n0, "value", (_foo = _ctx.foo))', + ) }) test('.attr modifier', () => { @@ -545,7 +579,9 @@ describe('compiler v-bind', () => { }, }) expect(code).contains('renderEffect') - expect(code).contains('_setAttr(n0, "foo-bar", _ctx.id)') + expect(code).contains( + '_id !== _ctx.id && _setAttr(n0, "foo-bar", (_id = _ctx.id))', + ) }) test('.attr modifier w/ no expression', () => { @@ -570,31 +606,41 @@ describe('compiler v-bind', () => { }) expect(code).contains('renderEffect') - expect(code).contains('_setAttr(n0, "foo-bar", _ctx.fooBar)') + expect(code).contains( + '_fooBar !== _ctx.fooBar && _setAttr(n0, "foo-bar", (_fooBar = _ctx.fooBar))', + ) }) test('.attr modifier w/ innerHTML', () => { const { code } = compileWithVBind(`
`) expect(code).matchSnapshot() - expect(code).contains('_setAttr(n0, "innerHTML", _ctx.foo)') + expect(code).contains( + '_foo !== _ctx.foo && _setAttr(n0, "innerHTML", (_foo = _ctx.foo))', + ) }) test('.attr modifier w/ textContent', () => { const { code } = compileWithVBind(`
`) expect(code).matchSnapshot() - expect(code).contains('_setAttr(n0, "textContent", _ctx.foo)') + expect(code).contains( + '_foo !== _ctx.foo && _setAttr(n0, "textContent", (_foo = _ctx.foo))', + ) }) test('.attr modifier w/ value', () => { const { code } = compileWithVBind(`
`) expect(code).matchSnapshot() - expect(code).contains('_setAttr(n0, "value", _ctx.foo)') + expect(code).contains( + '_foo !== _ctx.foo && _setAttr(n0, "value", (_foo = _ctx.foo))', + ) }) test('.attr modifier w/ progress value', () => { const { code } = compileWithVBind(``) expect(code).matchSnapshot() - expect(code).contains('_setAttr(n0, "value", _ctx.foo)') + expect(code).contains( + '_foo !== _ctx.foo && _setAttr(n0, "value", (_foo = _ctx.foo))', + ) }) test('attributes must be set as attribute', () => { @@ -609,20 +655,35 @@ describe('compiler v-bind', () => { `) 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( + '_spellcheck !== _ctx.spellcheck && _setAttr(n0, "spellcheck", (_spellcheck = _ctx.spellcheck))', + ) + expect(code).contains( + '_draggable !== _ctx.draggable && _setAttr(n0, "draggable", (_draggable = _ctx.draggable))', + ) + expect(code).contains( + '_translate !== _ctx.translate && _setAttr(n0, "translate", (_translate = _ctx.translate))', + ) + expect(code).contains( + '_form !== _ctx.form && _setAttr(n0, "form", (_form = _ctx.form))', + ) + expect(code).contains( + '_list !== _ctx.list && _setAttr(n1, "list", (_list = _ctx.list))', + ) + expect(code).contains( + '_type !== _ctx.type && _setAttr(n2, "type", (_type = _ctx.type))', + ) + expect(code).contains('if(_width !== _ctx.width) {') + expect(code).contains('if(_height !== _ctx.height) {') + expect(code).contains('_height = _ctx.height') + expect(code).contains('_height = _ctx.height') 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)') + expect(code).contains(' _setAttr(n6, "width", _ctx.width)') }) test('HTML global attributes should set as dom prop', () => { @@ -631,11 +692,21 @@ describe('compiler v-bind', () => { `) expect(code).matchSnapshot() - 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)') + expect(code).contains( + '_id !== _ctx.id && _setDOMProp(n0, "id", (_id = _ctx.id))', + ) + expect(code).contains( + '_title !== _ctx.title && _setDOMProp(n0, "title", (_title = _ctx.title))', + ) + expect(code).contains( + '_lang !== _ctx.lang && _setDOMProp(n0, "lang", (_lang = _ctx.lang))', + ) + expect(code).contains( + '_dir !== _ctx.dir && _setDOMProp(n0, "dir", (_dir = _ctx.dir))', + ) + expect(code).contains( + '_tabindex !== _ctx.tabindex && _setDOMProp(n0, "tabindex", (_tabindex = _ctx.tabindex))', + ) }) test('SVG global attributes should set as dom prop', () => { @@ -644,9 +715,15 @@ describe('compiler v-bind', () => { `) expect(code).matchSnapshot() - expect(code).contains('_setDOMProp(n0, "id", _ctx.id)') - expect(code).contains('_setDOMProp(n0, "lang", _ctx.lang)') - expect(code).contains('_setDOMProp(n0, "tabindex", _ctx.tabindex)') + expect(code).contains( + '_id !== _ctx.id && _setDOMProp(n0, "id", (_id = _ctx.id))', + ) + expect(code).contains( + '_lang !== _ctx.lang && _setDOMProp(n0, "lang", (_lang = _ctx.lang))', + ) + expect(code).contains( + '_tabindex !== _ctx.tabindex && _setDOMProp(n0, "tabindex", (_tabindex = _ctx.tabindex))', + ) }) test('MathML global attributes should set as dom prop', () => { @@ -655,11 +732,21 @@ describe('compiler v-bind', () => { `) expect(code).matchSnapshot() - 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)') + expect(code).contains( + '_autofucus !== _ctx.autofucus && _setDOMProp(n0, "autofucus", (_autofucus = _ctx.autofucus))', + ) + expect(code).contains( + '_dir !== _ctx.dir && _setDOMProp(n0, "dir", (_dir = _ctx.dir))', + ) + expect(code).contains( + '_displaystyle !== _ctx.displaystyle && _setDOMProp(n0, "displaystyle", (_displaystyle = _ctx.displaystyle))', + ) + expect(code).contains( + '_mathcolor !== _ctx.mathcolor && _setDOMProp(n0, "mathcolor", (_mathcolor = _ctx.mathcolor))', + ) + expect(code).contains( + '_tabindex !== _ctx.tabindex && _setDOMProp(n0, "tabindex", (_tabindex = _ctx.tabindex))', + ) }) test(':innerHTML', () => { @@ -667,7 +754,10 @@ describe('compiler v-bind', () => {
`) expect(code).matchSnapshot() - expect(code).contains('_setHtml(n0, _ctx.foo)') + expect(code).contains('let _foo') + expect(code).contains( + '_foo !== _ctx.foo && _setHtml(n0, (_foo = _ctx.foo))', + ) }) test(':textContext', () => { @@ -675,7 +765,10 @@ describe('compiler v-bind', () => {
`) expect(code).matchSnapshot() - expect(code).contains('_setText(n0, _ctx.foo)') + expect(code).contains('let _foo') + expect(code).contains( + '_foo !== _ctx.foo && _setText(n0, (_foo = _ctx.foo))', + ) }) test(':value', () => { @@ -683,7 +776,10 @@ describe('compiler v-bind', () => { `) expect(code).matchSnapshot() - expect(code).contains('_setValue(n0, _ctx.foo)') + expect(code).contains('let _foo') + expect(code).contains( + '_foo !== _ctx.foo && _setValue(n0, (_foo = _ctx.foo))', + ) }) test(':value w/ progress', () => { @@ -691,7 +787,19 @@ describe('compiler v-bind', () => { `) expect(code).matchSnapshot() - expect(code).contains('_setDynamicProp(n0, "value", _ctx.foo)') + expect(code).contains( + '_foo !== _ctx.foo && (_foo = _setDynamicProp(n0, "value", _foo, _ctx.foo))', + ) + }) + + test('bind member expression', () => { + const { code } = compileWithVBind(` +
/> + `) + expect(code).matchSnapshot() + expect(code).contains( + '_obj !== _ctx.obj.count.bar && _setDOMProp(n0, "id", (_obj = _ctx.obj.count.bar))', + ) }) test('number value', () => { diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts index 58cae3b88..14bc78fd0 100644 --- a/packages/compiler-vapor/src/generate.ts +++ b/packages/compiler-vapor/src/generate.ts @@ -2,7 +2,7 @@ import type { CodegenOptions as BaseCodegenOptions, BaseCodegenResult, } from '@vue/compiler-dom' -import type { BlockIRNode, RootIRNode, VaporHelper } from './ir' +import type { BlockIRNode, IREffect, RootIRNode, VaporHelper } from './ir' import { extend, remove } from '@vue/shared' import { genBlockContent } from './generators/block' import { genTemplates } from './generators/template' @@ -35,6 +35,15 @@ export class CodegenContext { delegates: Set = new Set() + processingRenderEffect: IREffect | undefined = undefined + allRenderEffectSeenNames: Record = Object.create(null) + shouldCacheRenderEffectDeps = (): boolean => { + // only need to generate effect deps when it's not nested in v-for + return !!( + this.processingRenderEffect && !this.processingRenderEffect.inVFor + ) + } + identifiers: Record = Object.create(null) block: BlockIRNode diff --git a/packages/compiler-vapor/src/generators/expression.ts b/packages/compiler-vapor/src/generators/expression.ts index d13a66412..ef0176263 100644 --- a/packages/compiler-vapor/src/generators/expression.ts +++ b/packages/compiler-vapor/src/generators/expression.ts @@ -1,4 +1,4 @@ -import { isGloballyAllowed } from '@vue/shared' +import { isArray, isGloballyAllowed } from '@vue/shared' import { BindingTypes, NewlineType, @@ -95,7 +95,14 @@ export function genExpression( ) if (i === ids.length - 1 && end < content.length) { - push([content.slice(end), NewlineType.Unknown]) + const rest = content.slice(end) + const last = frag[frag.length - 1] + if (hasMemberExpression && isArray(last)) { + // merge rest content into the last identifier's generated name + last[0] += rest + } else { + push([rest, NewlineType.Unknown]) + } } }) diff --git a/packages/compiler-vapor/src/generators/html.ts b/packages/compiler-vapor/src/generators/html.ts index 1b129375a..9175ae372 100644 --- a/packages/compiler-vapor/src/generators/html.ts +++ b/packages/compiler-vapor/src/generators/html.ts @@ -2,18 +2,17 @@ import type { CodegenContext } from '../generate' import type { SetHtmlIRNode } from '../ir' import { genExpression } from './expression' import { type CodeFragment, NEWLINE, genCall } from './utils' +import { processValues } from './prop' export function genSetHtml( oper: SetHtmlIRNode, context: CodegenContext, ): CodeFragment[] { - const { vaporHelper } = context - return [ - NEWLINE, - ...genCall( - vaporHelper('setHtml'), - `n${oper.element}`, - genExpression(oper.value, context), - ), - ] + const { vaporHelper, shouldCacheRenderEffectDeps } = context + const { value, element } = oper + let html = genExpression(value, context) + if (shouldCacheRenderEffectDeps()) { + processValues(context, [html]) + } + return [NEWLINE, ...genCall(vaporHelper('setHtml'), `n${element}`, html)] } diff --git a/packages/compiler-vapor/src/generators/operation.ts b/packages/compiler-vapor/src/generators/operation.ts index b637e797a..8dc196e59 100644 --- a/packages/compiler-vapor/src/generators/operation.ts +++ b/packages/compiler-vapor/src/generators/operation.ts @@ -78,9 +78,33 @@ export function genEffects( effects: IREffect[], context: CodegenContext, ): CodeFragment[] { - const [frag, push] = buildCodeFragment() - for (const effect of effects) { - push(...genEffect(effect, context)) + const { vaporHelper } = context + const [frag, push, unshift] = buildCodeFragment() + const declareNames = new Set() + let operationsCount = 0 + for (let i = 0; i < effects.length; i++) { + const effect = (context.processingRenderEffect = effects[i]) + operationsCount += effect.operations.length + const frags = genEffect(effect, context, declareNames) + const needSemi = frag[frag.length - 1] === ')' && frags[0] === '(' + i > 0 && push(NEWLINE) + push(needSemi ? ';' : undefined, ...frags) + } + + const newLineCount = frag.filter(frag => frag === NEWLINE).length + if (newLineCount > 1 || operationsCount > 1) { + unshift(`{`, INDENT_START, NEWLINE) + push(INDENT_END, NEWLINE, '}') + } + + if (effects.length) { + unshift(NEWLINE, `${vaporHelper('renderEffect')}(() => `) + push(`)`) + } + + // declare variables: let _foo, _bar + if (declareNames.size) { + frag.splice(1, 0, `let ${[...declareNames].join(', ')}`, NEWLINE) } return frag } @@ -88,21 +112,50 @@ export function genEffects( export function genEffect( { operations }: IREffect, context: CodegenContext, + allDeclareNames: Set, ): CodeFragment[] { - const { vaporHelper } = context - const [frag, push] = buildCodeFragment( - NEWLINE, - `${vaporHelper('renderEffect')}(() => `, - ) + const { processingRenderEffect } = context + const [frag, push] = buildCodeFragment() + const { declareNames, earlyCheckExps } = processingRenderEffect! + const operationsExps = genOperations(operations, context) - const [operationsExps, pushOps] = buildCodeFragment() - operations.forEach(op => pushOps(...genOperation(op, context))) + if (declareNames.size) { + allDeclareNames.add([...declareNames].join(', ')) + } const newlineCount = operationsExps.filter(frag => frag === NEWLINE).length if (newlineCount > 1) { - push('{', INDENT_START, ...operationsExps, INDENT_END, NEWLINE, '})') + // multiline check expression: if (_foo !== _ctx.foo || _bar !== _ctx.bar) { + const checkExpsStart: CodeFragment[] = + earlyCheckExps.length > 0 + ? [`if(`, ...earlyCheckExps.join(' || '), `) {`, INDENT_START] + : [] + const checkExpsEnd: CodeFragment[] = + earlyCheckExps.length > 0 ? [INDENT_END, NEWLINE, '}'] : [] + // assignment: _foo = _ctx.foo; _bar = _ctx.bar + const assignmentExps: CodeFragment[] = + earlyCheckExps.length > 0 + ? [NEWLINE, ...earlyCheckExps.map(c => c.replace('!==', '=')).join(';')] + : [] + push( + ...checkExpsStart, + ...operationsExps, + ...assignmentExps, + ...checkExpsEnd, + ) } else { - push(...operationsExps.filter(frag => frag !== NEWLINE), ')') + // single line check expression: (_foo !== _ctx.foo || _bar !== _ctx.bar) && + const multiple = earlyCheckExps.length > 1 + const checkExps: CodeFragment[] = + earlyCheckExps.length > 0 + ? [ + multiple ? `(` : undefined, + ...earlyCheckExps.join(' || '), + multiple ? `)` : undefined, + ' && ', + ] + : [] + push(...checkExps, ...operationsExps.filter(frag => frag !== NEWLINE)) } return frag diff --git a/packages/compiler-vapor/src/generators/prop.ts b/packages/compiler-vapor/src/generators/prop.ts index e30d4c7ed..3a0964dd1 100644 --- a/packages/compiler-vapor/src/generators/prop.ts +++ b/packages/compiler-vapor/src/generators/prop.ts @@ -24,6 +24,7 @@ import { import { attributeCache, canSetValueDirectly, + isArray, isHTMLGlobalAttr, isHTMLTag, isMathMLGlobalAttr, @@ -44,20 +45,30 @@ export function genSetProp( prop: { key, values, modifier }, tag, } = oper - const { helperName, omitKey } = getRuntimeHelper(tag, key.content, modifier) + const propValue = genPropValue(values, context) + const { prevValueName, shouldWrapInParentheses } = processPropValues( + context, + helperName, + [propValue], + ) return [ NEWLINE, + ...(prevValueName + ? [shouldWrapInParentheses ? `(` : undefined, `${prevValueName} = `] + : []), ...genCall( [vaporHelper(helperName), null], `n${oper.element}`, omitKey ? false : genExpression(key, context), - genPropValue(values, context), + ...(prevValueName ? [`${prevValueName}`] : []), + propValue, // only `setClass` and `setStyle` need merge inherit attr oper.root && (helperName === 'setClass' || helperName === 'setStyle') ? 'true' : undefined, ), + ...(prevValueName && shouldWrapInParentheses ? [`)`] : []), ] } @@ -67,24 +78,31 @@ export function genDynamicProps( context: CodegenContext, ): CodeFragment[] { const { vaporHelper } = context + const values = oper.props.map(props => + Array.isArray(props) + ? genLiteralObjectProps(props, context) // static and dynamic arg props + : props.kind === IRDynamicPropsKind.ATTRIBUTE + ? genLiteralObjectProps([props], context) // dynamic arg props + : genExpression(props.value, context), + ) // v-bind="" + const { prevValueName, shouldWrapInParentheses } = processPropValues( + context, + 'setDynamicProps', + values, + ) return [ NEWLINE, + ...(prevValueName + ? [shouldWrapInParentheses ? `(` : undefined, `${prevValueName} = `] + : []), ...genCall( vaporHelper('setDynamicProps'), `n${oper.element}`, - genMulti( - DELIMITERS_ARRAY, - ...oper.props.map( - props => - Array.isArray(props) - ? genLiteralObjectProps(props, context) // static and dynamic arg props - : props.kind === IRDynamicPropsKind.ATTRIBUTE - ? genLiteralObjectProps([props], context) // dynamic arg props - : genExpression(props.value, context), // v-bind="" - ), - ), + ...(prevValueName ? [`${prevValueName}`] : []), + genMulti(DELIMITERS_ARRAY, ...values), oper.root && 'true', ), + ...(prevValueName && shouldWrapInParentheses ? [`)`] : []), ] } @@ -235,3 +253,95 @@ const getSpecialHelper = ( return specialHelpers[keyName] || null } + +// those runtime helpers will return the prevValue +const helpersNeedCachedReturnValue = [ + 'setStyle', + 'setDynamicProp', + 'setDynamicProps', +] + +function processPropValues( + context: CodegenContext, + helperName: string, + values: CodeFragment[][], +): { prevValueName: string | undefined; shouldWrapInParentheses: boolean } { + const { shouldCacheRenderEffectDeps, processingRenderEffect } = context + // single-line render effect and the operation needs cache return a value, + // the expression needs to be wrapped in parentheses. + // e.g. _foo === _ctx.foo && (_foo = _setStyle(...)) + let shouldWrapInParentheses: boolean = false + let prevValueName + if (shouldCacheRenderEffectDeps()) { + const needReturnValue = helpersNeedCachedReturnValue.includes(helperName) + processValues(context, values, !needReturnValue) + const { declareNames } = processingRenderEffect! + // if the operation needs to cache the return value and has multiple declareNames, + // combine them into a single name as the return value name. + if (declareNames.size > 0 && needReturnValue) { + prevValueName = [...declareNames].join('') + declareNames.add(prevValueName) + } + shouldWrapInParentheses = processingRenderEffect!.operations.length === 1 + } + return { prevValueName, shouldWrapInParentheses } +} + +export function processValues( + context: CodegenContext, + values: CodeFragment[][], + needRewrite: boolean = true, +): string[] { + const allCheckExps: string[] = [] + values.forEach(value => { + const checkExps = processValue(context, value, needRewrite) + if (checkExps) allCheckExps.push(...checkExps, ' && ') + }) + + return allCheckExps.length > 0 + ? (context.processingRenderEffect!.earlyCheckExps = [ + ...new Set(allCheckExps), + ]) + : [] +} + +function processValue( + context: CodegenContext, + values: CodeFragment[], + needRewrite: boolean = true, +): string[] | undefined { + const { processingRenderEffect, allRenderEffectSeenNames } = context + const { declareNames, rewrittenNames, earlyCheckExps, operations } = + processingRenderEffect! + + const isSingleLine = operations.length === 1 + for (const frag of values) { + if (!isArray(frag)) continue + // [code, newlineIndex, loc, name] -> [(_name = code), newlineIndex, loc, name] + const [newName, , , rawName] = frag + if (rawName) { + let name = rawName.replace(/[^\w]/g, '_') + if (rewrittenNames.has(name)) continue + rewrittenNames.add(name) + + name = `_${name}` + if (declareNames.has(name)) continue + + if (allRenderEffectSeenNames[name] === undefined) + allRenderEffectSeenNames[name] = 0 + else name += ++allRenderEffectSeenNames[name] + + declareNames.add(name) + earlyCheckExps.push(`${name} !== ${newName}`) + + if (needRewrite && isSingleLine) { + // replace the original code fragment with the assignment expression + frag[0] = `(${name} = ${newName})` + } + } + } + + if (earlyCheckExps.length > 0) { + return [[...new Set(earlyCheckExps)].join(' && ')] + } +} diff --git a/packages/compiler-vapor/src/generators/text.ts b/packages/compiler-vapor/src/generators/text.ts index e433bb977..7eaaaa7fc 100644 --- a/packages/compiler-vapor/src/generators/text.ts +++ b/packages/compiler-vapor/src/generators/text.ts @@ -8,21 +8,19 @@ import { genCall, genMulti, } from './utils' +import { processValues } from './prop' export function genSetText( oper: SetTextIRNode, context: CodegenContext, ): CodeFragment[] { - const { vaporHelper } = context + const { vaporHelper, shouldCacheRenderEffectDeps } = context const { element, values } = oper - return [ - NEWLINE, - ...genCall( - vaporHelper('setText'), - `n${element}`, - ...values.map(value => genExpression(value, context)), - ), - ] + const texts = values.map(value => genExpression(value, context)) + if (shouldCacheRenderEffectDeps()) { + processValues(context, texts) + } + return [NEWLINE, ...genCall(vaporHelper('setText'), `n${element}`, ...texts)] } export function genCreateTextNode( diff --git a/packages/compiler-vapor/src/generators/utils.ts b/packages/compiler-vapor/src/generators/utils.ts index f001d8e92..904b3dc87 100644 --- a/packages/compiler-vapor/src/generators/utils.ts +++ b/packages/compiler-vapor/src/generators/utils.ts @@ -29,9 +29,14 @@ export type CodeFragments = Exclude | CodeFragment[] export function buildCodeFragment( ...frag: CodeFragment[] -): [CodeFragment[], (...items: CodeFragment[]) => number] { +): [ + CodeFragment[], + (...items: CodeFragment[]) => number, + (...items: CodeFragment[]) => number, +] { const push = frag.push.bind(frag) - return [frag, push] + const unshift = frag.unshift.bind(frag) + return [frag, push, unshift] } export type CodeFragmentDelimiters = [ diff --git a/packages/compiler-vapor/src/ir/index.ts b/packages/compiler-vapor/src/ir/index.ts index 7d1ddac89..d71d5b556 100644 --- a/packages/compiler-vapor/src/ir/index.ts +++ b/packages/compiler-vapor/src/ir/index.ts @@ -266,7 +266,12 @@ export interface IRDynamicInfo { export interface IREffect { expressions: SimpleExpressionNode[] + identifiers: string[] operations: OperationNode[] + declareNames: Set + rewrittenNames: Set + earlyCheckExps: string[] + inVFor: boolean } type Overwrite = Pick> & diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index f4290ab89..0b4f71f8d 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -5,6 +5,7 @@ import { type CompilerCompatOptions, type ElementNode, ElementTypes, + type ExpressionNode, NodeTypes, type RootNode, type SimpleExpressionNode, @@ -12,8 +13,16 @@ import { defaultOnError, defaultOnWarn, isVSlot, + walkIdentifiers, } from '@vue/compiler-dom' -import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared' +import { + EMPTY_OBJ, + NOOP, + extend, + isArray, + isString, + looseEqual, +} from '@vue/shared' import { type BlockIRNode, DynamicFlag, @@ -142,8 +151,10 @@ export class TransformContext { if (this.inVOnce || expressions.length === 0) { return this.registerOperation(...operations) } + const ids = new Set() + expressions.forEach(exp => extractIdentifiers(ids, exp)) const existing = this.block.effect.find(e => - isSameExpression(e.expressions, expressions), + looseEqual(e.identifiers, Array.from(ids)), ) if (existing) { existing.operations.push(...operations) @@ -151,16 +162,13 @@ export class TransformContext { this.block.effect.push({ expressions, operations, + earlyCheckExps: [], + declareNames: new Set(), + rewrittenNames: new Set(), + inVFor: this.inVFor > 0, + identifiers: Array.from(ids), }) } - - function isSameExpression( - a: SimpleExpressionNode[], - b: SimpleExpressionNode[], - ) { - if (a.length !== b.length) return false - return a.every((exp, i) => exp.content === b[i].content) - } } registerOperation(...node: OperationNode[]): void { this.block.operation.push(...node) @@ -296,3 +304,11 @@ export function createStructuralDirectiveTransform( } } } + +function extractIdentifiers(ids: Set, node: ExpressionNode) { + if (node.ast) { + walkIdentifiers(node.ast, n => ids.add(n.name), true) + } else if (node.ast === null) { + ids.add((node as SimpleExpressionNode).content) + } +} diff --git a/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts b/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts index 3444c3407..e94cd2196 100644 --- a/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts +++ b/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts @@ -78,7 +78,8 @@ describe('api: setup context', () => { inheritAttrs: false, setup(props, { attrs }) { const el = document.createElement('div') - renderEffect(() => setDynamicProps(el, [attrs])) + let prev: any + renderEffect(() => (prev = setDynamicProps(el, prev, [attrs]))) return el }, }) @@ -115,7 +116,10 @@ describe('api: setup context', () => { const n0 = createComponent(Wrapper, null, { default: () => { const n0 = template('
')() as HTMLDivElement - renderEffect(() => setDynamicProps(n0, [attrs], true)) + let prev: any + renderEffect( + () => (prev = setDynamicProps(n0, prev, [attrs], true)), + ) return n0 }, }) diff --git a/packages/runtime-vapor/__tests__/dom/prop.spec.ts b/packages/runtime-vapor/__tests__/dom/prop.spec.ts index 50a39cea9..6f6666e4c 100644 --- a/packages/runtime-vapor/__tests__/dom/prop.spec.ts +++ b/packages/runtime-vapor/__tests__/dom/prop.spec.ts @@ -14,7 +14,6 @@ import { ComponentInternalInstance, setCurrentInstance, } from '../../src/component' -import { getMetadata, recordPropMetadata } from '../../src/componentMetadata' import { getCurrentScope } from '@vue/reactivity' let removeComponentInstance = NOOP @@ -35,34 +34,6 @@ afterEach(() => { }) describe('patchProp', () => { - describe('recordPropMetadata', () => { - test('should record prop metadata', () => { - const node = {} as Node // the node is just a key - let prev = recordPropMetadata(node, 'class', 'foo') - expect(prev).toBeUndefined() - prev = recordPropMetadata(node, 'class', 'bar') - expect(prev).toBe('foo') - prev = recordPropMetadata(node, 'style', 'color: red') - expect(prev).toBeUndefined() - prev = recordPropMetadata(node, 'style', 'color: blue') - expect(prev).toBe('color: red') - - expect(getMetadata(node)).toEqual([ - { class: 'bar', style: 'color: blue' }, - {}, - ]) - }) - - test('should have different metadata for different nodes', () => { - const node1 = {} as Node - const node2 = {} as Node - recordPropMetadata(node1, 'class', 'foo') - recordPropMetadata(node2, 'class', 'bar') - expect(getMetadata(node1)).toEqual([{ class: 'foo' }, {}]) - expect(getMetadata(node2)).toEqual([{ class: 'bar' }, {}]) - }) - }) - describe('setClass', () => { test('should set class', () => { const el = document.createElement('div') @@ -78,83 +49,87 @@ describe('patchProp', () => { describe('setStyle', () => { test('should set style', () => { const el = document.createElement('div') - setStyle(el, 'color: red') + setStyle(el, '', 'color: red') expect(el.style.cssText).toBe('color: red;') }) test('should work with camelCase', () => { const el = document.createElement('div') - setStyle(el, { fontSize: '12px' }) + setStyle(el, null, { fontSize: '12px' }) expect(el.style.cssText).toBe('font-size: 12px;') }) test('shoud set style with object and array property', () => { const el = document.createElement('div') - setStyle(el, { color: 'red' }) + let prev: any + prev = setStyle(el, prev, { color: 'red' }) expect(el.style.cssText).toBe('color: red;') - setStyle(el, [{ color: 'blue' }, { fontSize: '12px' }]) + setStyle(el, prev, [{ color: 'blue' }, { fontSize: '12px' }]) expect(el.style.cssText).toBe('color: blue; font-size: 12px;') }) test('should remove if falsy value', () => { const el = document.createElement('div') - setStyle(el, { color: undefined, borderRadius: null }) + let prev + prev = setStyle(el, prev, { color: undefined, borderRadius: null }) expect(el.style.cssText).toBe('') - setStyle(el, { color: 'red' }) + prev = setStyle(el, prev, { color: 'red' }) expect(el.style.cssText).toBe('color: red;') - setStyle(el, { color: undefined, borderRadius: null }) + setStyle(el, prev, { color: undefined, borderRadius: null }) expect(el.style.cssText).toBe('') }) test('should work with !important', () => { const el = document.createElement('div') - setStyle(el, { color: 'red !important' }) + setStyle(el, null, { color: 'red !important' }) expect(el.style.cssText).toBe('color: red !important;') }) test('should work with camelCase and !important', () => { const el = document.createElement('div') - setStyle(el, { fontSize: '12px !important' }) + setStyle(el, null, { fontSize: '12px !important' }) expect(el.style.cssText).toBe('font-size: 12px !important;') }) test('should work with multiple entries', () => { const el = document.createElement('div') - setStyle(el, { color: 'red', marginRight: '10px' }) + setStyle(el, null, { color: 'red', marginRight: '10px' }) expect(el.style.getPropertyValue('color')).toBe('red') expect(el.style.getPropertyValue('margin-right')).toBe('10px') }) test('should patch with falsy style value', () => { const el = document.createElement('div') - setStyle(el, { width: '100px' }) + let prev: any + prev = setStyle(el, prev, { width: '100px' }) expect(el.style.cssText).toBe('width: 100px;') - setStyle(el, { width: 0 }) + prev = setStyle(el, prev, { width: 0 }) expect(el.style.cssText).toBe('width: 0px;') }) test('should remove style attribute on falsy value', () => { const el = document.createElement('div') - setStyle(el, { width: '100px' }) + let prev: any + prev = setStyle(el, prev, { width: '100px' }) expect(el.style.cssText).toBe('width: 100px;') - setStyle(el, { width: undefined }) + prev = setStyle(el, prev, { width: undefined }) expect(el.style.cssText).toBe('') - setStyle(el, { width: '100px' }) + prev = setStyle(el, prev, { width: '100px' }) expect(el.style.cssText).toBe('width: 100px;') - setStyle(el, null) + setStyle(el, prev, null) expect(el.hasAttribute('style')).toBe(false) expect(el.style.cssText).toBe('') }) test('should warn for trailing semicolons', () => { const el = document.createElement('div') - setStyle(el, { color: 'red;' }) + setStyle(el, null, { color: 'red;' }) expect( `Unexpected semicolon at the end of 'color' style value: 'red;'`, ).toHaveBeenWarned() - setStyle(el, { '--custom': '100; ' }) + setStyle(el, null, { '--custom': '100; ' }) expect( `Unexpected semicolon at the end of '--custom' style value: '100; '`, ).toHaveBeenWarned() @@ -162,13 +137,16 @@ describe('patchProp', () => { test('should not warn for trailing semicolons', () => { const el = document.createElement('div') - setStyle(el, { '--custom': '100\\;' }) + setStyle(el, null, { '--custom': '100\\;' }) expect(el.style.getPropertyValue('--custom')).toBe('100\\;') }) test('should work with shorthand properties', () => { const el = document.createElement('div') - setStyle(el, { borderBottom: '1px solid red', border: '1px solid green' }) + setStyle(el, null, { + borderBottom: '1px solid red', + border: '1px solid green', + }) expect(el.style.border).toBe('1px solid green') expect(el.style.borderBottom).toBe('1px solid green') }) @@ -193,19 +171,21 @@ describe('patchProp', () => { test('should work with css custom properties', () => { const el = mockElementWithStyle() - setStyle(el as any, { '--theme': 'red' }) + setStyle(el as any, null, { '--theme': 'red' }) expect(el.style.getPropertyValue('--theme')).toBe('red') }) test('should auto vendor prefixing', () => { const el = mockElementWithStyle() - setStyle(el as any, { transition: 'all 1s' }) + setStyle(el as any, null, { transition: 'all 1s' }) expect(el.style.WebkitTransition).toBe('all 1s') }) test('should work with multiple values', () => { const el = mockElementWithStyle() - setStyle(el as any, { display: ['-webkit-box', '-ms-flexbox', 'flex'] }) + setStyle(el as any, null, { + display: ['-webkit-box', '-ms-flexbox', 'flex'], + }) expect(el.style.display).toBe('flex') }) }) @@ -335,12 +315,13 @@ describe('patchProp', () => { describe('setDynamicProp', () => { const element = document.createElement('div') + let prev: any function setDynamicProp( key: string, value: any, el = element.cloneNode(true) as HTMLElement, ) { - _setDynamicProp(el, key, value) + prev = _setDynamicProp(el, key, prev, value) return el } @@ -397,25 +378,25 @@ describe('patchProp', () => { describe('setDynamicProps', () => { test('basic set dynamic props', () => { const el = document.createElement('div') - setDynamicProps(el, [{ foo: 'val' }, { bar: 'val' }]) + setDynamicProps(el, null, [{ foo: 'val' }, { bar: 'val' }]) expect(el.getAttribute('foo')).toBe('val') expect(el.getAttribute('bar')).toBe('val') }) test('should merge props', () => { const el = document.createElement('div') - setDynamicProps(el, [{ foo: 'val' }, { foo: 'newVal' }]) + setDynamicProps(el, null, [{ foo: 'val' }, { foo: 'newVal' }]) expect(el.getAttribute('foo')).toBe('newVal') }) test('should reset old props', () => { const el = document.createElement('div') - - setDynamicProps(el, [{ foo: 'val' }]) + let prev: any + prev = setDynamicProps(el, prev, [{ foo: 'val' }]) expect(el.attributes.length).toBe(1) expect(el.getAttribute('foo')).toBe('val') - setDynamicProps(el, [{ bar: 'val' }]) + prev = setDynamicProps(el, prev, [{ bar: 'val' }]) expect(el.attributes.length).toBe(1) expect(el.getAttribute('bar')).toBe('val') expect(el.getAttribute('foo')).toBeNull() @@ -424,18 +405,19 @@ describe('patchProp', () => { test('should reset old modifier props', () => { const el = document.createElement('div') - setDynamicProps(el, [{ ['.foo']: 'val' }]) + let prev: any + prev = setDynamicProps(el, prev, [{ ['.foo']: 'val' }]) expect((el as any).foo).toBe('val') - setDynamicProps(el, [{ ['.bar']: 'val' }]) + prev = setDynamicProps(el, prev, [{ ['.bar']: 'val' }]) expect((el as any).bar).toBe('val') expect((el as any).foo).toBe('') - setDynamicProps(el, [{ ['^foo']: 'val' }]) + prev = setDynamicProps(el, prev, [{ ['^foo']: 'val' }]) expect(el.attributes.length).toBe(1) expect(el.getAttribute('foo')).toBe('val') - setDynamicProps(el, [{ ['^bar']: 'val' }]) + prev = setDynamicProps(el, prev, [{ ['^bar']: 'val' }]) expect(el.attributes.length).toBe(1) expect(el.getAttribute('bar')).toBe('val') expect(el.getAttribute('foo')).toBeNull() diff --git a/packages/runtime-vapor/src/componentAttrs.ts b/packages/runtime-vapor/src/componentAttrs.ts index 635b45a7a..683597783 100644 --- a/packages/runtime-vapor/src/componentAttrs.ts +++ b/packages/runtime-vapor/src/componentAttrs.ts @@ -119,6 +119,7 @@ export function fallThroughAttrs( } } + let prevAttrs = instance.attrs renderEffect(() => { for (const key in instance.attrs) { if (dynamicAttrs && dynamicAttrs.includes(key)) continue @@ -130,7 +131,7 @@ export function fallThroughAttrs( value = instance.attrs[key] } - setDynamicProp(element, key, value) + setDynamicProp(element, key, prevAttrs[key], value) } }) } diff --git a/packages/runtime-vapor/src/componentFallback.ts b/packages/runtime-vapor/src/componentFallback.ts index adceb5446..ce0e82e5d 100644 --- a/packages/runtime-vapor/src/componentFallback.ts +++ b/packages/runtime-vapor/src/componentFallback.ts @@ -24,6 +24,7 @@ export function fallbackComponent( if (rawProps || Object.keys(instance.attrs).length) { rawProps = [() => instance.attrs, ...normalizeRawProps(rawProps)] + let prevValue: any, prevStyle: any renderEffect(() => { let classes: unknown[] | undefined let styles: unknown[] | undefined @@ -34,12 +35,16 @@ export function fallbackComponent( const value = getter ? valueOrGetter() : valueOrGetter if (key === 'class') (classes ||= []).push(value) else if (key === 'style') (styles ||= []).push(value) - else setDynamicProp(el, key, value) + else { + prevValue = setDynamicProp(el, key, prevValue, value) + } }, ) if (classes) setClass(el, classes) - if (styles) setStyle(el, styles) + if (styles) { + prevStyle = setStyle(el, prevStyle, styles) + } }) } diff --git a/packages/runtime-vapor/src/componentMetadata.ts b/packages/runtime-vapor/src/componentMetadata.ts index ab2ad0bc6..4160d083c 100644 --- a/packages/runtime-vapor/src/componentMetadata.ts +++ b/packages/runtime-vapor/src/componentMetadata.ts @@ -18,13 +18,6 @@ export function getMetadata( return el.$$metadata || (el.$$metadata = [{}, {}]) } -export function recordPropMetadata(el: Node, key: string, value: any): any { - const metadata = getMetadata(el)[MetadataKind.prop] - const prev = metadata[key] - if (prev !== value) metadata[key] = value - return prev -} - export function recordEventMetadata(el: Node, key: string, value: any) { const metadata = getMetadata(el)[MetadataKind.event] const handlers = (metadata[key] ||= []) diff --git a/packages/runtime-vapor/src/dom/prop.ts b/packages/runtime-vapor/src/dom/prop.ts index bd6ce1f12..e43cff1d9 100644 --- a/packages/runtime-vapor/src/dom/prop.ts +++ b/packages/runtime-vapor/src/dom/prop.ts @@ -14,11 +14,6 @@ import { } from '@vue/shared' import { warn } from '../warning' import { setStyle } from './style' -import { - MetadataKind, - getMetadata, - recordPropMetadata, -} from '../componentMetadata' import { on } from './event' import type { Data } from '@vue/runtime-shared' import { currentInstance } from '../component' @@ -29,32 +24,18 @@ export function mergeInheritAttr(key: string, value: any): unknown { } export function setClass(el: Element, value: any, root?: boolean): void { - const prev = recordPropMetadata( - el, - 'class', - (value = normalizeClass(root ? mergeInheritAttr('class', value) : value)), - ) - - if (value !== prev && (value || prev)) { - el.className = value - } + el.className = normalizeClass(root ? mergeInheritAttr('class', value) : value) } export function setAttr(el: Element, key: string, value: any): void { - const oldVal = recordPropMetadata(el, key, value) - if (value !== oldVal) { - if (value != null) { - el.setAttribute(key, value) - } else { - el.removeAttribute(key) - } + if (value != null) { + el.setAttribute(key, value) + } else { + el.removeAttribute(key) } } 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 @@ -71,9 +52,6 @@ export function setValue(el: any, value: any): void { } export function setDOMProp(el: any, key: string, value: any): void { - const oldVal = recordPropMetadata(el, key, value) - if (value === oldVal) return - let needRemove = false if (value === '' || value == null) { const type = typeof el[key] @@ -109,13 +87,18 @@ export function setDOMProp(el: any, key: string, value: any): void { needRemove && el.removeAttribute(key) } -export function setDynamicProp(el: Element, key: string, value: any): void { +export function setDynamicProp( + el: Element, + key: string, + prev: any, + value: any, +): any { // TODO const isSVG = false if (key === 'class') { setClass(el, value) } else if (key === 'style') { - setStyle(el as HTMLElement, value) + return setStyle(el as HTMLElement, prev, value) } else if (isOn(key)) { on(el, key[2].toLowerCase() + key.slice(3), () => value, { effect: true }) } else if ( @@ -150,30 +133,42 @@ export function setDynamicProp(el: Element, key: string, value: any): void { export function setDynamicProps( el: Element, + oldProps: any, args: any[], root?: boolean, ): void { - const oldProps = getMetadata(el)[MetadataKind.prop] + // const oldProps = getMetadata(el)[MetadataKind.prop] if (root) { args.unshift(currentInstance!.attrs) } const props = args.length > 1 ? mergeProps(...args) : args[0] - for (const key in oldProps) { - // TODO should these keys be allowed as dynamic keys? The current logic of the runtime-core will throw an error - if (key === 'textContent' || key === 'innerHTML') { - continue - } + if (oldProps) { + for (const key in oldProps) { + // TODO should these keys be allowed as dynamic keys? The current logic of the runtime-core will throw an error + if (key === 'textContent' || key === 'innerHTML') { + continue + } - const hasNewValue = props[key] || props['.' + key] || props['^' + key] - if (oldProps[key] && !hasNewValue) { - setDynamicProp(el, key, null) + const oldValue = oldProps[key] + const hasNewValue = props[key] || props['.' + key] || props['^' + key] + if (oldValue && !hasNewValue) { + setDynamicProp(el, key, oldValue, null) + } } } + const prev = Object.create(null) for (const key in props) { - setDynamicProp(el, key, props[key]) + setDynamicProp( + el, + key, + oldProps ? oldProps[key] : undefined, + (prev[key] = props[key]), + ) } + + return prev } export function mergeProp( @@ -213,18 +208,11 @@ export function mergeProps(...args: Data[]): Data { } export function setText(el: Node, ...values: any[]): void { - const text = values.map(v => toDisplayString(v)).join('') - const oldVal = recordPropMetadata(el, 'textContent', text) - if (text !== oldVal) { - el.textContent = text - } + el.textContent = values.map(v => toDisplayString(v)).join('') } export function setHtml(el: Element, value: any): void { - const oldVal = recordPropMetadata(el, 'innerHTML', value) - if (value !== oldVal) { - el.innerHTML = value == null ? '' : value - } + el.innerHTML = value == null ? '' : value } // TODO copied from runtime-dom diff --git a/packages/runtime-vapor/src/dom/style.ts b/packages/runtime-vapor/src/dom/style.ts index 5ee233a0c..213dfec4b 100644 --- a/packages/runtime-vapor/src/dom/style.ts +++ b/packages/runtime-vapor/src/dom/style.ts @@ -7,16 +7,17 @@ import { normalizeStyle, } from '@vue/shared' import { warn } from '../warning' -import { recordPropMetadata } from '../componentMetadata' import { mergeInheritAttr } from './prop' -export function setStyle(el: HTMLElement, value: any, root?: boolean): void { - const prev = recordPropMetadata( - el, - 'style', - (value = normalizeStyle(root ? mergeInheritAttr('style', value) : value)), - ) +export function setStyle( + el: HTMLElement, + prev: any, + value: any, + root?: boolean, +): any { + value = normalizeStyle(root ? mergeInheritAttr('style', value) : value) patchStyle(el, prev, value) + return value } // TODO copied from packages/runtime-dom/src/modules/style.ts