From 709864c2c74b8be87e9f315c89bc7bd1d8d8ab0b Mon Sep 17 00:00:00 2001 From: daiwei Date: Fri, 15 Aug 2025 16:06:02 +0800 Subject: [PATCH] feat(vapor): dom event error handling --- .../__snapshots__/compile.spec.ts.snap | 4 +- .../transformElement.spec.ts.snap | 6 +- .../__snapshots__/vFor.spec.ts.snap | 4 +- .../transforms/__snapshots__/vOn.spec.ts.snap | 180 +++++++++--------- .../__tests__/transforms/vOn.spec.ts | 58 ++++-- .../compiler-vapor/src/generators/event.ts | 6 +- .../__tests__/errorHandling.spec.ts | 41 +++- packages/runtime-vapor/src/dom/event.ts | 14 ++ packages/runtime-vapor/src/index.ts | 8 +- 9 files changed, 202 insertions(+), 119 deletions(-) diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap index b10a98d32c..a7461f1193 100644 --- a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap @@ -180,14 +180,14 @@ export function render(_ctx) { `; exports[`compile > dynamic root nodes and interpolation 1`] = ` -"import { child as _child, setProp as _setProp, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { child as _child, createInvoker as _createInvoker, setProp as _setProp, toDisplayString as _toDisplayString, setText as _setText, 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) + n0.$evtclick = _createInvoker(e => _ctx.handleClick(e)) _renderEffect(() => { const _count = _ctx.count _setProp(n0, "id", _count) 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 7aa56aa9c2..46d596b57f 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap @@ -374,14 +374,14 @@ export function render(_ctx) { `; exports[`compiler: element transform > props merging: event handlers 1`] = ` -"import { withKeys as _withKeys, delegate as _delegate, delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, withKeys as _withKeys, delegate as _delegate, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx) { const n0 = t0() - _delegate(n0, "click", _withKeys(e => _ctx.a(e), ["foo"])) - _delegate(n0, "click", _withKeys(e => _ctx.b(e), ["bar"])) + _delegate(n0, "click", _createInvoker(_withKeys(e => _ctx.a(e), ["foo"]))) + _delegate(n0, "click", _createInvoker(_withKeys(e => _ctx.b(e), ["bar"]))) 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 69c695a246..3dde66569e 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap @@ -31,7 +31,7 @@ export function render(_ctx) { `; exports[`compiler: v-for > basic v-for 1`] = ` -"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { child as _child, createInvoker as _createInvoker, 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") @@ -39,7 +39,7 @@ 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)) + n2.$evtclick = _createInvoker(() => (_ctx.remove(_for_item0.value))) _renderEffect(() => _setText(x2, _toDisplayString(_for_item0.value))) return n2 }, (item) => (item.id)) 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 e7a2b30e69..223e45d1bc 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap @@ -1,13 +1,13 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`v-on > complex member expression w/ prefixIdentifiers: true 1`] = ` -"import { delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx) { const n0 = t0() - n0.$evtclick = e => _ctx.a['b' + _ctx.c](e) + n0.$evtclick = _createInvoker(e => _ctx.a['b' + _ctx.c](e)) return n0 }" `; @@ -28,14 +28,14 @@ export function render(_ctx) { `; exports[`v-on > dynamic arg 1`] = ` -"import { on as _on, renderEffect as _renderEffect, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, on as _on, renderEffect as _renderEffect, template as _template } from 'vue'; const t0 = _template("
", true) export function render(_ctx) { const n0 = t0() _renderEffect(() => { - _on(n0, _ctx.event, e => _ctx.handler(e), { + _on(n0, _ctx.event, _createInvoker(e => _ctx.handler(e)), { effect: true }) }) @@ -44,14 +44,14 @@ export function render(_ctx) { `; exports[`v-on > dynamic arg with complex exp prefixing 1`] = ` -"import { on as _on, renderEffect as _renderEffect, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, on as _on, renderEffect as _renderEffect, template as _template } from 'vue'; const t0 = _template("
", true) export function render(_ctx) { const n0 = t0() _renderEffect(() => { - _on(n0, _ctx.event(_ctx.foo), e => _ctx.handler(e), { + _on(n0, _ctx.event(_ctx.foo), _createInvoker(e => _ctx.handler(e)), { effect: true }) }) @@ -60,14 +60,14 @@ export function render(_ctx) { `; exports[`v-on > dynamic arg with prefixing 1`] = ` -"import { on as _on, renderEffect as _renderEffect, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, on as _on, renderEffect as _renderEffect, template as _template } from 'vue'; const t0 = _template("
", true) export function render(_ctx) { const n0 = t0() _renderEffect(() => { - _on(n0, _ctx.event, e => _ctx.handler(e), { + _on(n0, _ctx.event, _createInvoker(e => _ctx.handler(e)), { effect: true }) }) @@ -76,7 +76,7 @@ export function render(_ctx) { `; exports[`v-on > event modifier 1`] = ` -"import { withModifiers as _withModifiers, on as _on, withKeys as _withKeys, delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, withModifiers as _withModifiers, on as _on, withKeys as _withKeys, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("") const t1 = _template("
") const t2 = _template("
") @@ -106,233 +106,233 @@ export function render(_ctx, $props, $emit, $attrs, $slots) { const n19 = t3() const n20 = t3() const n21 = t3() - n0.$evtclick = _withModifiers(_ctx.handleEvent, ["stop"]) - _on(n1, "submit", _withModifiers(_ctx.handleEvent, ["prevent"])) - n2.$evtclick = _withModifiers(_ctx.handleEvent, ["stop","prevent"]) - n3.$evtclick = _withModifiers(_ctx.handleEvent, ["self"]) - _on(n4, "click", _ctx.handleEvent, { + n0.$evtclick = _createInvoker(_withModifiers(_ctx.handleEvent, ["stop"])) + _on(n1, "submit", _createInvoker(_withModifiers(_ctx.handleEvent, ["prevent"]))) + n2.$evtclick = _createInvoker(_withModifiers(_ctx.handleEvent, ["stop","prevent"])) + n3.$evtclick = _createInvoker(_withModifiers(_ctx.handleEvent, ["self"])) + _on(n4, "click", _createInvoker(_ctx.handleEvent), { capture: true }) - _on(n5, "click", _ctx.handleEvent, { + _on(n5, "click", _createInvoker(_ctx.handleEvent), { once: true }) - _on(n6, "scroll", _ctx.handleEvent, { + _on(n6, "scroll", _createInvoker(_ctx.handleEvent), { passive: true }) - n7.$evtcontextmenu = _withModifiers(_ctx.handleEvent, ["right"]) - n8.$evtclick = _withModifiers(_ctx.handleEvent, ["left"]) - n9.$evtmouseup = _withModifiers(_ctx.handleEvent, ["middle"]) - n10.$evtcontextmenu = _withKeys(_withModifiers(_ctx.handleEvent, ["right"]), ["enter"]) - n11.$evtkeyup = _withKeys(_ctx.handleEvent, ["enter"]) - n12.$evtkeyup = _withKeys(_ctx.handleEvent, ["tab"]) - n13.$evtkeyup = _withKeys(_ctx.handleEvent, ["delete"]) - n14.$evtkeyup = _withKeys(_ctx.handleEvent, ["esc"]) - n15.$evtkeyup = _withKeys(_ctx.handleEvent, ["space"]) - n16.$evtkeyup = _withKeys(_ctx.handleEvent, ["up"]) - n17.$evtkeyup = _withKeys(_ctx.handleEvent, ["down"]) - n18.$evtkeyup = _withKeys(_ctx.handleEvent, ["left"]) - n19.$evtkeyup = _withModifiers(e => _ctx.submit(e), ["middle"]) - n20.$evtkeyup = _withModifiers(e => _ctx.submit(e), ["middle","self"]) - n21.$evtkeyup = _withKeys(_withModifiers(_ctx.handleEvent, ["self"]), ["enter"]) + n7.$evtcontextmenu = _createInvoker(_withModifiers(_ctx.handleEvent, ["right"])) + n8.$evtclick = _createInvoker(_withModifiers(_ctx.handleEvent, ["left"])) + n9.$evtmouseup = _createInvoker(_withModifiers(_ctx.handleEvent, ["middle"])) + n10.$evtcontextmenu = _createInvoker(_withKeys(_withModifiers(_ctx.handleEvent, ["right"]), ["enter"])) + n11.$evtkeyup = _createInvoker(_withKeys(_ctx.handleEvent, ["enter"])) + n12.$evtkeyup = _createInvoker(_withKeys(_ctx.handleEvent, ["tab"])) + n13.$evtkeyup = _createInvoker(_withKeys(_ctx.handleEvent, ["delete"])) + n14.$evtkeyup = _createInvoker(_withKeys(_ctx.handleEvent, ["esc"])) + n15.$evtkeyup = _createInvoker(_withKeys(_ctx.handleEvent, ["space"])) + n16.$evtkeyup = _createInvoker(_withKeys(_ctx.handleEvent, ["up"])) + n17.$evtkeyup = _createInvoker(_withKeys(_ctx.handleEvent, ["down"])) + n18.$evtkeyup = _createInvoker(_withKeys(_ctx.handleEvent, ["left"])) + n19.$evtkeyup = _createInvoker(_withModifiers(e => _ctx.submit(e), ["middle"])) + n20.$evtkeyup = _createInvoker(_withModifiers(e => _ctx.submit(e), ["middle","self"])) + n21.$evtkeyup = _createInvoker(_withKeys(_withModifiers(_ctx.handleEvent, ["self"]), ["enter"])) return [n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16, n17, n18, n19, n20, n21] }" `; exports[`v-on > expression with type 1`] = ` -"import { delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx, $props, $emit, $attrs, $slots) { const n0 = t0() - n0.$evtclick = e => _ctx.handleClick(e) + n0.$evtclick = _createInvoker(e => _ctx.handleClick(e)) return n0 }" `; exports[`v-on > function expression w/ prefixIdentifiers: true 1`] = ` -"import { delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx) { const n0 = t0() - n0.$evtclick = e => _ctx.foo(e) + n0.$evtclick = _createInvoker(e => _ctx.foo(e)) return n0 }" `; exports[`v-on > inline statement w/ prefixIdentifiers: true 1`] = ` -"import { delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx) { const n0 = t0() - n0.$evtclick = $event => (_ctx.foo($event)) + n0.$evtclick = _createInvoker($event => (_ctx.foo($event))) return n0 }" `; exports[`v-on > multiple inline statements w/ prefixIdentifiers: true 1`] = ` -"import { delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx) { const n0 = t0() - n0.$evtclick = $event => {_ctx.foo($event);_ctx.bar()} + n0.$evtclick = _createInvoker($event => {_ctx.foo($event);_ctx.bar()}) return n0 }" `; exports[`v-on > should NOT add a prefix to $event if the expression is a function expression 1`] = ` -"import { delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx) { const n0 = t0() - n0.$evtclick = $event => {_ctx.i++;_ctx.foo($event)} + n0.$evtclick = _createInvoker($event => {_ctx.i++;_ctx.foo($event)}) return n0 }" `; exports[`v-on > should NOT wrap as function if expression is already function expression (with Typescript) 1`] = ` -"import { delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx) { const n0 = t0() - n0.$evtclick = (e: any): any => _ctx.foo(e) + n0.$evtclick = _createInvoker((e: any): any => _ctx.foo(e)) return n0 }" `; exports[`v-on > should NOT wrap as function if expression is already function expression (with newlines) 1`] = ` -"import { delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx) { const n0 = t0() - n0.$evtclick = + n0.$evtclick = _createInvoker( $event => { _ctx.foo($event) } - + ) return n0 }" `; exports[`v-on > should NOT wrap as function if expression is already function expression 1`] = ` -"import { delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx) { const n0 = t0() - n0.$evtclick = $event => _ctx.foo($event) + n0.$evtclick = _createInvoker($event => _ctx.foo($event)) return n0 }" `; exports[`v-on > should NOT wrap as function if expression is complex member expression 1`] = ` -"import { delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx) { const n0 = t0() - n0.$evtclick = e => _ctx.a['b' + _ctx.c](e) + n0.$evtclick = _createInvoker(e => _ctx.a['b' + _ctx.c](e)) return n0 }" `; exports[`v-on > should delegate event 1`] = ` -"import { delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx) { const n0 = t0() - n0.$evtclick = e => _ctx.test(e) + n0.$evtclick = _createInvoker(e => _ctx.test(e)) return n0 }" `; exports[`v-on > should handle multi-line statement 1`] = ` -"import { delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx) { const n0 = t0() - n0.$evtclick = () => { + n0.$evtclick = _createInvoker(() => { _ctx.foo(); _ctx.bar() -} +}) return n0 }" `; exports[`v-on > should handle multiple inline statement 1`] = ` -"import { delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx) { const n0 = t0() - n0.$evtclick = () => {_ctx.foo();_ctx.bar()} + n0.$evtclick = _createInvoker(() => {_ctx.foo();_ctx.bar()}) return n0 }" `; exports[`v-on > should not prefix member expression 1`] = ` -"import { delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx) { const n0 = t0() - n0.$evtclick = e => _ctx.foo.bar(e) + n0.$evtclick = _createInvoker(e => _ctx.foo.bar(e)) return n0 }" `; exports[`v-on > should not wrap keys guard if no key modifier is present 1`] = ` -"import { withModifiers as _withModifiers, delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, withModifiers as _withModifiers, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("keyup") export function render(_ctx) { const n0 = t0() - n0.$evtkeyup = _withModifiers(e => _ctx.test(e), ["exact"]) + n0.$evtkeyup = _createInvoker(_withModifiers(e => _ctx.test(e), ["exact"])) return n0 }" `; exports[`v-on > should support multiple events and modifiers options w/ prefixIdentifiers: true 1`] = ` -"import { withModifiers as _withModifiers, withKeys as _withKeys, delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, withModifiers as _withModifiers, withKeys as _withKeys, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click", "keyup") export function render(_ctx) { const n0 = t0() - n0.$evtclick = _withModifiers(e => _ctx.test(e), ["stop"]) - n0.$evtkeyup = _withKeys(e => _ctx.test(e), ["enter"]) + n0.$evtclick = _createInvoker(_withModifiers(e => _ctx.test(e), ["stop"])) + n0.$evtkeyup = _createInvoker(_withKeys(e => _ctx.test(e), ["enter"])) return n0 }" `; exports[`v-on > should support multiple modifiers and event options w/ prefixIdentifiers: true 1`] = ` -"import { withModifiers as _withModifiers, on as _on, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, withModifiers as _withModifiers, on as _on, template as _template } from 'vue'; const t0 = _template("
", true) export function render(_ctx) { const n0 = t0() - _on(n0, "click", _withModifiers(e => _ctx.test(e), ["stop","prevent"]), { + _on(n0, "click", _createInvoker(_withModifiers(e => _ctx.test(e), ["stop","prevent"])), { capture: true, once: true }) @@ -341,26 +341,26 @@ export function render(_ctx) { `; exports[`v-on > should transform click.middle 1`] = ` -"import { withModifiers as _withModifiers, delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, withModifiers as _withModifiers, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("mouseup") export function render(_ctx) { const n0 = t0() - n0.$evtmouseup = _withModifiers(e => _ctx.test(e), ["middle"]) + n0.$evtmouseup = _createInvoker(_withModifiers(e => _ctx.test(e), ["middle"])) return n0 }" `; exports[`v-on > should transform click.middle 2`] = ` -"import { withModifiers as _withModifiers, on as _on, renderEffect as _renderEffect, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, withModifiers as _withModifiers, on as _on, renderEffect as _renderEffect, template as _template } from 'vue'; const t0 = _template("
", true) export function render(_ctx) { const n0 = t0() _renderEffect(() => { - _on(n0, (_ctx.event) === "click" ? "mouseup" : (_ctx.event), _withModifiers(e => _ctx.test(e), ["middle"]), { + _on(n0, (_ctx.event) === "click" ? "mouseup" : (_ctx.event), _createInvoker(_withModifiers(e => _ctx.test(e), ["middle"])), { effect: true }) }) @@ -369,26 +369,26 @@ export function render(_ctx) { `; exports[`v-on > should transform click.right 1`] = ` -"import { withModifiers as _withModifiers, delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, withModifiers as _withModifiers, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("contextmenu") export function render(_ctx) { const n0 = t0() - n0.$evtcontextmenu = _withModifiers(e => _ctx.test(e), ["right"]) + n0.$evtcontextmenu = _createInvoker(_withModifiers(e => _ctx.test(e), ["right"])) return n0 }" `; exports[`v-on > should transform click.right 2`] = ` -"import { withModifiers as _withModifiers, withKeys as _withKeys, on as _on, renderEffect as _renderEffect, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, withModifiers as _withModifiers, withKeys as _withKeys, on as _on, renderEffect as _renderEffect, template as _template } from 'vue'; const t0 = _template("
", true) export function render(_ctx) { const n0 = t0() _renderEffect(() => { - _on(n0, (_ctx.event) === "click" ? "contextmenu" : (_ctx.event), _withKeys(_withModifiers(e => _ctx.test(e), ["right"]), ["right"]), { + _on(n0, (_ctx.event) === "click" ? "contextmenu" : (_ctx.event), _createInvoker(_withKeys(_withModifiers(e => _ctx.test(e), ["right"]), ["right"])), { effect: true }) }) @@ -397,39 +397,39 @@ export function render(_ctx) { `; exports[`v-on > should use delegate helper when have multiple events of same name 1`] = ` -"import { delegate as _delegate, withModifiers as _withModifiers, delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, delegate as _delegate, withModifiers as _withModifiers, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx) { const n0 = t0() - _delegate(n0, "click", e => _ctx.test(e)) - _delegate(n0, "click", _withModifiers(e => _ctx.test(e), ["stop"])) + _delegate(n0, "click", _createInvoker(e => _ctx.test(e))) + _delegate(n0, "click", _createInvoker(_withModifiers(e => _ctx.test(e), ["stop"]))) return n0 }" `; exports[`v-on > should wrap as function if expression is inline statement 1`] = ` -"import { delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx) { const n0 = t0() - n0.$evtclick = () => (_ctx.i++) + n0.$evtclick = _createInvoker(() => (_ctx.i++)) return n0 }" `; exports[`v-on > should wrap both for dynamic key event w/ left/right modifiers 1`] = ` -"import { withModifiers as _withModifiers, withKeys as _withKeys, on as _on, renderEffect as _renderEffect, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, withModifiers as _withModifiers, withKeys as _withKeys, on as _on, renderEffect as _renderEffect, template as _template } from 'vue'; const t0 = _template("
", true) export function render(_ctx) { const n0 = t0() _renderEffect(() => { - _on(n0, _ctx.e, _withKeys(_withModifiers(e => _ctx.test(e), ["left"]), ["left"]), { + _on(n0, _ctx.e, _createInvoker(_withKeys(_withModifiers(e => _ctx.test(e), ["left"]), ["left"])), { effect: true }) }) @@ -442,20 +442,20 @@ exports[`v-on > should wrap in unref if identifier is setup-maybe-ref w/ inline: const n0 = t0() const n1 = t0() const n2 = t0() - n0.$evtclick = () => (x.value=_unref(y)) - n1.$evtclick = () => (x.value++) - n2.$evtclick = () => ({ x: x.value } = _unref(y)) + n0.$evtclick = _createInvoker(() => (x.value=_unref(y))) + n1.$evtclick = _createInvoker(() => (x.value++)) + n2.$evtclick = _createInvoker(() => ({ x: x.value } = _unref(y))) return [n0, n1, n2] " `; exports[`v-on > should wrap keys guard for keyboard events or dynamic events 1`] = ` -"import { withModifiers as _withModifiers, withKeys as _withKeys, on as _on, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, withModifiers as _withModifiers, withKeys as _withKeys, on as _on, template as _template } from 'vue'; const t0 = _template("
", true) export function render(_ctx) { const n0 = t0() - _on(n0, "keydown", _withKeys(_withModifiers(e => _ctx.test(e), ["stop","ctrl"]), ["a"]), { + _on(n0, "keydown", _createInvoker(_withKeys(_withModifiers(e => _ctx.test(e), ["stop","ctrl"]), ["a"])), { capture: true }) return n0 @@ -463,25 +463,25 @@ export function render(_ctx) { `; exports[`v-on > should wrap keys guard for static key event w/ left/right modifiers 1`] = ` -"import { withKeys as _withKeys, delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, withKeys as _withKeys, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("keyup") export function render(_ctx) { const n0 = t0() - n0.$evtkeyup = _withKeys(e => _ctx.test(e), ["left"]) + n0.$evtkeyup = _createInvoker(_withKeys(e => _ctx.test(e), ["left"])) return n0 }" `; exports[`v-on > simple expression 1`] = ` -"import { delegateEvents as _delegateEvents, template as _template } from 'vue'; +"import { createInvoker as _createInvoker, delegateEvents as _delegateEvents, template as _template } from 'vue'; const t0 = _template("
", true) _delegateEvents("click") export function render(_ctx, $props, $emit, $attrs, $slots) { const n0 = t0() - n0.$evtclick = _ctx.handleClick + n0.$evtclick = _createInvoker(_ctx.handleClick) return n0 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/vOn.spec.ts b/packages/compiler-vapor/__tests__/transforms/vOn.spec.ts index 963f46ad2a..eab3931ca1 100644 --- a/packages/compiler-vapor/__tests__/transforms/vOn.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vOn.spec.ts @@ -165,7 +165,7 @@ describe('v-on', () => { delegate: true, }, ]) - expect(code).contains(`n0.$evtclick = () => (_ctx.i++)`) + expect(code).contains(`n0.$evtclick = _createInvoker(() => (_ctx.i++))`) }) test('should wrap in unref if identifier is setup-maybe-ref w/ inline: true', () => { @@ -182,9 +182,13 @@ describe('v-on', () => { ) expect(code).matchSnapshot() expect(helpers).contains('unref') - expect(code).contains(`n0.$evtclick = () => (x.value=_unref(y))`) - expect(code).contains(`n1.$evtclick = () => (x.value++)`) - expect(code).contains(`n2.$evtclick = () => ({ x: x.value } = _unref(y))`) + expect(code).contains( + `n0.$evtclick = _createInvoker(() => (x.value=_unref(y)))`, + ) + expect(code).contains(`n1.$evtclick = _createInvoker(() => (x.value++))`) + expect(code).contains( + `n2.$evtclick = _createInvoker(() => ({ x: x.value } = _unref(y)))`, + ) }) test('should handle multiple inline statement', () => { @@ -200,7 +204,9 @@ describe('v-on', () => { // should wrap with `{` for multiple statements // in this case the return value is discarded and the behavior is // consistent with 2.x - expect(code).contains(`n0.$evtclick = () => {_ctx.foo();_ctx.bar()}`) + expect(code).contains( + `n0.$evtclick = _createInvoker(() => {_ctx.foo();_ctx.bar()})`, + ) }) test('should handle multi-line statement', () => { @@ -216,7 +222,9 @@ describe('v-on', () => { // should wrap with `{` for multiple statements // in this case the return value is discarded and the behavior is // consistent with 2.x - expect(code).contains(`n0.$evtclick = () => {\n_ctx.foo();\n_ctx.bar()\n}`) + expect(code).contains( + `n0.$evtclick = _createInvoker(() => {\n_ctx.foo();\n_ctx.bar()\n})`, + ) }) test('inline statement w/ prefixIdentifiers: true', () => { @@ -232,7 +240,9 @@ describe('v-on', () => { }, ]) // should NOT prefix $event - expect(code).contains(`n0.$evtclick = $event => (_ctx.foo($event))`) + expect(code).contains( + `n0.$evtclick = _createInvoker($event => (_ctx.foo($event)))`, + ) }) test('multiple inline statements w/ prefixIdentifiers: true', () => { @@ -249,7 +259,7 @@ describe('v-on', () => { ]) // should NOT prefix $event expect(code).contains( - `n0.$evtclick = $event => {_ctx.foo($event);_ctx.bar()}`, + `n0.$evtclick = _createInvoker($event => {_ctx.foo($event);_ctx.bar()})`, ) }) @@ -263,7 +273,9 @@ describe('v-on', () => { value: { content: '$event => foo($event)' }, }, ]) - expect(code).contains(`n0.$evtclick = $event => _ctx.foo($event)`) + expect(code).contains( + `n0.$evtclick = _createInvoker($event => _ctx.foo($event))`, + ) }) test('should NOT wrap as function if expression is already function expression (with Typescript)', () => { @@ -279,7 +291,9 @@ describe('v-on', () => { value: { content: '(e: any): any => foo(e)' }, }, ]) - expect(code).contains(`n0.$evtclick = (e: any): any => _ctx.foo(e)`) + expect(code).contains( + `n0.$evtclick = _createInvoker((e: any): any => _ctx.foo(e))`, + ) }) test('should NOT wrap as function if expression is already function expression (with newlines)', () => { @@ -344,7 +358,9 @@ describe('v-on', () => { ]) expect(code).matchSnapshot() - expect(code).contains(`n0.$evtclick = e => _ctx.a['b' + _ctx.c](e)`) + expect(code).contains( + `n0.$evtclick = _createInvoker(e => _ctx.a['b' + _ctx.c](e))`, + ) }) test('function expression w/ prefixIdentifiers: true', () => { @@ -359,7 +375,7 @@ describe('v-on', () => { value: { content: `e => foo(e)` }, }, ]) - expect(code).contains(`n0.$evtclick = e => _ctx.foo(e)`) + expect(code).contains(`n0.$evtclick = _createInvoker(e => _ctx.foo(e))`) }) test('should error if no expression AND no modifier', () => { @@ -414,7 +430,7 @@ describe('v-on', () => { }, ]) expect(code).contains( - `_on(n0, "click", _withModifiers(e => _ctx.test(e), ["stop","prevent"]), { + `_on(n0, "click", _createInvoker(_withModifiers(e => _ctx.test(e), ["stop","prevent"])), { capture: true, once: true })`, @@ -470,8 +486,8 @@ describe('v-on', () => { expect(code).matchSnapshot() expect(code).contains( - `n0.$evtclick = _withModifiers(e => _ctx.test(e), ["stop"]) - n0.$evtkeyup = _withKeys(e => _ctx.test(e), ["enter"])`, + `n0.$evtclick = _createInvoker(_withModifiers(e => _ctx.test(e), ["stop"])) + n0.$evtkeyup = _createInvoker(_withKeys(e => _ctx.test(e), ["enter"]))`, ) }) @@ -654,7 +670,7 @@ describe('v-on', () => { }) expect(code).matchSnapshot() - expect(code).contains(`n0.$evtclick = e => _ctx.foo.bar(e)`) + expect(code).contains(`n0.$evtclick = _createInvoker(e => _ctx.foo.bar(e))`) }) test('should delegate event', () => { @@ -677,9 +693,11 @@ describe('v-on', () => { ) expect(helpers).contains('delegate') expect(code).toMatchSnapshot() - expect(code).contains('_delegate(n0, "click", e => _ctx.test(e))') expect(code).contains( - '_delegate(n0, "click", _withModifiers(e => _ctx.test(e), ["stop"]))', + '_delegate(n0, "click", _createInvoker(e => _ctx.test(e)))', + ) + expect(code).contains( + '_delegate(n0, "click", _createInvoker(_withModifiers(e => _ctx.test(e), ["stop"])))', ) }) @@ -693,7 +711,9 @@ describe('v-on', () => { }, ) expect(code).matchSnapshot() - expect(code).include('n0.$evtclick = e => _ctx.handleClick(e)') + expect(code).include( + 'n0.$evtclick = _createInvoker(e => _ctx.handleClick(e))', + ) }) test('component event with special characters', () => { diff --git a/packages/compiler-vapor/src/generators/event.ts b/packages/compiler-vapor/src/generators/event.ts index cfb47b6118..cc2491bddf 100644 --- a/packages/compiler-vapor/src/generators/event.ts +++ b/packages/compiler-vapor/src/generators/event.ts @@ -28,7 +28,11 @@ export function genSetEvent( const { element, key, keyOverride, value, modifiers, delegate, effect } = oper const name = genName() - const handler = genEventHandler(context, value, modifiers) + const handler = [ + `${context.helper('createInvoker')}(`, + ...genEventHandler(context, value, modifiers), + `)`, + ] const eventOptions = genEventOptions() if (delegate) { diff --git a/packages/runtime-vapor/__tests__/errorHandling.spec.ts b/packages/runtime-vapor/__tests__/errorHandling.spec.ts index 87a79614d4..8935579c86 100644 --- a/packages/runtime-vapor/__tests__/errorHandling.spec.ts +++ b/packages/runtime-vapor/__tests__/errorHandling.spec.ts @@ -6,7 +6,14 @@ import { watch, watchEffect, } from '@vue/runtime-dom' -import { createComponent, createTemplateRefSetter, template } from '../src' +import { + createComponent, + createInvoker, + createTemplateRefSetter, + defineVaporComponent, + delegateEvents, + template, +} from '../src' import { makeRender } from './_utils' import type { VaporComponent } from '../src/component' import type { RefEl } from '../src/apiTemplateRef' @@ -364,6 +371,38 @@ describe('error handling', () => { expect(fn).toHaveBeenCalledWith(err, 'watcher cleanup function') }) + test('in dom event handler', () => { + const err = new Error('foo') + const fn = vi.fn() + + const Comp = { + setup() { + onErrorCaptured((err, instance, info) => { + fn(err, info) + return false + }) + return createComponent(Child) + }, + } + + delegateEvents('click') + const Child = defineVaporComponent({ + setup() { + function onClick() { + throw err + } + const n0 = template('', true)() as any + n0.$evtclick = createInvoker(onClick) + return n0 + }, + }) + + const { host } = define(Comp).render() + const btn = host.querySelector('button') as HTMLButtonElement + btn.click() + expect(fn).toHaveBeenCalledWith(err, 'native event handler') + }) + test('in component event handler via emit', () => { const err = new Error('foo') const fn = vi.fn() diff --git a/packages/runtime-vapor/src/dom/event.ts b/packages/runtime-vapor/src/dom/event.ts index 4987ecfc0d..90f7d60ed2 100644 --- a/packages/runtime-vapor/src/dom/event.ts +++ b/packages/runtime-vapor/src/dom/event.ts @@ -1,5 +1,6 @@ import { onEffectCleanup } from '@vue/reactivity' import { isArray } from '@vue/shared' +import { ErrorCodes, callWithAsyncErrorHandling, currentInstance } from 'vue' export function addEventListener( el: Element, @@ -107,3 +108,16 @@ export function setDynamicEvents( on(el, name, events[name], { effect: true }) } } + +export function createInvoker( + handler: (...args: any[]) => any, +): (...args: any[]) => any { + const i = currentInstance + return (...args: any[]) => + callWithAsyncErrorHandling( + handler, + i, + ErrorCodes.NATIVE_EVENT_HANDLER, + args, + ) +} diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index 7a8aea5a0d..4550db497a 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -27,7 +27,13 @@ export { setDOMProp, setDynamicProps, } from './dom/prop' -export { on, delegate, delegateEvents, setDynamicEvents } from './dom/event' +export { + on, + delegate, + delegateEvents, + setDynamicEvents, + createInvoker, +} from './dom/event' export { createIf } from './apiCreateIf' export { createFor, -- 2.47.2