Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

edison1105
Copy link
Member

@edison1105 edison1105 commented Sep 19, 2025

close #13904

Summary by CodeRabbit

  • Bug Fixes
    • Fixes incorrect or stale slot content when slots are provided with props in custom elements, ensuring slot content updates correctly.
  • Performance
    • Maintains efficient rendering for slots without props while ensuring accurate updates when props are present.
  • Tests
    • Adds a test validating dynamic slot prop propagation and reactive updates in custom elements.

Copy link

coderabbitai bot commented Sep 19, 2025

Walkthrough

Updates renderSlot to detect when slot props are passed and use PatchFlags.BAIL for those native <slot/> renders; adds a test validating reactive slot props in custom elements. No public API or signature changes. (≤50 words)

Changes

Cohort / File(s) Summary of Changes
Slot rendering patch flag update
packages/runtime-core/src/helpers/renderSlot.ts
Add hasProps check (Object.keys(props).length > 0); select PatchFlags.BAIL when props exist, otherwise PatchFlags.STABLE_FRAGMENT; applies only to custom-element native <slot/> path. No signature/API changes.
Custom element slot props test
packages/runtime-dom/__tests__/customElement.spec.ts
Add test "render slot props" that mounts a custom element rendering a slot with reactive props, asserts initial class on native slot, updates reactive ref, awaits nextTick, and asserts updated class on shadowRoot slot.

Sequence Diagram(s)

sequenceDiagram
    participant C as Component
    participant R as Renderer
    participant RS as renderSlot
    participant P as Patcher
    participant D as DOM

    C->>R: render()
    R->>RS: renderSlot(name, props, children)
    RS->>RS: hasProps = Object.keys(props).length > 0
    alt hasProps
        RS-->>P: create Fragment with PatchFlags.BAIL
        Note over RS,P: Bailout path for dynamic props (forces full patch)
    else no props
        RS-->>P: create Fragment with PatchFlags.STABLE_FRAGMENT
        Note over RS,P: Stable fragment optimization
    end
    P->>D: apply updates to native <slot/> in shadowRoot
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

scope: custom elements, scope: slots, :hammer: p3-minor-bug, ready for review

Poem

I hopped through render code, soft and spry,
Found props that made a slot reply.
Bail or stable — I chose with care,
Shadow classes updated in the air.
Thump-thump — tests pass, tail in the sky. 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "fix(custom-element): use PatchFlags.BAIL for slot when props are present" is concise, accurately describes the primary change (switching the slot PatchFlag in custom-element mode when props exist), and is clear to a reviewer scanning commit history; it does not include noisy file lists or vague wording. It highlights the important fix from the developer's perspective without claiming unrelated changes. This matches the PR title criteria.
Linked Issues Check ✅ Passed The change directly addresses the linked issue [#13904] by introducing a hasProps check and switching the slot rendering PatchFlag to PatchFlags.BAIL when props are present, which forces a full patch so slot attributes can respond to reactive updates; the added test "render slot props" verifies the slot's class updates when the reactive ref changes. The modifications are limited to the custom-element slot rendering path and include no public API signature changes, and the test exercises the reported failure mode. Based on the provided summaries, this satisfies the coding objective of making native slot attributes respond to reactivity.
Out of Scope Changes Check ✅ Passed All recorded changes are confined to packages/runtime-core/src/helpers/renderSlot.ts and a corresponding test in packages/runtime-dom/tests/customElement.spec.ts, both of which are directly related to fixing slot prop reactivity; there are no other modified files or public API changes in the provided summary. I see no evidence of unrelated or out-of-scope modifications introduced by this PR. Therefore the change appears scoped appropriately to the linked issue.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ 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/13904

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

Copy link

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 101 kB (+36 B) 38.5 kB (+13 B) 34.6 kB (+13 B)
vue.global.prod.js 159 kB (+36 B) 58.6 kB (+18 B) 52.1 kB (+14 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 46.6 kB 18.2 kB 16.7 kB
createApp 54.6 kB 21.3 kB 19.4 kB
createSSRApp 58.9 kB 23 kB 21 kB
defineCustomElement 59.6 kB 22.8 kB 20.9 kB
overall 68.8 kB 26.4 kB 24.2 kB

Copy link

pkg-pr-new bot commented Sep 19, 2025

Open in StackBlitz

@vue/compiler-core

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

@vue/compiler-dom

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

@vue/compiler-sfc

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

@vue/compiler-ssr

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

@vue/reactivity

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

@vue/runtime-core

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

@vue/runtime-dom

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

@vue/server-renderer

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

@vue/shared

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

vue

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

@vue/compat

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

commit: d15554c

@edison1105 edison1105 added ready to merge The PR is ready to be merged. 🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. scope: custom elements labels Sep 19, 2025
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 (3)
packages/runtime-dom/__tests__/customElement.spec.ts (3)

658-666: Prefer DOM-based assertions over full innerHTML string.

Asserting the slot element’s class directly is less brittle and lets us extend checks (e.g., attribute removal) without relying on serialization details.

Apply this diff in this test:

-  expect(e.shadowRoot!.innerHTML).toBe(
-    `<div><slot class="foo"></slot></div>`,
-  )
+  const slotEl = e.shadowRoot!.querySelector('slot')!
+  expect(slotEl.getAttribute('class')).toBe('foo')

   foo.value = 'bar'
   await nextTick()
-  expect(e.shadowRoot!.innerHTML).toBe(
-    `<div><slot class="bar"></slot></div>`,
-  )
+  expect(slotEl.getAttribute('class')).toBe('bar')
+  // optional: ensure DOM node was patched, not replaced
+  expect(e.shadowRoot!.querySelector('slot')).toBe(slotEl)

642-667: Add coverage for attribute removal (original bug scenario).

Issue #13904 mentions the class should be removed when toggled off. Let’s assert removal when the prop becomes undefined.

Add a new test (or extend this one) like:

test('remove slot attribute when prop becomes undefined', async () => {
  const foo = ref<string | undefined>('foo')
  const E = defineCustomElement({
    render() {
      return h('div', null, renderSlot(this.$slots, 'default', { class: foo.value }))
    }
  })
  customElements.define('my-el-slot-props-removal', E)
  container.innerHTML = `<my-el-slot-props-removal><span>hi</span></my-el-slot-props-removal>`
  const e = container.childNodes[0] as VueElement
  const slotEl = e.shadowRoot!.querySelector('slot')!
  expect(slotEl.getAttribute('class')).toBe('foo')

  foo.value = undefined
  await nextTick()
  expect(slotEl.hasAttribute('class')).toBe(false)
})

642-667: Consider a named-slot case to ensure props + name co-exist.

Validates BAIL path when props are present on non-default slots and that the name attribute is preserved.

You can add:

test('render named slot with props', async () => {
  const foo = ref('a')
  const E = defineCustomElement({
    render() {
      return h('div', null, renderSlot(this.$slots, 'named', { class: foo.value }))
    }
  })
  customElements.define('my-el-named-slot-props', E)
  container.innerHTML = `<my-el-named-slot-props><span slot="named">hi</span></my-el-named-slot-props>`
  const e = container.childNodes[0] as VueElement
  const slotEl = e.shadowRoot!.querySelector('slot[name="named"]')!
  expect(slotEl.getAttribute('class')).toBe('a')

  foo.value = 'b'
  await nextTick()
  expect(slotEl.getAttribute('class')).toBe('b')
})
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f595ec and d15554c.

📒 Files selected for processing (1)
  • packages/runtime-dom/__tests__/customElement.spec.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/runtime-dom/__tests__/customElement.spec.ts (2)
packages/runtime-dom/src/apiCustomElement.ts (2)
  • defineCustomElement (167-185)
  • VueElement (202-689)
packages/runtime-core/src/helpers/renderSlot.ts (1)
  • renderSlot (25-101)
🪛 ast-grep (0.38.6)
packages/runtime-dom/__tests__/customElement.spec.ts

[warning] 655-655: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: container.innerHTML = <my-el-slot-props><span>hi</span></my-el-slot-props>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 655-655: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: container.innerHTML = <my-el-slot-props><span>hi</span></my-el-slot-props>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)

⏰ 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
🔇 Additional comments (1)
packages/runtime-dom/__tests__/customElement.spec.ts (1)

642-667: Good targeted test for the CE slot-props regression.

This reliably exercises reactive updates on native attributes for custom elements and guards the PatchFlags.BAIL change.

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: custom elements
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[custom-element] native slots attributes not responding to reactivity
1 participant