From: Evan You Date: Tue, 11 Feb 2025 10:39:27 +0000 (+0800) Subject: perf(vapor): optimize text bindings and v-text X-Git-Tag: v3.6.0-alpha.1~16^2~67 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5d1c6ca5a991c808b21f30a261d29fe7fb48beaf;p=thirdparty%2Fvuejs%2Fcore.git perf(vapor): optimize text bindings and v-text --- diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap index c8c9d254d6..323b9df784 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 { setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue'; -const t0 = _template("
", true) +"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue'; +const t0 = _template("
", true) export function render(_ctx, $props, $emit, $attrs, $slots) { const n0 = t0() - _renderEffect(() => _setText(n0, "count is ", _ctx.count, ".")) + const x0 = _child(n0) + _renderEffect(() => _setText(x0, "count is " + _toDisplayString(_ctx.count) + ".")) return n0 }" `; @@ -148,7 +149,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, createComponentWithFallback as _createComponentWithFallback, createTextNode as _createTextNode, insert as _insert, setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, createTextNode as _createTextNode, insert as _insert, toDisplayString as _toDisplayString, setText as _setText, setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue'; const t0 = _template("
{{ bar }}
") const t1 = _template("
") @@ -157,33 +158,37 @@ export function render(_ctx, $props, $emit, $attrs, $slots) { const n0 = t0() const n3 = t1() const n1 = _createComponentWithFallback(_component_Comp) - const n2 = _createTextNode(() => [_ctx.bar]) + const n2 = _createTextNode() _insert([n1, n2], n3) - _renderEffect(() => _setProp(n3, "id", _ctx.foo)) + _renderEffect(() => { + _setText(n2, _toDisplayString(_ctx.bar)) + _setProp(n3, "id", _ctx.foo) + }) return [n0, n3] }" `; exports[`compile > dynamic root 1`] = ` -"import { createTextNode as _createTextNode } from 'vue'; +"import { createTextNode as _createTextNode, toDisplayString as _toDisplayString } from 'vue'; export function render(_ctx) { - const n0 = _createTextNode(() => [1, 2]) + const n0 = _createTextNode(_toDisplayString(1) + _toDisplayString(2)) return n0 }" `; exports[`compile > dynamic root nodes and interpolation 1`] = ` -"import { setText as _setText, setProp as _setProp, renderEffect as _renderEffect, delegateEvents as _delegateEvents, template as _template } from 'vue'; -const t0 = _template("", true) +"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, setProp as _setProp, renderEffect as _renderEffect, delegateEvents as _delegateEvents, template as _template } from 'vue'; +const t0 = _template("", true) _delegateEvents("click") export function render(_ctx) { const n0 = t0() + const x0 = _child(n0) n0.$evtclick = e => _ctx.handleClick(e) _renderEffect(() => { const _count = _ctx.count - _setText(n0, _count, "foo", _count, "foo", _count) + _setText(x0, _toDisplayString(_count) + "foo" + _toDisplayString(_count) + "foo" + _toDisplayString(_count)) _setProp(n0, "id", _count) }) return n0 @@ -192,7 +197,8 @@ export function render(_ctx) { exports[`compile > expression parsing > interpolation 1`] = ` " - const n0 = _createTextNode(() => [a + b.value]) + const n0 = _createTextNode() + _renderEffect(() => _setText(n0, _toDisplayString(a + b.value))) return n0 " `; @@ -223,10 +229,10 @@ export function render(_ctx) { `; exports[`compile > static + dynamic root 1`] = ` -"import { createTextNode as _createTextNode } from 'vue'; +"import { createTextNode as _createTextNode, toDisplayString as _toDisplayString } from 'vue'; export function render(_ctx) { - const n0 = _createTextNode(() => [1, 2, "3", 4, 5, "6", 7, 8, "9", 'A', 'B']) + const n0 = _createTextNode(_toDisplayString(1) + _toDisplayString(2) + "3" + _toDisplayString(4) + _toDisplayString(5) + "6" + _toDisplayString(7) + _toDisplayString(8) + "9" + 'A' + 'B') return n0 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/expression.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/expression.spec.ts.snap index 53e0ed772c..ec449cbd2d 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/expression.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/expression.spec.ts.snap @@ -1,28 +1,31 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compiler: expression > basic 1`] = ` -"import { createTextNode as _createTextNode } from 'vue'; +"import { createTextNode as _createTextNode, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect } from 'vue'; export function render(_ctx) { - const n0 = _createTextNode(() => [_ctx.a]) + const n0 = _createTextNode() + _renderEffect(() => _setText(n0, _toDisplayString(_ctx.a))) return n0 }" `; exports[`compiler: expression > props 1`] = ` -"import { createTextNode as _createTextNode } from 'vue'; +"import { createTextNode as _createTextNode, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect } from 'vue'; export function render(_ctx, $props, $emit, $attrs, $slots) { - const n0 = _createTextNode(() => [$props.foo]) + const n0 = _createTextNode() + _renderEffect(() => _setText(n0, _toDisplayString($props.foo))) return n0 }" `; exports[`compiler: expression > props aliased 1`] = ` -"import { createTextNode as _createTextNode } from 'vue'; +"import { createTextNode as _createTextNode, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect } from 'vue'; export function render(_ctx, $props, $emit, $attrs, $slots) { - const n0 = _createTextNode(() => [$props['bar']]) + const n0 = _createTextNode() + _renderEffect(() => _setText(n0, _toDisplayString($props['bar']))) return n0 }" `; 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 f8c5da21bc..373b0795bb 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap @@ -1,42 +1,48 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compiler: children transform > children & sibling references 1`] = ` -"import { child as _child, nextn as _nextn, next as _next, createTextNode as _createTextNode, insert as _insert, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue'; -const t0 = _template("

", true) +"import { child as _child, nextn as _nextn, next as _next, createTextNode as _createTextNode, insert as _insert, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue'; +const t0 = _template("

", true) export function render(_ctx) { const n4 = t0() const n0 = _child(n4) const n3 = _nextn(n0, 2) const n2 = _next(n3) - const n1 = _createTextNode(() => [_ctx.second, " ", _ctx.third, " "]) + const x0 = _child(n0) + const n1 = _createTextNode() + const x2 = _child(n2) _insert(n1, n4, n3) _renderEffect(() => { - _setText(n0, _ctx.first) - _setText(n2, _ctx.forth) + _setText(x0, _toDisplayString(_ctx.first)) + _setText(n1, _toDisplayString(_ctx.second) + " " + _toDisplayString(_ctx.third) + " ") + _setText(x2, _toDisplayString(_ctx.forth)) }) return n4 }" `; exports[`compiler: children transform > efficient traversal 1`] = ` -"import { child as _child, next as _next, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue'; -const t0 = _template("
x
", true) +"import { child as _child, next as _next, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue'; +const t0 = _template("
x
", true) export function render(_ctx) { const n3 = t0() - const _n0 = _next(_child(n3)) - const n0 = _child(_n0) - const _n1 = _next(_n0) - const n1 = _child(_n1) - const _n2 = _next(_n1) - const n2 = _child(_n2) + const p0 = _next(_child(n3)) + const n0 = _child(p0) + const p1 = _next(p0) + const n1 = _child(p1) + const p2 = _next(p1) + const n2 = _child(p2) + const x0 = _child(n0) + const x1 = _child(n1) + const x2 = _child(n2) _renderEffect(() => { const _msg = _ctx.msg - _setText(n0, _msg) - _setText(n1, _msg) - _setText(n2, _msg) + _setText(x0, _toDisplayString(_msg)) + _setText(x1, _toDisplayString(_msg)) + _setText(x2, _toDisplayString(_msg)) }) return n3 }" diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformText.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformText.spec.ts.snap index cdc6984834..706b44c9a3 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformText.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformText.spec.ts.snap @@ -1,10 +1,11 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compiler: text transform > consecutive text 1`] = ` -"import { createTextNode as _createTextNode } from 'vue'; +"import { createTextNode as _createTextNode, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect } from 'vue'; export function render(_ctx) { - const n0 = _createTextNode(() => [_ctx.msg]) + const n0 = _createTextNode() + _renderEffect(() => _setText(n0, _toDisplayString(_ctx.msg))) return n0 }" `; @@ -13,7 +14,7 @@ exports[`compiler: text transform > no consecutive text 1`] = ` "import { createTextNode as _createTextNode } from 'vue'; export function render(_ctx) { - const n0 = _createTextNode(["hello world"]) + const n0 = _createTextNode("hello world") 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 183a3b726c..d7e763e849 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap @@ -1,13 +1,14 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compiler: v-for > array de-structured value (with rest) 1`] = ` -"import { setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; -const t0 = _template("
", true) +"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; +const t0 = _template("
", true) export function render(_ctx) { const n0 = _createFor(() => (_ctx.list), (_for_item0, _for_key0) => { const n2 = t0() - _renderEffect(() => _setText(n2, _for_item0.value[0] + _for_item0.value.slice(1) + _for_key0.value)) + const x2 = _child(n2) + _renderEffect(() => _setText(x2, _toDisplayString(_for_item0.value[0] + _for_item0.value.slice(1) + _for_key0.value))) return n2 }, ([id, ...other], index) => (id)) return n0 @@ -15,13 +16,14 @@ export function render(_ctx) { `; exports[`compiler: v-for > array de-structured value 1`] = ` -"import { setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; -const t0 = _template("
", true) +"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; +const t0 = _template("
", true) export function render(_ctx) { const n0 = _createFor(() => (_ctx.list), (_for_item0, _for_key0) => { const n2 = t0() - _renderEffect(() => _setText(n2, _for_item0.value[0] + _for_item0.value[1] + _for_key0.value)) + const x2 = _child(n2) + _renderEffect(() => _setText(x2, _toDisplayString(_for_item0.value[0] + _for_item0.value[1] + _for_key0.value))) return n2 }, ([id, other], index) => (id)) return n0 @@ -29,15 +31,16 @@ export function render(_ctx) { `; exports[`compiler: v-for > basic v-for 1`] = ` -"import { setText as _setText, renderEffect as _renderEffect, createFor as _createFor, delegateEvents as _delegateEvents, template as _template } from 'vue'; -const t0 = _template("
", true) +"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, delegateEvents as _delegateEvents, template as _template } from 'vue'; +const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx) { const n0 = _createFor(() => (_ctx.items), (_for_item0) => { const n2 = t0() + const x2 = _child(n2) n2.$evtclick = () => (_ctx.remove(_for_item0.value)) - _renderEffect(() => _setText(n2, _for_item0.value)) + _renderEffect(() => _setText(x2, _toDisplayString(_for_item0.value))) return n2 }, (item) => (item.id)) return n0 @@ -62,8 +65,8 @@ export function render(_ctx) { `; exports[`compiler: v-for > nested v-for 1`] = ` -"import { setText as _setText, renderEffect as _renderEffect, createFor as _createFor, insert as _insert, template as _template } from 'vue'; -const t0 = _template("") +"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, insert as _insert, template as _template } from 'vue'; +const t0 = _template(" ") const t1 = _template("
", true) export function render(_ctx) { @@ -71,7 +74,8 @@ export function render(_ctx) { const n5 = t1() const n2 = _createFor(() => (_for_item0.value), (_for_item1) => { const n4 = t0() - _renderEffect(() => _setText(n4, _for_item1.value+_for_item0.value)) + const x4 = _child(n4) + _renderEffect(() => _setText(x4, _toDisplayString(_for_item1.value+_for_item0.value))) return n4 }, null, null, null, true) _insert(n2, n5) @@ -82,13 +86,14 @@ export function render(_ctx) { `; exports[`compiler: v-for > object de-structured value (with rest) 1`] = ` -"import { getRestElement as _getRestElement, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; -const t0 = _template("
", true) +"import { getRestElement as _getRestElement, child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; +const t0 = _template("
", true) export function render(_ctx) { const n0 = _createFor(() => (_ctx.list), (_for_item0, _for_key0) => { const n2 = t0() - _renderEffect(() => _setText(n2, _for_item0.value.id + _getRestElement(_for_item0.value, ["id"]) + _for_key0.value)) + const x2 = _child(n2) + _renderEffect(() => _setText(x2, _toDisplayString(_for_item0.value.id + _getRestElement(_for_item0.value, ["id"]) + _for_key0.value))) return n2 }, ({ id, ...other }, index) => (id)) return n0 @@ -96,13 +101,14 @@ export function render(_ctx) { `; exports[`compiler: v-for > object de-structured value 1`] = ` -"import { setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; -const t0 = _template("", true) +"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; +const t0 = _template(" ", true) export function render(_ctx) { const n0 = _createFor(() => (_ctx.items), (_for_item0) => { const n2 = t0() - _renderEffect(() => _setText(n2, _for_item0.value.id, _for_item0.value.value)) + const x2 = _child(n2) + _renderEffect(() => _setText(x2, _toDisplayString(_for_item0.value.id) + _toDisplayString(_for_item0.value.value))) return n2 }, ({ id, value }) => (id)) return n0 @@ -110,13 +116,14 @@ export function render(_ctx) { `; exports[`compiler: v-for > v-for aliases w/ complex expressions 1`] = ` -"import { getDefaultValue as _getDefaultValue, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; -const t0 = _template("
", true) +"import { getDefaultValue as _getDefaultValue, child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; +const t0 = _template("
", true) export function render(_ctx) { const n0 = _createFor(() => (_ctx.list), (_for_item0) => { const n2 = t0() - _renderEffect(() => _setText(n2, _getDefaultValue(_for_item0.value.foo, _ctx.bar) + _ctx.bar + _ctx.baz + _getDefaultValue(_for_item0.value.baz[0], _ctx.quux) + _ctx.quux)) + const x2 = _child(n2) + _renderEffect(() => _setText(x2, _toDisplayString(_getDefaultValue(_for_item0.value.foo, _ctx.bar) + _ctx.bar + _ctx.baz + _getDefaultValue(_for_item0.value.baz[0], _ctx.quux) + _ctx.quux))) return n2 }) 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 a01ec4774d..360823cdcf 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 { setText as _setText, renderEffect as _renderEffect, createIf as _createIf, template as _template } from 'vue'; -const t0 = _template("
", true) +"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createIf as _createIf, template as _template } from 'vue'; +const t0 = _template("
", true) export function render(_ctx) { const n0 = _createIf(() => (_ctx.ok), () => { const n2 = t0() - _renderEffect(() => _setText(n2, _ctx.msg)) + const x2 = _child(n2) + _renderEffect(() => _setText(x2, _toDisplayString(_ctx.msg))) return n2 }) return n0 @@ -15,13 +16,13 @@ export function render(_ctx) { `; exports[`compiler: v-if > comment between branches 1`] = ` -"import { createIf as _createIf, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue'; +"import { createIf as _createIf, child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue'; const t0 = _template("
") const t1 = _template("") const t2 = _template("

") const t3 = _template("") const t4 = _template("fine") -const t5 = _template("") +const t5 = _template("
") export function render(_ctx) { const n13 = t5() @@ -37,7 +38,8 @@ export function render(_ctx) { const n11 = t4() return [n10, n11] })) - _renderEffect(() => _setText(n13, _ctx.text)) + const x13 = _child(n13) + _renderEffect(() => _setText(x13, _toDisplayString(_ctx.text))) return [n0, n13] }" `; @@ -60,17 +62,18 @@ export function render(_ctx) { `; exports[`compiler: v-if > template v-if 1`] = ` -"import { setText as _setText, renderEffect as _renderEffect, createIf as _createIf, template as _template } from 'vue'; +"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createIf as _createIf, template as _template } from 'vue'; const t0 = _template("
") const t1 = _template("hello") -const t2 = _template("

", true) +const t2 = _template("

", true) export function render(_ctx) { const n0 = _createIf(() => (_ctx.ok), () => { const n2 = t0() const n3 = t1() const n4 = t2() - _renderEffect(() => _setText(n4, _ctx.msg)) + const x4 = _child(n4) + _renderEffect(() => _setText(x4, _toDisplayString(_ctx.msg))) return [n2, n3, n4] }) return n0 diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap index 15abf38f99..94fd139589 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap @@ -12,13 +12,13 @@ export function render(_ctx) { `; exports[`compiler: v-once > basic 1`] = ` -"import { child as _child, createTextNode as _createTextNode, setClass as _setClass, prepend as _prepend, template as _template } from 'vue'; +"import { child as _child, createTextNode as _createTextNode, toDisplayString as _toDisplayString, setClass as _setClass, prepend as _prepend, template as _template } from 'vue'; const t0 = _template("
", true) export function render(_ctx, $props, $emit, $attrs, $slots) { const n2 = t0() const n1 = _child(n2) - const n0 = _createTextNode([_ctx.msg, " "]) + const n0 = _createTextNode(_toDisplayString(_ctx.msg) + " ") _setClass(n1, _ctx.clz) _prepend(n2, n0) return n2 diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap index 334efa7455..bab5c10460 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap @@ -22,7 +22,7 @@ export function render(_ctx) { `; exports[`compiler: transform slot > dynamic slots name w/ v-for 1`] = ` -"import { resolveComponent as _resolveComponent, createTextNode as _createTextNode, createForSlots as _createForSlots, createComponentWithFallback as _createComponentWithFallback } from 'vue'; +"import { resolveComponent as _resolveComponent, createTextNode as _createTextNode, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createForSlots as _createForSlots, createComponentWithFallback as _createComponentWithFallback } from 'vue'; export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") @@ -31,7 +31,8 @@ export function render(_ctx) { () => (_createForSlots(_ctx.list, (item) => ({ name: item, fn: (_slotProps0) => { - const n0 = _createTextNode(() => [_slotProps0["bar"]]) + const n0 = _createTextNode() + _renderEffect(() => _setText(n0, _toDisplayString(_slotProps0["bar"]))) return n0 } }))) @@ -157,7 +158,7 @@ export function render(_ctx) { `; exports[`compiler: transform slot > nested slots scoping 1`] = ` -"import { resolveComponent as _resolveComponent, createTextNode as _createTextNode, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, createTextNode as _createTextNode, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template(" ") export function render(_ctx) { @@ -168,11 +169,13 @@ export function render(_ctx) { const n2 = t0() const n1 = _createComponentWithFallback(_component_Inner, null, { "default": (_slotProps1) => { - const n0 = _createTextNode(() => [_slotProps0["foo"] + _slotProps1["bar"] + _ctx.baz]) + const n0 = _createTextNode() + _renderEffect(() => _setText(n0, _toDisplayString(_slotProps0["foo"] + _slotProps1["bar"] + _ctx.baz))) return n0 } }) - const n3 = _createTextNode(() => [_slotProps0["foo"] + _ctx.bar + _ctx.baz]) + const n3 = _createTextNode() + _renderEffect(() => _setText(n3, _toDisplayString(_slotProps0["foo"] + _ctx.bar + _ctx.baz))) return [n1, n2, n3] } }, true) @@ -181,7 +184,7 @@ export function render(_ctx) { `; exports[`compiler: transform slot > on component dynamically named slot 1`] = ` -"import { resolveComponent as _resolveComponent, createTextNode as _createTextNode, createComponentWithFallback as _createComponentWithFallback } from 'vue'; +"import { resolveComponent as _resolveComponent, createTextNode as _createTextNode, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback } from 'vue'; export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") @@ -190,7 +193,8 @@ export function render(_ctx) { () => ({ name: _ctx.named, fn: (_slotProps0) => { - const n0 = _createTextNode(() => [_slotProps0["foo"] + _ctx.bar]) + const n0 = _createTextNode() + _renderEffect(() => _setText(n0, _toDisplayString(_slotProps0["foo"] + _ctx.bar))) return n0 } }) @@ -201,13 +205,14 @@ export function render(_ctx) { `; exports[`compiler: transform slot > on component named slot 1`] = ` -"import { resolveComponent as _resolveComponent, createTextNode as _createTextNode, createComponentWithFallback as _createComponentWithFallback } from 'vue'; +"import { resolveComponent as _resolveComponent, createTextNode as _createTextNode, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback } from 'vue'; export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n1 = _createComponentWithFallback(_component_Comp, null, { "named": (_slotProps0) => { - const n0 = _createTextNode(() => [_slotProps0["foo"] + _ctx.bar]) + const n0 = _createTextNode() + _renderEffect(() => _setText(n0, _toDisplayString(_slotProps0["foo"] + _ctx.bar))) return n0 } }, true) @@ -216,13 +221,14 @@ export function render(_ctx) { `; exports[`compiler: transform slot > on-component default slot 1`] = ` -"import { resolveComponent as _resolveComponent, createTextNode as _createTextNode, createComponentWithFallback as _createComponentWithFallback } from 'vue'; +"import { resolveComponent as _resolveComponent, createTextNode as _createTextNode, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback } from 'vue'; export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n1 = _createComponentWithFallback(_component_Comp, null, { "default": (_slotProps0) => { - const n0 = _createTextNode(() => [_slotProps0["foo"] + _ctx.bar]) + const n0 = _createTextNode() + _renderEffect(() => _setText(n0, _toDisplayString(_slotProps0["foo"] + _ctx.bar))) return n0 } }, true) 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 5ae5b2a741..9a3b88acba 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 { setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue'; -const t0 = _template("
", true) +exports[`v-text > should convert v-text to setText 1`] = ` +"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue'; +const t0 = _template("
", true) export function render(_ctx, $props, $emit, $attrs, $slots) { const n0 = t0() - _renderEffect(() => _setText(n0, _ctx.str)) + const x0 = _child(n0) + _renderEffect(() => _setText(x0, _toDisplayString(_ctx.str))) return n0 }" `; exports[`v-text > should raise error and ignore children when v-text is present 1`] = ` -"import { setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue'; -const t0 = _template("
", true) +"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue'; +const t0 = _template("
", true) export function render(_ctx) { const n0 = t0() - _renderEffect(() => _setText(n0, _ctx.test)) + const x0 = _child(n0) + _renderEffect(() => _setText(x0, _toDisplayString(_ctx.test))) return n0 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/transformText.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformText.spec.ts index 96f4d85462..a668c76258 100644 --- a/packages/compiler-vapor/__tests__/transforms/transformText.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/transformText.spec.ts @@ -36,7 +36,6 @@ describe('compiler: text transform', () => { isStatic: false, }, ], - effect: false, }, ]) }) @@ -49,15 +48,9 @@ describe('compiler: text transform', () => { { type: IRNodeTypes.CREATE_TEXT_NODE, id: 0, - values: [ - { - type: IRNodeTypes.SET_TEXT, - content: 'msg', - isStatic: false, - }, - ], - effect: true, + values: undefined, }, ]) + expect(ir.block.effect.length).toBe(1) }) }) diff --git a/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts b/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts index 22ded75aeb..31a6da3690 100644 --- a/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts @@ -32,7 +32,7 @@ describe('compiler: v-for', () => { expect(code).matchSnapshot() expect(helpers).contains('createFor') - expect(ir.template).toEqual(['
']) + expect(ir.template).toEqual(['
']) expect(ir.block.operation).toMatchObject([ { type: IRNodeTypes.FOR, @@ -89,7 +89,7 @@ describe('compiler: v-for', () => { `_createFor(() => (_for_item0.value), (_for_item1) => {`, ) expect(code).contains(`_for_item1.value+_for_item0.value`) - expect(ir.template).toEqual(['', '
']) + expect(ir.template).toEqual([' ', '
']) expect(ir.block.operation).toMatchObject([ { type: IRNodeTypes.FOR, diff --git a/packages/compiler-vapor/__tests__/transforms/vIf.spec.ts b/packages/compiler-vapor/__tests__/transforms/vIf.spec.ts index bac95e0d21..8f2d572d0b 100644 --- a/packages/compiler-vapor/__tests__/transforms/vIf.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vIf.spec.ts @@ -32,7 +32,7 @@ describe('compiler: v-if', () => { expect(helpers).contains('createIf') - expect(ir.template).toEqual(['
']) + expect(ir.template).toEqual(['
']) expect(ir.block.operation).toMatchObject([ { type: IRNodeTypes.IF, @@ -68,7 +68,7 @@ describe('compiler: v-if', () => { ) expect(code).matchSnapshot() - expect(ir.template).toEqual(['
', 'hello', '

']) + expect(ir.template).toEqual(['
', 'hello', '

']) expect(ir.block.effect).toEqual([]) expect((ir.block.operation[0] as IfIRNode).positive.effect).toMatchObject([ { @@ -227,7 +227,7 @@ describe('compiler: v-if', () => {

- +

`) expect(code).matchSnapshot() expect(ir.template).toEqual([ @@ -237,7 +237,7 @@ describe('compiler: v-if', () => { '', 'fine', - '', + '
', ]) }) diff --git a/packages/compiler-vapor/__tests__/transforms/vOnce.spec.ts b/packages/compiler-vapor/__tests__/transforms/vOnce.spec.ts index 1f40424e5f..228350486b 100644 --- a/packages/compiler-vapor/__tests__/transforms/vOnce.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vOnce.spec.ts @@ -42,7 +42,6 @@ describe('compiler: v-once', () => { isStatic: true, }, ], - effect: false, }, { element: 1, diff --git a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts index b3b1e6b19a..caa45681a2 100644 --- a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts @@ -297,7 +297,7 @@ describe('compiler: transform slot', () => { expect(code).toMatchSnapshot() expect(code).contains(`fn: (_slotProps0) =>`) - expect(code).contains(`_createTextNode(() => [_slotProps0["bar"]])`) + expect(code).contains(`_setText(n0, _toDisplayString(_slotProps0["bar"]))`) expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE) expect(ir.block.operation).toMatchObject([ diff --git a/packages/compiler-vapor/__tests__/transforms/vText.spec.ts b/packages/compiler-vapor/__tests__/transforms/vText.spec.ts index 9a45b1eec0..4f074fee87 100644 --- a/packages/compiler-vapor/__tests__/transforms/vText.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vText.spec.ts @@ -15,7 +15,7 @@ const compileWithVText = makeCompile({ }) describe('v-text', () => { - test('should convert v-text to textContent', () => { + test('should convert v-text to setText', () => { const { code, ir, helpers } = compileWithVText(`
`, { bindingMetadata: { str: BindingTypes.SETUP_REF, @@ -23,7 +23,12 @@ describe('v-text', () => { }) expect(helpers).contains('setText') - expect(ir.block.operation).toMatchObject([]) + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.GET_TEXT_CHILD, + parent: 0, + }, + ]) expect(ir.block.effect).toMatchObject([ { @@ -63,7 +68,7 @@ describe('v-text', () => { ]) // children should have been removed - expect(ir.template).toEqual(['
']) + expect(ir.template).toEqual(['
']) expect(ir.block.effect).toMatchObject([ { @@ -92,7 +97,7 @@ describe('v-text', () => { expect(code).matchSnapshot() // children should have been removed - expect(code).contains('template("
", true)') + expect(code).contains('template("
", true)') }) test('should raise error if has no expression', () => { diff --git a/packages/compiler-vapor/src/generators/operation.ts b/packages/compiler-vapor/src/generators/operation.ts index 997b2655ec..93eff05846 100644 --- a/packages/compiler-vapor/src/generators/operation.ts +++ b/packages/compiler-vapor/src/generators/operation.ts @@ -7,7 +7,7 @@ import { genSetHtml } from './html' import { genIf } from './if' import { genDynamicProps, genSetProp } from './prop' import { genDeclareOldRef, genSetTemplateRef } from './templateRef' -import { genCreateTextNode, genSetText } from './text' +import { genCreateTextNode, genGetTextChild, genSetText } from './text' import { type CodeFragment, INDENT_END, @@ -68,6 +68,8 @@ export function genOperation( return genSlotOutlet(oper, context) case IRNodeTypes.DIRECTIVE: return genBuiltinDirective(oper, context) + case IRNodeTypes.GET_TEXT_CHILD: + return genGetTextChild(oper, context) default: const exhaustiveCheck: never = oper throw new Error( diff --git a/packages/compiler-vapor/src/generators/template.ts b/packages/compiler-vapor/src/generators/template.ts index bc94a8dbfb..68d4c20b3e 100644 --- a/packages/compiler-vapor/src/generators/template.ts +++ b/packages/compiler-vapor/src/generators/template.ts @@ -60,7 +60,9 @@ export function genChildren( const elementIndex = Number(index) + offset const newPath = [...path, elementIndex] - const variable = id === undefined ? `_n${context.block.tempId++}` : `n${id}` + // p for "placeholder" variables that are meant for possible reuse by + // other access paths + const variable = id === undefined ? `p${context.block.tempId++}` : `n${id}` push(NEWLINE, `const ${variable} = `) if (prev) { diff --git a/packages/compiler-vapor/src/generators/text.ts b/packages/compiler-vapor/src/generators/text.ts index 8d8b849c80..3c9835f88b 100644 --- a/packages/compiler-vapor/src/generators/text.ts +++ b/packages/compiler-vapor/src/generators/text.ts @@ -1,22 +1,25 @@ +import type { SimpleExpressionNode } from '@vue/compiler-dom' import type { CodegenContext } from '../generate' -import type { CreateTextNodeIRNode, SetTextIRNode } from '../ir' +import type { + CreateTextNodeIRNode, + GetTextChildIRNode, + SetTextIRNode, +} from '../ir' +import { getLiteralExpressionValue } from '../utils' import { genExpression } from './expression' -import { - type CodeFragment, - DELIMITERS_ARRAY, - NEWLINE, - genCall, - genMulti, -} from './utils' +import { type CodeFragment, NEWLINE, genCall } from './utils' export function genSetText( oper: SetTextIRNode, context: CodegenContext, ): CodeFragment[] { const { helper } = context - const { element, values } = oper - const texts = values.map(value => genExpression(value, context)) - return [NEWLINE, ...genCall(helper('setText'), `n${element}`, ...texts)] + const { element, values, generated } = oper + const texts = combineValues(values, context) + return [ + NEWLINE, + ...genCall(helper('setText'), `${generated ? 'x' : 'n'}${element}`, texts), + ] } export function genCreateTextNode( @@ -24,16 +27,40 @@ export function genCreateTextNode( context: CodegenContext, ): CodeFragment[] { const { helper } = context - const { id, values, effect } = oper + const { id, values } = oper return [ NEWLINE, `const n${id} = `, - ...genCall(helper('createTextNode'), [ - effect && '() => ', - ...genMulti( - DELIMITERS_ARRAY, - ...values.map(value => genExpression(value, context)), - ), - ]), + ...genCall( + helper('createTextNode'), + values && combineValues(values, context), + ), + ] +} + +function combineValues( + values: SimpleExpressionNode[], + context: CodegenContext, +): CodeFragment[] { + return values.flatMap((value, i) => { + let exp = genExpression(value, context) + if (getLiteralExpressionValue(value) == null) { + // dynamic, wrap with toDisplayString + exp = genCall(context.helper('toDisplayString'), exp) + } + if (i > 0) { + exp.unshift(' + ') + } + return exp + }) +} + +export function genGetTextChild( + oper: GetTextChildIRNode, + context: CodegenContext, +): CodeFragment[] { + return [ + NEWLINE, + `const x${oper.parent} = ${context.helper('child')}(n${oper.parent})`, ] } diff --git a/packages/compiler-vapor/src/ir/index.ts b/packages/compiler-vapor/src/ir/index.ts index 395a5c964e..1509d37424 100644 --- a/packages/compiler-vapor/src/ir/index.ts +++ b/packages/compiler-vapor/src/ir/index.ts @@ -34,6 +34,8 @@ export enum IRNodeTypes { IF, FOR, + + GET_TEXT_CHILD, } export interface BaseIRNode { @@ -118,6 +120,7 @@ export interface SetTextIRNode extends BaseIRNode { type: IRNodeTypes.SET_TEXT element: number values: SimpleExpressionNode[] + generated?: boolean // whether this is a generated empty text node by `processTextLikeContainer` } export type KeyOverride = [find: string, replacement: string] @@ -157,8 +160,7 @@ export interface SetTemplateRefIRNode extends BaseIRNode { export interface CreateTextNodeIRNode extends BaseIRNode { type: IRNodeTypes.CREATE_TEXT_NODE id: number - values: SimpleExpressionNode[] - effect: boolean + values?: SimpleExpressionNode[] } export interface InsertNodeIRNode extends BaseIRNode { @@ -209,6 +211,11 @@ export interface SlotOutletIRNode extends BaseIRNode { fallback?: BlockIRNode } +export interface GetTextChildIRNode extends BaseIRNode { + type: IRNodeTypes.GET_TEXT_CHILD + parent: number +} + export type IRNode = OperationNode | RootIRNode export type OperationNode = | SetPropIRNode @@ -227,6 +234,7 @@ export type OperationNode = | CreateComponentIRNode | DeclareOldRefIRNode | SlotOutletIRNode + | GetTextChildIRNode export enum DynamicFlag { NONE = 0, diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index 1cace2e47b..bf619b17dc 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -1,7 +1,6 @@ import { type AllNode, type TransformOptions as BaseTransformOptions, - BindingTypes, type CommentNode, type CompilerCompatOptions, type ElementNode, @@ -12,7 +11,6 @@ import { type TemplateChildNode, defaultOnError, defaultOnWarn, - isConstantNode, isVSlot, } from '@vue/compiler-dom' import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared' @@ -27,7 +25,7 @@ import { type RootIRNode, type VaporDirectiveNode, } from './ir' -import { isConstantExpression } from './utils' +import { isConstantExpression, isStaticExpression } from './utils' import { newBlock, newDynamic } from './transforms/utils' export type NodeTransform = ( @@ -307,21 +305,3 @@ export function createStructuralDirectiveTransform( } } } - -function isStaticExpression( - context: TransformContext, - expressions: SimpleExpressionNode[], -) { - const { - options: { bindingMetadata }, - } = context - const isLiteralConst = (name: string) => - bindingMetadata[name] === BindingTypes.LITERAL_CONST - return expressions.every(node => { - if (node.ast) { - return isConstantNode(node.ast, isLiteralConst) - } else if (node.ast === null) { - return isLiteralConst(node.content) - } - }) -} diff --git a/packages/compiler-vapor/src/transforms/transformText.ts b/packages/compiler-vapor/src/transforms/transformText.ts index 37da24453d..852cfd463e 100644 --- a/packages/compiler-vapor/src/transforms/transformText.ts +++ b/packages/compiler-vapor/src/transforms/transformText.ts @@ -11,7 +11,11 @@ import { } from '@vue/compiler-dom' import type { NodeTransform, TransformContext } from '../transform' import { DynamicFlag, IRNodeTypes } from '../ir' -import { getLiteralExpressionValue, isConstantExpression } from '../utils' +import { + getLiteralExpressionValue, + isConstantExpression, + isStaticExpression, +} from '../utils' type TextLike = TextNode | InterpolationNode const seen = new WeakMap< @@ -52,12 +56,24 @@ function processTextLike(context: TransformContext) { context.dynamic.flags |= DynamicFlag.INSERT | DynamicFlag.NON_TEMPLATE + const nonConstantExps = values.filter(v => !isConstantExpression(v)) + const isStatic = + !nonConstantExps.length || + isStaticExpression(context, nonConstantExps) || + context.inVOnce + context.registerOperation({ type: IRNodeTypes.CREATE_TEXT_NODE, id, - values, - effect: !values.every(isConstantExpression) && !context.inVOnce, + values: isStatic ? values : undefined, }) + if (!isStatic) { + context.registerEffect(values, { + type: IRNodeTypes.SET_TEXT, + element: id, + values, + }) + } } function processTextLikeContainer( @@ -69,10 +85,17 @@ function processTextLikeContainer( if (literals.every(l => l != null)) { context.childrenTemplate = literals.map(l => String(l)) } else { + context.childrenTemplate = [' '] + context.registerOperation({ + type: IRNodeTypes.GET_TEXT_CHILD, + parent: context.reference(), + }) context.registerEffect(values, { type: IRNodeTypes.SET_TEXT, element: context.reference(), values, + // indicates this node is generated, so prefix should be "x" instead of "n" + generated: true, }) } } diff --git a/packages/compiler-vapor/src/transforms/vText.ts b/packages/compiler-vapor/src/transforms/vText.ts index cb894b0cd9..0832398e12 100644 --- a/packages/compiler-vapor/src/transforms/vText.ts +++ b/packages/compiler-vapor/src/transforms/vText.ts @@ -3,6 +3,7 @@ import { IRNodeTypes } from '../ir' import { EMPTY_EXPRESSION } from './utils' import type { DirectiveTransform } from '../transform' import { getLiteralExpressionValue } from '../utils' +import { isVoidTag } from '../../../shared/src' export const transformVText: DirectiveTransform = (dir, node, context) => { let { exp, loc } = dir @@ -19,14 +20,25 @@ export const transformVText: DirectiveTransform = (dir, node, context) => { context.childrenTemplate.length = 0 } + // v-text on void tags do nothing + if (isVoidTag(context.node.tag)) { + return + } + const literal = getLiteralExpressionValue(exp) if (literal != null) { context.childrenTemplate = [String(literal)] } else { + context.childrenTemplate = [' '] + context.registerOperation({ + type: IRNodeTypes.GET_TEXT_CHILD, + parent: context.reference(), + }) context.registerEffect([exp], { type: IRNodeTypes.SET_TEXT, element: context.reference(), values: [exp], + generated: true, }) } } diff --git a/packages/compiler-vapor/src/utils.ts b/packages/compiler-vapor/src/utils.ts index 319eb9f492..b20bd3ef16 100644 --- a/packages/compiler-vapor/src/utils.ts +++ b/packages/compiler-vapor/src/utils.ts @@ -2,16 +2,19 @@ import type { BigIntLiteral, NumericLiteral, StringLiteral } from '@babel/types' import { isGloballyAllowed } from '@vue/shared' import { type AttributeNode, + BindingTypes, type ElementNode, NodeTypes, type SimpleExpressionNode, findDir as _findDir, findProp as _findProp, createSimpleExpression, + isConstantNode, isLiteralWhitelisted, } from '@vue/compiler-dom' import type { VaporDirectiveNode } from './ir' import { EMPTY_EXPRESSION } from './transforms/utils' +import type { TransformContext } from './transform' export const findProp = _findProp as ( node: ElementNode, @@ -45,6 +48,24 @@ export function isConstantExpression(exp: SimpleExpressionNode): boolean { ) } +export function isStaticExpression( + context: TransformContext, + expressions: SimpleExpressionNode[], +): boolean { + const { + options: { bindingMetadata }, + } = context + const isLiteralConst = (name: string) => + bindingMetadata[name] === BindingTypes.LITERAL_CONST + return expressions.every(node => { + if (node.ast) { + return isConstantNode(node.ast, isLiteralConst) + } else if (node.ast === null) { + return isLiteralConst(node.content) + } + }) +} + export function resolveExpression( exp: SimpleExpressionNode, ): SimpleExpressionNode { diff --git a/packages/runtime-vapor/__tests__/apiCreateVaporApp.spec.ts b/packages/runtime-vapor/__tests__/apiCreateVaporApp.spec.ts index a8bbd96a99..4bd3528f44 100644 --- a/packages/runtime-vapor/__tests__/apiCreateVaporApp.spec.ts +++ b/packages/runtime-vapor/__tests__/apiCreateVaporApp.spec.ts @@ -28,7 +28,7 @@ describe('api: createVaporApp', () => { count: { default: 0 }, }, setup(props) { - return createTextNode(() => [props.count]) + return createTextNode(String(props.count)) }, }) @@ -60,7 +60,7 @@ describe('api: createVaporApp', () => { count: { default: 0 }, }, setup(props) { - return createTextNode(() => [props.count]) + return createTextNode(String(props.count)) }, }) @@ -93,7 +93,7 @@ describe('api: createVaporApp', () => { try { inject('__proto__') } catch (e: any) {} - return createTextNode([`${foo},${bar}`]) + return createTextNode(`${foo},${bar}`) }, }) @@ -142,12 +142,12 @@ describe('api: createVaporApp', () => { }, }).create() - const FooBar = () => createTextNode(['foobar!']) + const FooBar = () => createTextNode('foobar!') app.component('FooBar', FooBar) expect(app.component('FooBar')).toBe(FooBar) - app.component('BarBaz', () => createTextNode(['barbaz!'])) - app.component('BarBaz', () => createTextNode(['barbaz!'])) + app.component('BarBaz', () => createTextNode('barbaz!')) + app.component('BarBaz', () => createTextNode('barbaz!')) expect( 'Component "BarBaz" has already been registered in target app.', ).toHaveBeenWarnedTimes(1) @@ -309,7 +309,7 @@ describe('api: createVaporApp', () => { writable: false, }) - app.component('div', () => createTextNode(['div'])) + app.component('div', () => createTextNode('div')) mount() expect( `Do not use built-in or reserved HTML elements as component id: div`, diff --git a/packages/runtime-vapor/__tests__/apiInject.spec.ts b/packages/runtime-vapor/__tests__/apiInject.spec.ts index 9a2998a2bc..ea185e14ac 100644 --- a/packages/runtime-vapor/__tests__/apiInject.spec.ts +++ b/packages/runtime-vapor/__tests__/apiInject.spec.ts @@ -8,15 +8,16 @@ import { reactive, readonly, ref, + toDisplayString, } from '@vue/runtime-dom' import { createComponent, createTextNode, createVaporApp, renderEffect, - setText, } from '../src' import { makeRender } from './_utils' +import { setElementText } from '../src/dom/prop' const define = makeRender() @@ -41,7 +42,7 @@ describe('api: provide/inject', () => { const foo = inject('foo') return (() => { const n0 = createTextNode() - setText(n0, foo) + setElementText(n0, foo) return n0 })() }, @@ -71,7 +72,7 @@ describe('api: provide/inject', () => { const foo = inject(key) return (() => { const n0 = createTextNode() - setText(n0, foo) + setElementText(n0, foo) return n0 })() }, @@ -101,7 +102,7 @@ describe('api: provide/inject', () => { const bar = inject('bar', 'bar') return (() => { const n0 = createTextNode() - setText(n0, foo + bar) + setElementText(n0, foo + bar) return n0 })() }, @@ -139,7 +140,7 @@ describe('api: provide/inject', () => { const baz = inject('baz') return (() => { const n0 = createTextNode() - setText(n0, [foo, bar, baz].join(',')) + setElementText(n0, [foo, bar, baz].join(',')) return n0 })() }, @@ -169,7 +170,7 @@ describe('api: provide/inject', () => { return (() => { const n0 = createTextNode() renderEffect(() => { - setText(n0, count.value) + setElementText(n0, count.value) }) return n0 })() @@ -206,7 +207,7 @@ describe('api: provide/inject', () => { return (() => { const n0 = createTextNode() renderEffect(() => { - setText(n0, count.value) + setElementText(n0, count.value) }) return n0 })() @@ -245,7 +246,7 @@ describe('api: provide/inject', () => { return (() => { const n0 = createTextNode() renderEffect(() => { - setText(n0, state.count) + setElementText(n0, state.count) }) return n0 })() @@ -282,7 +283,7 @@ describe('api: provide/inject', () => { return (() => { const n0 = createTextNode() renderEffect(() => { - setText(n0, state.count) + setElementText(n0, state.count) }) return n0 })() @@ -318,7 +319,7 @@ describe('api: provide/inject', () => { expect(foo).toBeUndefined() return (() => { const n0 = createTextNode() - setText(n0, foo) + setElementText(n0, foo) return n0 })() }, @@ -345,7 +346,7 @@ describe('api: provide/inject', () => { const foo = inject('foo', undefined) return (() => { const n0 = createTextNode() - setText(n0, foo) + setElementText(n0, foo) return n0 })() }, @@ -361,7 +362,7 @@ describe('api: provide/inject', () => { setup() { provide('foo', 'foo') const injection = inject('foo', null) - return createTextNode(() => [injection]) + return createTextNode(toDisplayString(injection)) }, }).render() expect(host.innerHTML).toBe('') diff --git a/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts b/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts index d3bbeb8e7a..2ad7d2e8ff 100644 --- a/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts +++ b/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts @@ -26,11 +26,11 @@ import { createTextNode, insert, renderEffect, - setText, template, } from '../src' import { makeRender } from './_utils' import { ITERATE_KEY } from '@vue/reactivity' +import { setElementText } from '../src/dom/prop' const define = makeRender() @@ -73,7 +73,7 @@ describe('api: lifecycle hooks', () => { onBeforeUpdate(fn) const n0 = createTextNode() renderEffect(() => { - setText(n0, count.value) + setElementText(n0, count.value) }) return n0 }, @@ -99,7 +99,7 @@ describe('api: lifecycle hooks', () => { const n0 = createTextNode() renderEffect(() => { renderSpy() - setText(n0, count.value) + setElementText(n0, count.value) }) return n0 }, @@ -120,7 +120,7 @@ describe('api: lifecycle hooks', () => { const n0 = createTextNode() renderEffect(() => { - setText(n0, count.value) + setElementText(n0, count.value) }) return n0 }, @@ -282,7 +282,7 @@ describe('api: lifecycle hooks', () => { const t0 = template('
') const n0 = t0() - renderEffect(() => setText(n0, props.count)) + renderEffect(() => setElementText(n0, props.count)) return n0 }, } @@ -334,7 +334,7 @@ describe('api: lifecycle hooks', () => { const n0 = createTextNode() renderEffect(() => { - setText(n0, [obj.foo, 'bar' in obj, Object.keys(obj).join('')]) + setElementText(n0, [obj.foo, 'bar' in obj, Object.keys(obj).join('')]) }) return n0 }, @@ -377,7 +377,7 @@ describe('api: lifecycle hooks', () => { const n0 = createTextNode() renderEffect(() => { - setText(n0, [obj.foo, 'bar' in obj, Object.keys(obj).join('')]) + setElementText(n0, [obj.foo, 'bar' in obj, Object.keys(obj).join('')]) }) return n0 }, @@ -451,7 +451,7 @@ describe('api: lifecycle hooks', () => { onUpdated(() => handleUpdated()) const n0 = createTextNode() - renderEffect(() => setText(n0, count.value)) + renderEffect(() => setElementText(n0, count.value)) const n1 = createComponent(Child, { count: () => count.value }) return [n0, n1] }, @@ -464,7 +464,7 @@ describe('api: lifecycle hooks', () => { const props = currentInstance!.props const n2 = createTextNode() - renderEffect(() => setText(n2, props.count)) + renderEffect(() => setElementText(n2, props.count)) return n2 }, } @@ -496,7 +496,7 @@ describe('api: lifecycle hooks', () => { onUpdated(() => handleUpdated()) const n0 = createTextNode() - renderEffect(() => setText(n0, count.value)) + renderEffect(() => setElementText(n0, count.value)) const n1 = createComponent(Child, { count: () => count.value }) return [n0, n1] }, @@ -511,7 +511,7 @@ describe('api: lifecycle hooks', () => { update = () => count.value++ const n2 = createTextNode() - renderEffect(() => setText(n2, count.value)) + renderEffect(() => setElementText(n2, count.value)) return n2 }, } diff --git a/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts b/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts index e07ab6c376..5e0d5d98aa 100644 --- a/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts +++ b/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts @@ -8,6 +8,7 @@ import { insert, renderEffect, setDynamicProps, + setText, template, } from '../src' import { nextTick, reactive, ref, watchEffect } from '@vue/runtime-dom' @@ -26,7 +27,7 @@ describe('api: setup context', () => { } }, render(ctx) { - return createTextNode([`${ctx.ref} ${ctx.object.msg} ${ctx.value}`]) + return createTextNode(`${ctx.ref} ${ctx.object.msg} ${ctx.value}`) }, }).render() expect(html()).toMatch(`foo bar baz`) @@ -35,7 +36,7 @@ describe('api: setup context', () => { it('should support returning render function', () => { const { html } = define({ setup() { - return createTextNode([`hello`]) + return createTextNode(`hello`) }, }).render() expect(html()).toMatch(`hello`) @@ -51,7 +52,11 @@ describe('api: setup context', () => { watchEffect(() => { dummy = props.count }) - return createTextNode(() => [props.count]) + const n = createTextNode() + renderEffect(() => { + setText(n, props.count) + }) + return n }, }) @@ -148,11 +153,15 @@ describe('api: setup context', () => { $: [ () => ({ name: 'foo', - fn: () => createTextNode(() => [id.value]), + fn: () => { + const n = createTextNode() + renderEffect(() => setText(n, id.value)) + return n + }, }), () => ({ name: 'bar', - fn: () => createTextNode(['bar']), + fn: () => createTextNode('bar'), }), ], }) @@ -181,10 +190,9 @@ describe('api: setup context', () => { delegate(n0, 'click', () => { emit('inc', props.count + 1) }) - insert( - createTextNode(() => [props.count]), - n0, - ) + const n = createTextNode() + renderEffect(() => setText(n, props.count)) + insert(n, n0) return n0 }, }) diff --git a/packages/runtime-vapor/__tests__/component.spec.ts b/packages/runtime-vapor/__tests__/component.spec.ts index 3a945bc096..5fdff8eafe 100644 --- a/packages/runtime-vapor/__tests__/component.spec.ts +++ b/packages/runtime-vapor/__tests__/component.spec.ts @@ -13,11 +13,11 @@ import { createIf, createTextNode, renderEffect, - setText, template, } from '../src' import { makeRender } from './_utils' import type { VaporComponentInstance } from '../src/component' +import { setElementText, setText } from '../src/dom/prop' const define = makeRender() @@ -100,7 +100,7 @@ describe('component', () => { const n = inject>('foo')! n.value++ const n0 = template('
')() - renderEffect(() => setText(n0, n.value)) + renderEffect(() => setElementText(n0, n.value)) return n0 }, }) @@ -110,7 +110,7 @@ describe('component', () => { const n = ref(0) provide('foo', n) const n0 = template('
')() - renderEffect(() => setText(n0, n.value)) + renderEffect(() => setElementText(n0, n.value)) return [n0, createComponent(Child)] }, }).render() @@ -129,7 +129,7 @@ describe('component', () => { val => emit('update', val), ) const n0 = template('
')() - renderEffect(() => setText(n0, props.value)) + renderEffect(() => setElementText(n0, props.value)) return n0 }, }) @@ -139,7 +139,7 @@ describe('component', () => { setup() { const inner = ref(0) const n0 = template('
')() - renderEffect(() => setText(n0, inner.value)) + renderEffect(() => setElementText(n0, inner.value)) const n1 = createComponent(Child, { value: () => outer.value, onUpdate: () => (val: number) => (inner.value = val), @@ -162,7 +162,11 @@ describe('component', () => { props: ['count'], setup(props: any) { onUpdated(() => calls.push('update child')) - return createTextNode(() => [`${props.count} - ${a.value}`]) + const n = createTextNode() + renderEffect(() => { + setText(n, `${props.count} - ${a.value}`) + }) + return n }, }) @@ -206,10 +210,11 @@ describe('component', () => { props: ['count'], setup(props: any) { onUpdated(() => calls.push('update parent')) - const n1 = createTextNode(() => [ - `${globalCount.value} - ${props.count}`, - ]) + const n1 = createTextNode() const n2 = createComponent(Child, { count: () => parentCount.value }) + renderEffect(() => { + setText(n1, `${globalCount.value} - ${props.count}`) + }) return [n1, n2] }, }) @@ -240,7 +245,7 @@ describe('component', () => { const n1 = template('

')() renderEffect(() => { spy() - setText(n1, props.text) + setElementText(n1, props.text) }) return n1 }, @@ -267,7 +272,7 @@ describe('component', () => { const t0 = template('
') const n0 = t0() watchEffect(() => { - setText(n0, count.value) + setElementText(n0, count.value) }) renderEffect(() => {}) return n0 diff --git a/packages/runtime-vapor/__tests__/componentAttrs.spec.ts b/packages/runtime-vapor/__tests__/componentAttrs.spec.ts index 4305a27096..e4076855cb 100644 --- a/packages/runtime-vapor/__tests__/componentAttrs.spec.ts +++ b/packages/runtime-vapor/__tests__/componentAttrs.spec.ts @@ -7,11 +7,11 @@ import { setDynamicProps, setProp, setStyle, - setText, template, } from '../src' import { makeRender } from './_utils' import { stringifyStyle } from '@vue/shared' +import { setElementText } from '../src/dom/prop' const define = makeRender() @@ -24,7 +24,7 @@ describe('attribute fallthrough', () => { props: ['foo'], setup(props: any) { const n0 = t0() as Element - renderEffect(() => setText(n0, props.foo)) + renderEffect(() => setElementText(n0, props.foo)) return n0 }, }) @@ -62,7 +62,7 @@ describe('attribute fallthrough', () => { inheritAttrs: false, setup(props: any) { const n0 = t0() as Element - renderEffect(() => setText(n0, props.foo)) + renderEffect(() => setElementText(n0, props.foo)) return n0 }, }) @@ -99,7 +99,7 @@ describe('attribute fallthrough', () => { props: ['custom-attr'], setup(_: any, { attrs }: any) { const n0 = t0() as Element - renderEffect(() => setText(n0, attrs.foo)) + renderEffect(() => setElementText(n0, attrs.foo)) return n0 }, }) diff --git a/packages/runtime-vapor/__tests__/componentProps.spec.ts b/packages/runtime-vapor/__tests__/componentProps.spec.ts index 3f8270b3d9..2fd0e9df1a 100644 --- a/packages/runtime-vapor/__tests__/componentProps.spec.ts +++ b/packages/runtime-vapor/__tests__/componentProps.spec.ts @@ -13,10 +13,10 @@ import { createComponent, defineVaporComponent, renderEffect, - setText, template, } from '../src' import { makeRender } from './_utils' +import { setElementText } from '../src/dom/prop' const define = makeRender() @@ -243,7 +243,7 @@ describe('component: props', () => { props: ['foo'], setup(props: any) { const n0 = t0() - renderEffect(() => setText(n0, props.foo)) + renderEffect(() => setElementText(n0, props.foo)) return n0 }, }) @@ -396,7 +396,7 @@ describe('component: props', () => { }, setup(props: any) { const n0 = t0() - renderEffect(() => setText(n0, props.foo)) + renderEffect(() => setElementText(n0, props.foo)) return n0 }, }).render({ @@ -428,7 +428,7 @@ describe('component: props', () => { const t0 = template('

') const n0 = t0() renderEffect(() => { - setText(n0, props.foo.val, props.bar) + setElementText(n0, String(props.foo.val) + String(props.bar)) }) return n0 }, @@ -503,7 +503,7 @@ describe('component: props', () => { const t0 = template('
') const n0 = t0() renderEffect(() => - setText(n0, JSON.stringify(attrs) + Object.keys(attrs)), + setElementText(n0, JSON.stringify(attrs) + Object.keys(attrs)), ) return n0 }, diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts index 149da8b94e..58076fff9e 100644 --- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts +++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts @@ -10,12 +10,12 @@ import { insert, prepend, renderEffect, - setText, template, } from '../src' import { currentInstance, nextTick, ref } from '@vue/runtime-dom' import { makeRender } from './_utils' import type { DynamicSlot } from '../src/componentSlots' +import { setElementText } from '../src/dom/prop' const define = makeRender() @@ -186,7 +186,7 @@ describe('component: slots', () => { header: props => { const el = template('

')() renderEffect(() => { - setText(el, props.title) + setElementText(el, props.title) }) return el }, @@ -273,7 +273,7 @@ describe('component: slots', () => { fn: (props: any) => { const el = template('

')() renderEffect(() => { - setText(el, props.title) + setElementText(el, props.title) }) return el }, diff --git a/packages/runtime-vapor/__tests__/dom/prop.spec.ts b/packages/runtime-vapor/__tests__/dom/prop.spec.ts index ead9e75cd5..e879b7103e 100644 --- a/packages/runtime-vapor/__tests__/dom/prop.spec.ts +++ b/packages/runtime-vapor/__tests__/dom/prop.spec.ts @@ -4,6 +4,7 @@ import { setAttr, setClass, setDynamicProps, + setElementText, setHtml, setProp, setText, @@ -11,7 +12,11 @@ import { } from '../../src/dom/prop' import { setStyle } from '../../src/dom/prop' import { VaporComponentInstance } from '../../src/component' -import { currentInstance, simpleSetCurrentInstance } from '@vue/runtime-dom' +import { + currentInstance, + ref, + simpleSetCurrentInstance, +} from '@vue/runtime-dom' let removeComponentInstance = NOOP beforeEach(() => { @@ -227,7 +232,7 @@ describe('patchProp', () => { expect((el as any)._value).toBe(obj) const option = document.createElement('option') - setText(option, 'foo') + setElementText(option, 'foo') expect(option.value).toBe('foo') expect(option.getAttribute('value')).toBe(null) @@ -412,9 +417,9 @@ describe('patchProp', () => { }) describe('setText', () => { - test('should set textContent', () => { - const el = document.createElement('div') - setText(el, null) + test('should set nodeValue', () => { + const el = document.createTextNode('foo') + setText(el, '') expect(el.textContent).toBe('') setText(el, 'foo') expect(el.textContent).toBe('foo') @@ -423,6 +428,18 @@ describe('patchProp', () => { }) }) + describe('setElementText', () => { + test('should set textContent w/ toDisplayString', () => { + const el = document.createElement('div') + setElementText(el, null) + expect(el.textContent).toBe('') + setElementText(el, { a: 1 }) + expect(el.textContent).toBe(JSON.stringify({ a: 1 }, null, 2)) + setElementText(el, ref('bar')) + expect(el.textContent).toBe('bar') + }) + }) + describe('setHtml', () => { test('should set innerHTML', () => { const el = document.createElement('div') diff --git a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts index 96a533551c..e696fadd46 100644 --- a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts +++ b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts @@ -7,7 +7,6 @@ import { createTemplateRefSetter, insert, renderEffect, - setText, template, } from '../../src' import { makeRender } from '../_utils' @@ -20,6 +19,7 @@ import { useTemplateRef, watchEffect, } from '@vue/runtime-dom' +import { setElementText } from '../../src/dom/prop' const define = makeRender() @@ -292,7 +292,7 @@ describe('api: template ref', () => { const n0 = t0() createTemplateRefSetter()(n0 as Element, el) renderEffect(() => { - setText(n0, el.value && el.value.getAttribute('id')) + setElementText(n0, el.value && el.value.getAttribute('id')) }) return n0 }, @@ -431,7 +431,7 @@ describe('api: template ref', () => { true, ) renderEffect(() => { - setText(n1, item) + setElementText(n1, item) }) return n1 }, @@ -493,7 +493,7 @@ describe('api: template ref', () => { true, ) renderEffect(() => { - setText(n1, item) + setElementText(n1, item) }) return n1 }, @@ -553,14 +553,14 @@ describe('api: template ref', () => { true, ) renderEffect(() => { - setText(n4, item) + setElementText(n4, item) }) return n4 }, ) insert(n3, n2 as unknown as ParentNode) renderEffect(() => { - setText(n1!, String(listRefs.value)) + setElementText(n1!, String(listRefs.value)) }) return n0 }, diff --git a/packages/runtime-vapor/__tests__/if.spec.ts b/packages/runtime-vapor/__tests__/if.spec.ts index a7e6266a7e..e319a884c7 100644 --- a/packages/runtime-vapor/__tests__/if.spec.ts +++ b/packages/runtime-vapor/__tests__/if.spec.ts @@ -3,7 +3,6 @@ import { createIf, insert, renderEffect, - setText, template, // @ts-expect-error withDirectives, @@ -12,6 +11,7 @@ import { nextTick, ref } from '@vue/runtime-dom' import type { Mock } from 'vitest' import { makeRender } from './_utils' import { unmountComponent } from '../src/component' +import { setElementText } from '../src/dom/prop' const define = makeRender() @@ -44,7 +44,7 @@ describe('createIf', () => { (spyIfFn ||= vi.fn(() => { const n2 = t1() renderEffect(() => { - setText(n2, count.value) + setElementText(n2, count.value) }) return n2 })), diff --git a/packages/runtime-vapor/src/dom/node.ts b/packages/runtime-vapor/src/dom/node.ts index f80b9ea676..76ceba5f2b 100644 --- a/packages/runtime-vapor/src/dom/node.ts +++ b/packages/runtime-vapor/src/dom/node.ts @@ -1,17 +1,6 @@ -import { isArray } from '@vue/shared' -import { renderEffect } from '../renderEffect' -import { setText } from './prop' - -export function createTextNode(values?: any[] | (() => any[])): Text { - const node = document.createTextNode('') - if (values) { - if (isArray(values)) { - setText(node, ...values) - } else { - renderEffect(() => setText(node, ...values())) - } - } - return node +/*! #__NO_SIDE_EFFECTS__ */ +export function createTextNode(value = ''): Text { + return document.createTextNode(value) } /*! #__NO_SIDE_EFFECTS__ */ diff --git a/packages/runtime-vapor/src/dom/prop.ts b/packages/runtime-vapor/src/dom/prop.ts index 137f2d807f..f464a2f629 100644 --- a/packages/runtime-vapor/src/dom/prop.ts +++ b/packages/runtime-vapor/src/dom/prop.ts @@ -171,13 +171,26 @@ export function setValue(el: TargetElement, value: any): void { } } -export function setText(el: Node & { $txt?: string }, ...values: any[]): void { - const value = - values.length > 1 - ? values.map(toDisplayString).join('') - : toDisplayString(values[0]) +/** + * Only called on text nodes! + * Compiler should also ensure value passed here is already converted by + * `toDisplayString` + */ +export function setText(el: Text & { $txt?: string }, value: string): void { if (el.$txt !== value) { - el.textContent = el.$txt = value + el.nodeValue = el.$txt = value + } +} + +/** + * Used by setDynamicProps only, so need to guard with `toDisplayString` + */ +export function setElementText( + el: Node & { $txt?: string }, + value: unknown, +): void { + if (el.$txt !== (value = toDisplayString(value))) { + el.textContent = el.$txt = value as string } } @@ -232,7 +245,7 @@ export function setDynamicProp( if (key === 'innerHTML') { setHtml(el, value) } else if (key === 'textContent') { - setText(el, value) + setElementText(el, value) } else if (key === 'value' && canSetValueDirectly(el.tagName)) { setValue(el, value) } else {