Skip to content

Conversation

@skirtles-code
Copy link
Contributor

@skirtles-code skirtles-code commented May 25, 2025

This PR builds on the work done in #12236 to try to make the compat build more accurately emulate Vue 2.

Watching arrays in Vue 2 behaved like deep: 1, rather than deep: true, so mutating nested objects should not trigger the watcher. Support for a numeric depth was added in 3.5 and wasn't available when the compat build was first implemented.

But there are several other inconsistencies in the compat build. Some of these seem to have been introduced by #9927, and worked correctly prior to 3.5:

  • The special handling for arrays should not depend on the initial value of the property, only the current value needs to be an array. In practice, it's quite common for a property to have an initial value of null while data is loading.
  • The special handling is also supposed to apply to this.$watch(), not just the watch option.
  • The compat warning should not be shown when deep: true has been set.

There were relatively few tests for WATCH_ARRAY, so it's understandable that these regressions were introduced. I've added a lot of extra tests to try to avoid future problems.

I've introduced a compatWatchArray flag to the internal watcher options as a way to tell @vue/reactivity that we want the special handling of arrays. This only impacts the equality check, which needs to behave like a deep watcher only if the current value is an array. I didn't want to add this flag to the public types, so there's a bit of as any hackery to access it.

Summary by CodeRabbit

  • New Features

    • Improved compatibility for watching arrays, ensuring watchers are triggered as expected when using legacy array watching behavior in compatibility mode.
  • Bug Fixes

    • Enhanced handling of array watchers to better support legacy scenarios and prevent missed updates.
  • Tests

    • Expanded and reorganized test coverage for array watching compatibility, including more scenarios and dynamic behaviors.

@coderabbitai
Copy link

coderabbitai bot commented May 25, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Compatibility logic for array watching was added and refactored across the reactivity and runtime-core modules. The watch callback triggering conditions were extended to support legacy array semantics under a compatibility flag. The watcher getter logic was centralized and reused, and the test suite for the WATCH_ARRAY deprecation was significantly expanded to cover more scenarios.

Changes

File(s) Change Summary
packages/reactivity/src/watch.ts Extended watch callback triggering to include compat mode with compatWatchArray for arrays.
packages/runtime-core/src/apiWatch.ts Added createCompatWatchGetter for compat array watching; updated instanceWatch for compat array logic.
packages/runtime-core/src/componentOptions.ts Refactored compat array watch logic to use createCompatWatchGetter; made createWatcher internal.
packages/vue-compat/tests/misc.spec.ts Replaced single test with comprehensive suite for WATCH_ARRAY deprecation and compat behaviors.

Sequence Diagram(s)

sequenceDiagram
    participant Component
    participant Watcher
    participant CompatGetter
    participant Callback

    Component->>Watcher: Register watch on array property (compat mode)
    Watcher->>CompatGetter: Get value (array)
    CompatGetter->>Watcher: Return deeply traversed array (depth 1)
    Watcher->>Callback: Invoke callback if compatWatchArray flag and array detected
Loading

Poem

A bunny with code in its paws,
Watches arrays with legacy laws.
Compat flags set, old flows revive,
Deep or shallow, the tests now thrive.
With every tick, the watcher’s aware—
Arrays in harmony, handled with care!
🐇✨

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
Copy link

pkg-pr-new bot commented May 25, 2025

Open in StackBlitz

@vue/compiler-core

npm i https://pkg.pr.new/@vue/compiler-core@13377

@vue/compiler-dom

npm i https://pkg.pr.new/@vue/compiler-dom@13377

@vue/compiler-sfc

npm i https://pkg.pr.new/@vue/compiler-sfc@13377

@vue/compiler-ssr

npm i https://pkg.pr.new/@vue/compiler-ssr@13377

@vue/compiler-vapor

npm i https://pkg.pr.new/@vue/compiler-vapor@13377

@vue/reactivity

npm i https://pkg.pr.new/@vue/reactivity@13377

@vue/runtime-core

npm i https://pkg.pr.new/@vue/runtime-core@13377

@vue/runtime-dom

npm i https://pkg.pr.new/@vue/runtime-dom@13377

@vue/runtime-vapor

npm i https://pkg.pr.new/@vue/runtime-vapor@13377

@vue/server-renderer

npm i https://pkg.pr.new/@vue/server-renderer@13377

@vue/shared

npm i https://pkg.pr.new/@vue/shared@13377

vue

npm i https://pkg.pr.new/vue@13377

@vue/compat

npm i https://pkg.pr.new/@vue/compat@13377

commit: 5c2163e

@github-actions
Copy link

github-actions bot commented May 25, 2025

Size Report

Bundles

File Size Gzip Brotli
compiler-dom.global.prod.js 85.2 kB 29.9 kB 26.4 kB
runtime-dom.global.prod.js 108 kB 40.4 kB 36.4 kB
vue.global.prod.js 166 kB 60.4 kB 53.8 kB

Usages

Name Size Gzip Brotli
createApp (CAPI only) 47.9 kB 18.7 kB 17.1 kB
createApp 57 kB 21.9 kB 20 kB
createApp + vaporInteropPlugin 91.2 kB 34.1 kB 30.8 kB
createVaporApp 34.8 kB 13.4 kB 12.2 kB
createSSRApp 61.3 kB 23.7 kB 21.6 kB
defineCustomElement 63.1 kB 23.8 kB 21.7 kB
overall 72.1 kB 27.3 kB 24.9 kB

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/runtime-core/src/apiWatch.ts (1)

289-299: Address static analysis suggestion for optional chaining.

The compatibility logic is correct, but there's an opportunity to improve code quality by using optional chaining as suggested by the static analysis tool.

Apply this diff to use optional chaining:

-    const deep = options && options.deep
+    const deep = options?.deep
🧰 Tools
🪛 Biome (1.9.4)

[error] 294-294: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9fa787c and 351ce06.

📒 Files selected for processing (4)
  • packages/reactivity/src/watch.ts (1 hunks)
  • packages/runtime-core/src/apiWatch.ts (5 hunks)
  • packages/runtime-core/src/componentOptions.ts (5 hunks)
  • packages/vue-compat/__tests__/misc.spec.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
packages/reactivity/src/watch.ts (1)
packages/shared/src/general.ts (2)
  • hasChanged (143-144)
  • isArray (39-39)
packages/runtime-core/src/componentOptions.ts (3)
packages/runtime-core/src/index.ts (1)
  • DeprecationTypes (479-481)
packages/shared/src/general.ts (3)
  • isObject (53-54)
  • isArray (39-39)
  • isFunction (49-50)
packages/runtime-core/src/apiWatch.ts (1)
  • createCompatWatchGetter (252-266)
🪛 Biome (1.9.4)
packages/runtime-core/src/apiWatch.ts

[error] 294-294: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Redirect rules
  • GitHub Check: Header rules
  • GitHub Check: Pages changed
🔇 Additional comments (5)
packages/reactivity/src/watch.ts (1)

248-249: LGTM: Correctly implements array watching compatibility.

The additional condition properly bypasses normal change detection for arrays under compatibility mode, ensuring Vue 2-like behavior where array mutations trigger watchers even when the array reference hasn't changed.

packages/runtime-core/src/apiWatch.ts (1)

252-266: LGTM: Well-designed compatibility wrapper function.

The createCompatWatchGetter function correctly wraps the base getter and conditionally traverses arrays with depth 1 when the WATCH_ARRAY compatibility flag is enabled. This approach is clean and reusable.

packages/vue-compat/__tests__/misc.spec.ts (1)

50-421: Excellent comprehensive test coverage.

The expanded test suite thoroughly covers the WATCH_ARRAY compatibility feature with well-structured scenarios for both the watch option and $watch() method. The tests appropriately verify:

  • Basic array watching behavior and deprecation warnings
  • Dynamic depth behavior when switching between objects and arrays
  • Proper handling of the deep: true option
  • Component-level compatConfig overrides
  • Various option combinations (immediate, etc.)
  • Function getter behavior

This comprehensive coverage will help prevent regressions and ensure the compatibility feature works as expected.

packages/runtime-core/src/componentOptions.ts (2)

1-35: Clean import organization with proper type imports.

The import changes properly use import type for type-only imports and remove unused imports (traverse, getCurrentScope). The addition of createCompatWatchGetter aligns with the new compatibility approach.


857-866: Improved compatibility logic using centralized approach.

The refactored compatibility logic is cleaner and more maintainable than the previous approach. Instead of forcing deep: true, it now:

  1. Properly checks if deep is already explicitly set
  2. Uses the compatWatchArray flag to signal compatibility mode
  3. Leverages the reusable createCompatWatchGetter wrapper

This approach provides better separation of concerns and consistency across the codebase.

@edison1105 edison1105 added version: minor 🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. scope: v2 compat labels May 25, 2025
@edison1105
Copy link
Member

/ecosystem-ci run

@vue-bot
Copy link
Contributor

vue-bot commented May 26, 2025

📝 Ran ecosystem CI: Open

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

@edison1105
Copy link
Member

edison1105 commented May 26, 2025

@skirtles-code
the tests are all green after re-run.
I think the document https://v3-migration.vuejs.org/breaking-changes/watch.html should also be updated.
It is also possible that it should be merged into the minor branch
This PR looks good to me.

@edison1105 edison1105 added the ready to merge The PR is ready to be merged. label May 26, 2025
@edison1105 edison1105 moved this to Todo in Next Minor Jul 8, 2025
# Conflicts:
#	packages/reactivity/src/watch.ts
#	packages/runtime-core/src/apiWatch.ts
#	packages/runtime-core/src/componentOptions.ts
@skirtles-code skirtles-code changed the base branch from main to minor November 24, 2025 05:50
@skirtles-code
Copy link
Contributor Author

Updated to use minor as the target branch.

I had to add some extra lines to copy compatWatchArray from options to this inside WatcherEffect. This class doesn't exist on main and the options object was available directly instead.

@edison1105 edison1105 moved this from Todo to Ready To Merge in Next Minor Nov 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. ready to merge The PR is ready to be merged. scope: v2 compat version: minor

Projects

Status: Ready To Merge

Development

Successfully merging this pull request may close these issues.

Vue 2 migration build WATCH_ARRAY triggers on deep value modification

4 participants