Skip to content

fix(compiler-core): prevent cached array children from retaining detached dom nodes #13691

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

edison1105
Copy link
Member

@edison1105 edison1105 commented Jul 23, 2025

fix element-plus/element-plus#21408
Re-fix #13211, as the associated PR #13215 did not resolve the issue correctly.
This PR reverts #13215 and re-applies a fix for #13211, addressing and covering memory leak issues present in the following two scenarios (the scenario is the same in both cases: cache[1] is an array).

Playground
Playground with slots

The cached vnodes get replaced by cloned ones during mountChildren, which bind DOM elements. These DOM references persist after unmount, preventing garbage collection. Array spread avoids mutating the cached array, preventing memory leaks.

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of cached array and text nodes to prevent memory leaks, ensuring detached DOM nodes are properly cleaned up.
    • Refined slot object behavior by removing internal properties that are no longer needed.
  • Tests

    • Added new end-to-end tests to verify that cached array vnodes and text nodes in fragments do not retain detached DOM nodes, confirming proper memory management.
    • Updated existing test cases to reflect changes in slot object properties and caching logic.

Copy link

coderabbitai bot commented Jul 23, 2025

Walkthrough

This change eliminates internal slot cache key tracking in both the compiler and runtime, removes the __ property from slots, and always enables array spreading for cached array vnodes to prevent memory leaks. It updates related tests and adds end-to-end tests to verify that cached array vnodes and text nodes do not retain detached DOM nodes after component unmount.

Changes

File(s) Change Summary
packages/compiler-core/src/transforms/cacheStatic.ts Removed slot cache key tracking and all logic for attaching __ to slot objects; always sets needArraySpread to true.
packages/compiler-core/tests/transforms/cacheStatic.spec.ts Updated tests to reflect removal of the __ property from slot objects and default needArraySpread to true.
packages/runtime-core/src/componentSlots.ts Removed __ property from RawSlots type and related logic; updated internal key detection.
packages/runtime-core/tests/componentSlots.spec.ts Updated tests to remove checks for the __ property and focus on the _ property.
packages/runtime-core/src/renderer.ts Removed logic for clearing parent's render cache using slot cache keys; improved handling of cached text nodes.
packages/vue/tests/e2e/memory-leak.spec.ts Added two end-to-end tests verifying no memory leaks for cached array vnodes and text nodes in fragments.

Sequence Diagram(s)

sequenceDiagram
    participant TestRunner as E2E Test Runner
    participant App as Vue App
    participant Comp as Component with Cached Array/Text Nodes
    participant GC as Garbage Collector

    TestRunner->>App: Mounts component with large array/text nodes
    App->>Comp: Renders cached array/text nodes
    TestRunner->>App: Toggles component off (unmount)
    App->>Comp: Unmounts, removes from DOM
    loop Until WeakRef is cleared
        TestRunner->>GC: Triggers garbage collection
        GC->>Comp: Attempts to collect detached nodes
    end
    TestRunner->>TestRunner: Asserts WeakRef is cleared (no leak)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

ready to merge, scope: compiler, :exclamation: p4-important

Suggested reviewers

  • skirtles-code
  • yyx990803

Poem

A patch to help our memory stay light,
No more slots with hidden keys in sight!
Cached arrays and text, now swept away,
When components unmount, they do not stay.
With tests to ensure the leaks are gone,
This bunny hops forward—code marches on! 🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 01fdb51 and 639fc0b.

📒 Files selected for processing (1)
  • packages/compiler-core/src/transforms/cacheStatic.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/compiler-core/src/transforms/cacheStatic.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Redirect rules
  • GitHub Check: Header rules
  • GitHub Check: Pages changed
  • GitHub Check: test / e2e-test
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch edison/fix/refix13211

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

github-actions bot commented Jul 23, 2025

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 101 kB (-104 B) 38.4 kB (-35 B) 34.6 kB (-29 B)
vue.global.prod.js 159 kB (-430 B) 58.5 kB (-92 B) 52.1 kB (-102 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 46.5 kB (-104 B) 18.2 kB (-31 B) 16.7 kB (-28 B)
createApp 54.5 kB (-104 B) 21.2 kB (-35 B) 19.4 kB (-32 B)
createSSRApp 58.7 kB (-104 B) 22.9 kB (-32 B) 20.9 kB (-31 B)
defineCustomElement 59.5 kB (-104 B) 22.8 kB (-43 B) 20.8 kB (-32 B)
overall 68.6 kB (-104 B) 26.4 kB (-39 B) 24 kB (-43 B)

Copy link

pkg-pr-new bot commented Jul 23, 2025

Open in StackBlitz

@vue/compiler-core

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

@vue/compiler-dom

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

@vue/compiler-sfc

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

@vue/compiler-ssr

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

@vue/reactivity

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

@vue/runtime-core

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

@vue/runtime-dom

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

@vue/server-renderer

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

@vue/shared

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

vue

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

@vue/compat

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

commit: 639fc0b

@edison1105 edison1105 force-pushed the edison/fix/refix13211 branch 2 times, most recently from 1f482ab to eab33bf Compare July 23, 2025 03:50
@edison1105 edison1105 force-pushed the edison/fix/refix13211 branch 2 times, most recently from 41c3435 to ec921ec Compare July 24, 2025 03:31
@edison1105 edison1105 force-pushed the edison/fix/refix13211 branch from ec921ec to c577ad7 Compare July 24, 2025 03:32
@edison1105 edison1105 marked this pull request as ready for review July 24, 2025 03:51
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/vue/__tests__/e2e/memory-leak.spec.ts (1)

88-88: Fix grammatical error in test description.

The test description should use "retain" instead of "retaining".

-    'cached array vnodes should not retaining detached DOM nodes',
+    'cached array vnodes should not retain detached DOM nodes',
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c486536 and b6f0b7b.

⛔ Files ignored due to path filters (4)
  • packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap is excluded by !**/*.snap
  • packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap is excluded by !**/*.snap
  • packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap is excluded by !**/*.snap
  • packages/compiler-sfc/__tests__/__snapshots__/templateTransformSrcset.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (9)
  • packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts (0 hunks)
  • packages/compiler-core/src/ast.ts (2 hunks)
  • packages/compiler-core/src/codegen.ts (2 hunks)
  • packages/compiler-core/src/transforms/cacheStatic.ts (1 hunks)
  • packages/runtime-core/__tests__/componentSlots.spec.ts (1 hunks)
  • packages/runtime-core/src/componentSlots.ts (1 hunks)
  • packages/runtime-core/src/renderer.ts (1 hunks)
  • packages/runtime-core/src/vnode.ts (2 hunks)
  • packages/vue/__tests__/e2e/memory-leak.spec.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/compiler-core/tests/transforms/cacheStatic.spec.ts
🧰 Additional context used
🧬 Code Graph Analysis (3)
packages/runtime-core/src/vnode.ts (1)
packages/shared/src/general.ts (1)
  • isArray (39-39)
packages/compiler-core/src/transforms/cacheStatic.ts (1)
packages/compiler-core/src/ast.ts (2)
  • JSChildNode (349-359)
  • CacheExpression (416-424)
packages/vue/__tests__/e2e/memory-leak.spec.ts (1)
packages/vue/__tests__/e2e/e2eUtils.ts (1)
  • E2E_TIMEOUT (8-8)
🔇 Additional comments (12)
packages/runtime-core/__tests__/componentSlots.spec.ts (1)

62-62: LGTM - Test correctly updated to reflect removed functionality.

The test properly removes the __ property from the slots object, aligning with the removal of internal slot cache key tracking throughout the codebase.

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

683-687: Critical fix for memory leak prevention.

This change correctly addresses the core issue by deep cloning cached array children to prevent them from retaining detached DOM nodes. The removal of the __DEV__ conditional ensures this fix applies in production environments, which is essential for preventing memory leaks.

The logic properly checks for both PatchFlags.CACHED and isArray(children) before applying the expensive deep clone operation.


743-743: Minor documentation improvement.

Good cleanup removing the "Dev only" prefix since this function is now used in production for cached vnode cloning.

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

85-85: Properly removes internal cache key tracking.

The removal of "__" from the internal key check is consistent with eliminating the slot cache key tracking mechanism. This simplifies the slot handling by removing the unused cache key property.

packages/compiler-core/src/ast.ts (2)

423-423: Good addition of array tracking for cached expressions.

The cachedAsArray property provides the necessary metadata to determine when cached expressions need array cloning to prevent memory leaks.


788-788: Appropriate default initialization.

Initializing cachedAsArray to false is correct since most cached expressions are not arrays. This will be set to true only when needed by the transform phase.

packages/compiler-core/src/codegen.ts (3)

1015-1015: Correctly extracts the new array flag.

Proper destructuring of the cachedAsArray property for use in code generation logic.


1019-1019: Adds necessary parentheses for array cloning.

The opening parenthesis ensures proper grouping when the .slice() call is added later, generating code like (_cache[0] = value).slice().


1031-1031: Implements array cloning for memory leak prevention.

The .slice() call creates a shallow clone of cached arrays, preventing them from retaining references to detached DOM nodes. This is the key fix that complements the runtime changes in vnode.ts.

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

2280-2280: LGTM! Clean removal of unused destructuring.

This change appropriately removes the destructuring of unused variables as part of eliminating the slot cache key tracking mechanism.

packages/compiler-core/src/transforms/cacheStatic.ts (1)

217-230: LGTM! Proper implementation of array cache tracking.

The addition of the cachedAsArray parameter and property assignment correctly enables the code generator to handle cached arrays differently, preventing memory leaks from retained DOM nodes. The default value of true is appropriate since most cached expressions in this context are arrays.

packages/vue/__tests__/e2e/memory-leak.spec.ts (1)

86-145: Well-designed test for verifying the memory leak fix.

The test effectively validates that cached array vnodes are properly released after component unmount, preventing memory leaks from retained DOM nodes. The use of WeakRef and forced garbage collection provides a reliable way to verify the fix.

@edison1105 edison1105 force-pushed the edison/fix/refix13211 branch from b2e450e to 6cd3e1f Compare July 24, 2025 06:09
@edison1105 edison1105 force-pushed the edison/fix/refix13211 branch from 01fdb51 to 639fc0b Compare July 24, 2025 09:48
@edison1105 edison1105 added scope: compiler ❗ p4-important Priority 4: this fixes bugs that violate documented behavior, or significantly improves perf. labels Jul 24, 2025
Copy link

netlify bot commented Jul 25, 2025

Deploy Preview for vue-next-template-explorer failed. Why did it fail? →

Name Link
🔨 Latest commit b2e450e
🔍 Latest deploy log https://app.netlify.com/projects/vue-next-template-explorer/deploys/6881cd753109e8000836fefd

Copy link

netlify bot commented Jul 25, 2025

Deploy Preview for vue-sfc-playground failed. Why did it fail? →

Name Link
🔨 Latest commit 6cd3e1f
🔍 Latest deploy log https://app.netlify.com/projects/vue-sfc-playground/deploys/6881ce17e58ebb0008cf92cf

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
❗ p4-important Priority 4: this fixes bugs that violate documented behavior, or significantly improves perf. scope: compiler
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Performance] [dialog] El-dialog组件内存泄漏 Detached elements due to cached static vnodes
1 participant