Skip to content

perf(runtime-dom): optimize array event handler dispatch#14828

Merged
edison1105 merged 3 commits into
vuejs:mainfrom
wyf027:perf/runtime-dom-event-array-handlers
May 27, 2026
Merged

perf(runtime-dom): optimize array event handler dispatch#14828
edison1105 merged 3 commits into
vuejs:mainfrom
wyf027:perf/runtime-dom-event-array-handlers

Conversation

@wyf027

@wyf027 wyf027 commented May 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Avoid per-dispatch Array.map and wrapper closure allocations for runtime-dom event handler arrays.
  • Preserve stopImmediatePropagation() behavior by stopping later handlers in the same array.
  • Add regression coverage for the allocation path and stop-immediate behavior.
  • Add a runtime-dom event dispatch benchmark for single vs multiple handlers.

Test Plan

  • GitHub Actions passed on this PR:
    • test / unit-test
    • test / unit-test-windows
    • test / lint-and-test-dts
    • test / e2e-test
  • Patch syntax/hunk counts were validated locally before committing.
  • git apply --check was run against the fetched upstream target files before creating this branch.
  • Not run locally: this change was prepared via GitHub API without a full local checkout.

Suggested benchmark command:

pnpm vitest bench packages/runtime-dom/__benchmarks__/events.bench.ts

Summary by CodeRabbit

  • Refactor

    • Optimized event dispatch handling for multiple event handlers.
  • Tests

    • Added test to verify event handler arrays are correctly managed during dispatch, ensuring mutations don't affect the current event cycle.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 16, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 531ced0f-fead-4ab0-8531-83f62da6765e

📥 Commits

Reviewing files that changed from the base of the PR and between cc5d253 and 94babcf.

📒 Files selected for processing (2)
  • packages/runtime-dom/__tests__/patchEvents.spec.ts
  • packages/runtime-dom/src/modules/events.ts

📝 Walkthrough

Walkthrough

Event dispatch logic is inlined within createInvoker to handle handler arrays directly. When an invoker's value is an array, the dispatch branches to override stopImmediatePropagation, copy the handler list, and iterate through handlers until stopped. Non-array values invoke a single handler. A test validates that handler array mutations during dispatch do not affect the current dispatch.

Changes

Event Handler Array Processing

Layer / File(s) Summary
Event handler array inlining
packages/runtime-dom/src/modules/events.ts
createInvoker's event handler branches on invoker.value type. Arrays iterate with early termination when stopImmediatePropagation is called; non-arrays invoke once. The patchStopImmediatePropagation helper is removed.
Handler array snapshot validation
packages/runtime-dom/__tests__/patchEvents.spec.ts
New test verifies that handler arrays are snapshot at dispatch time, so mutations to handlers during dispatch (replacement, appending) do not affect which handlers execute for that event.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Arrays now snap at dispatch time so true,
No mutations mid-flight will slip through!
Handlers iterate, each called with care,
Stop signals halt them fair and square. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'perf(runtime-dom): optimize array event handler dispatch' accurately summarizes the main change: optimizing event handler array dispatch to avoid allocations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new

pkg-pr-new Bot commented May 16, 2026

Copy link
Copy Markdown

Open in StackBlitz

@vue/compiler-core

pnpm add https://pkg.pr.new/@vue/compiler-core@14828
npm i https://pkg.pr.new/@vue/compiler-core@14828
yarn add https://pkg.pr.new/@vue/compiler-core@14828.tgz

@vue/compiler-dom

pnpm add https://pkg.pr.new/@vue/compiler-dom@14828
npm i https://pkg.pr.new/@vue/compiler-dom@14828
yarn add https://pkg.pr.new/@vue/compiler-dom@14828.tgz

@vue/compiler-sfc

pnpm add https://pkg.pr.new/@vue/compiler-sfc@14828
npm i https://pkg.pr.new/@vue/compiler-sfc@14828
yarn add https://pkg.pr.new/@vue/compiler-sfc@14828.tgz

@vue/compiler-ssr

pnpm add https://pkg.pr.new/@vue/compiler-ssr@14828
npm i https://pkg.pr.new/@vue/compiler-ssr@14828
yarn add https://pkg.pr.new/@vue/compiler-ssr@14828.tgz

@vue/reactivity

pnpm add https://pkg.pr.new/@vue/reactivity@14828
npm i https://pkg.pr.new/@vue/reactivity@14828
yarn add https://pkg.pr.new/@vue/reactivity@14828.tgz

@vue/runtime-core

pnpm add https://pkg.pr.new/@vue/runtime-core@14828
npm i https://pkg.pr.new/@vue/runtime-core@14828
yarn add https://pkg.pr.new/@vue/runtime-core@14828.tgz

@vue/runtime-dom

pnpm add https://pkg.pr.new/@vue/runtime-dom@14828
npm i https://pkg.pr.new/@vue/runtime-dom@14828
yarn add https://pkg.pr.new/@vue/runtime-dom@14828.tgz

@vue/server-renderer

pnpm add https://pkg.pr.new/@vue/server-renderer@14828
npm i https://pkg.pr.new/@vue/server-renderer@14828
yarn add https://pkg.pr.new/@vue/server-renderer@14828.tgz

@vue/shared

pnpm add https://pkg.pr.new/@vue/shared@14828
npm i https://pkg.pr.new/@vue/shared@14828
yarn add https://pkg.pr.new/@vue/shared@14828.tgz

vue

pnpm add https://pkg.pr.new/vue@14828
npm i https://pkg.pr.new/vue@14828
yarn add https://pkg.pr.new/vue@14828.tgz

@vue/compat

pnpm add https://pkg.pr.new/@vue/compat@14828
npm i https://pkg.pr.new/@vue/compat@14828
yarn add https://pkg.pr.new/@vue/compat@14828.tgz

commit: 94babcf

@github-actions

Copy link
Copy Markdown

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 106 kB (+13 B) 40 kB (-4 B) 35.9 kB (-18 B)
vue.global.prod.js 164 kB (+13 B) 60 kB (-1 B) 53.4 kB (+5 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 48.7 kB (+13 B) 18.9 kB (+1 B) 17.3 kB (-10 B)
createApp 56.8 kB (+15 B) 22 kB (-6 B) 20.1 kB (-5 B)
createSSRApp 61.1 kB (+13 B) 23.7 kB (-6 B) 21.6 kB (-7 B)
defineCustomElement 63 kB (+13 B) 23.9 kB (-5 B) 21.8 kB (-1 B)
overall 71.6 kB (+13 B) 27.4 kB (-3 B) 25 kB (+40 B)

@wyf027 wyf027 force-pushed the perf/runtime-dom-event-array-handlers branch from bf46e08 to cc5d253 Compare May 16, 2026 05:04
@wyf027 wyf027 marked this pull request as ready for review May 16, 2026 15:25

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cc5d253fa5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/runtime-dom/src/modules/events.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/runtime-dom/src/modules/events.ts (1)

122-133: ⚡ Quick win

Avoid per-handler args-array allocations in the hot loop.

This path still allocates [e] for every handler invocation. Hoist it once per dispatch to keep the allocation reduction goal intact.

♻️ Proposed fix
     if (isArray(value)) {
       const originalStop = e.stopImmediatePropagation
       e.stopImmediatePropagation = () => {
         originalStop.call(e)
         ;(e as any)._stopped = true
       }
+      const args = [e]
       for (let i = 0; i < value.length; i++) {
         if ((e as any)._stopped) {
           break
         }
         const handler = value[i]
         if (handler) {
           callWithAsyncErrorHandling(
             handler,
             instance,
             ErrorCodes.NATIVE_EVENT_HANDLER,
-            [e],
+            args,
           )
         }
       }
     } else {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/runtime-dom/src/modules/events.ts` around lines 122 - 133, The
hot-loop currently allocates a new args array for every handler call by passing
[e] inline; hoist that allocation out of the loop by creating a single const
args = [e] immediately before the for loop and pass args into
callWithAsyncErrorHandling instead. Update the loop in events.ts where value
(the handlers array) is iterated and ensure callWithAsyncErrorHandling(handler,
instance, ErrorCodes.NATIVE_EVENT_HANDLER, args) is used so no per-handler array
allocations occur.
packages/runtime-dom/__benchmarks__/events.bench.ts (1)

5-22: ⚡ Quick win

Benchmark body currently mixes setup cost with dispatch cost.

Given the bench names, consider moving createElement + patchProp setup outside the timed callback so the metric isolates dispatch performance.

📊 Proposed refactor
 describe('runtime-dom events', () => {
+  const singleEl = document.createElement('button')
+  let singleCount = 0
+  patchProp(singleEl, 'onClick', null, () => singleCount++)
+
+  const multiEl = document.createElement('button')
+  let multiCount = 0
+  patchProp(multiEl, 'onClick', null, [
+    () => multiCount++,
+    () => multiCount++,
+    () => multiCount++,
+    () => multiCount++,
+  ])
+
   bench('dispatch click with single handler', () => {
-    const el = document.createElement('button')
-    let count = 0
-    patchProp(el, 'onClick', null, () => count++)
-    el.dispatchEvent(new Event('click'))
+    singleEl.dispatchEvent(new Event('click'))
   })
 
   bench('dispatch click with multiple handlers', () => {
-    const el = document.createElement('button')
-    let count = 0
-    patchProp(el, 'onClick', null, [
-      () => count++,
-      () => count++,
-      () => count++,
-      () => count++,
-    ])
-    el.dispatchEvent(new Event('click'))
+    multiEl.dispatchEvent(new Event('click'))
   })
 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/runtime-dom/__benchmarks__/events.bench.ts` around lines 5 - 22, The
benchmark callbacks currently include setup (document.createElement + patchProp)
which contaminates dispatch timing; extract the setup for both benches so only
el.dispatchEvent(new Event('click')) runs inside each bench callback: create the
button element once per bench, call patchProp(el, 'onClick', null, ...)
beforehand (for the single-handler and the array-of-handlers cases), then have
the bench callback simply call el.dispatchEvent(...). Reference the existing
bench(...) invocations, document.createElement, patchProp, and el.dispatchEvent
when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/runtime-dom/__benchmarks__/events.bench.ts`:
- Around line 5-22: The benchmark callbacks currently include setup
(document.createElement + patchProp) which contaminates dispatch timing; extract
the setup for both benches so only el.dispatchEvent(new Event('click')) runs
inside each bench callback: create the button element once per bench, call
patchProp(el, 'onClick', null, ...) beforehand (for the single-handler and the
array-of-handlers cases), then have the bench callback simply call
el.dispatchEvent(...). Reference the existing bench(...) invocations,
document.createElement, patchProp, and el.dispatchEvent when making the change.

In `@packages/runtime-dom/src/modules/events.ts`:
- Around line 122-133: The hot-loop currently allocates a new args array for
every handler call by passing [e] inline; hoist that allocation out of the loop
by creating a single const args = [e] immediately before the for loop and pass
args into callWithAsyncErrorHandling instead. Update the loop in events.ts where
value (the handlers array) is iterated and ensure
callWithAsyncErrorHandling(handler, instance, ErrorCodes.NATIVE_EVENT_HANDLER,
args) is used so no per-handler array allocations occur.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d77a28ed-4eb2-4916-88f5-8fc6d8e8aeca

📥 Commits

Reviewing files that changed from the base of the PR and between 1ce598e and cc5d253.

📒 Files selected for processing (3)
  • packages/runtime-dom/__benchmarks__/events.bench.ts
  • packages/runtime-dom/__tests__/patchProps.spec.ts
  • packages/runtime-dom/src/modules/events.ts

@edison1105 edison1105 changed the title perf(runtime-dom): avoid event handler array allocations perf(runtime-dom): optimize array event handler dispatch May 26, 2026
@edison1105 edison1105 added ready to merge The PR is ready to be merged. 🍰 p2-nice-to-have Priority 2: this is not breaking anything but nice to have it addressed. labels May 26, 2026
@edison1105

Copy link
Copy Markdown
Member

/ecosystem-ci run

@vue-bot

vue-bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

📝 Ran ecosystem CI: Open

suite result latest scheduled
nuxt failure failure
pinia success success
vant success success
vite-plugin-vue success success
vue-i18n success success
radix-vue success success
vueuse success success
vue-macros success success
language-tools success success
test-utils success success
primevue success success
vuetify success success
vitepress success success
vue-simple-compiler success success
router success success
quasar failure failure

@edison1105 edison1105 merged commit bb18dc8 into vuejs:main May 27, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🍰 p2-nice-to-have Priority 2: this is not breaking anything but nice to have it addressed. ready to merge The PR is ready to be merged.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants