Skip to content

fix(custom-element): ensure child component styles are injected in correct order before parent styles#13374

Merged
edison1105 merged 11 commits intomainfrom
edison/fix/13029
Mar 9, 2026
Merged

fix(custom-element): ensure child component styles are injected in correct order before parent styles#13374
edison1105 merged 11 commits intomainfrom
edison/fix/13029

Conversation

@edison1105
Copy link
Copy Markdown
Member

@edison1105 edison1105 commented May 23, 2025

close #13029

Summary by CodeRabbit

  • New Features

    • Custom element style handling now preserves child-before-parent ordering by tracking per-component insertion anchors and honoring parent context when injecting styles.
  • Bug Fixes

    • Improved HMR and style-update reliability to prevent stale or misordered styles in nested, wrapped, and deeply nested components; maintains correct injection order across reloads.
  • Tests

    • Added extensive tests covering nested, wrapper, multi-level and HMR style ordering scenarios.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 23, 2025

Important

Review skipped

This PR was authored by the user configured for CodeRabbit reviews. CodeRabbit does not review PRs authored by this user. It's recommended to use a dedicated user account to post CodeRabbit review feedback.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 800d7b5c-de56-4d91-86da-8989db17662a

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

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Custom element style injection was made parent-aware and now tracks per-component style anchors so child component styles are inserted before parent styles in shadow DOM; renderer and custom element APIs updated and tests added for nested/wrapper/HMR scenarios.

Changes

Cohort / File(s) Summary
Runtime core declaration
packages/runtime-core/src/component.ts
ComponentCustomElementInterface._injectChildStyle signature extended to accept an optional parent?: ConcreteComponent.
Renderer callsite
packages/runtime-core/src/renderer.ts
setupRenderEffect now forwards parent type when calling CE style injection: _injectChildStyle(type, instance.parent ? instance.parent.type : undefined).
Custom element style management
packages/runtime-dom/src/apiCustomElement.ts
Added private _styleAnchors: WeakMap<ConcreteComponent, HTMLStyleElement>; _applyStyles and _injectChildStyle accept optional parentComp; insertion logic updated to compute insertion anchor via _getStyleAnchor(parentComp) or _getRootStyleInsertionAnchor(root); anchors tracked and cleaned up (HMR/removals).
Tests
packages/runtime-dom/__tests__/customElement.spec.ts
New tests covering child-before-parent style injection ordering, nested/wrapper configurations, HMR interactions, shadowRoot=false and nonce scenarios.

Sequence Diagram(s)

sequenceDiagram
    participant Renderer as Renderer (setupRenderEffect)
    participant Instance as Component Instance
    participant VueElement as VueElement (custom element)
    participant Shadow as ShadowRoot

    Renderer->>Instance: mount -> determine type & parentType
    Renderer->>VueElement: _injectChildStyle(type, parentType?)
    VueElement->>VueElement: _applyStyles(styles, owner=type, parentComp=parentType?)
    VueElement->>Shadow: _getStyleAnchor(parentComp) or _getRootStyleInsertionAnchor(root)
    Shadow->>Shadow: insert style before parent's anchor or at root insertion point
    VueElement->>VueElement: store/update _styleAnchors for owner
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • skirtles-code
  • johnsoncodehk

Poem

"I’m a rabbit of styles, quick and spry,
I tuck child rules first before parents try.
Anchors I plant in shadow’s light,
HMR hops in, swaps old for bright.
Hop — colors settle, everything’s right!" 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.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 title accurately describes the main change: fixing style injection order for child components in custom elements to ensure they're injected before parent styles.
Linked Issues check ✅ Passed The code changes directly address issue #13029 by modifying style injection order to ensure child styles are applied before parent styles in custom element mode.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing style injection order in custom elements; no unrelated modifications are present.

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


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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 23, 2025

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 105 kB (+570 B) 39.6 kB (+183 B) 35.6 kB (+159 B)
vue.global.prod.js 163 kB (+570 B) 59.6 kB (+173 B) 53.1 kB (+145 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 48.2 kB (+30 B) 18.7 kB (+11 B) 17.2 kB (+11 B)
createApp 56.3 kB (+30 B) 21.8 kB (+11 B) 19.9 kB (+15 B)
createSSRApp 60.6 kB (+30 B) 23.5 kB (+12 B) 21.5 kB (+9 B)
defineCustomElement 62.5 kB (+570 B) 23.7 kB (+179 B) 21.6 kB (+152 B)
overall 70.7 kB (+30 B) 27.1 kB (+10 B) 24.7 kB (+54 B)

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 23, 2025

Open in StackBlitz

@vue/compiler-core

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

@vue/compiler-dom

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

@vue/compiler-sfc

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

@vue/compiler-ssr

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

@vue/reactivity

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

@vue/runtime-core

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

@vue/runtime-dom

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

@vue/server-renderer

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

@vue/shared

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

vue

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

@vue/compat

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

commit: cf3761b

@edison1105 edison1105 force-pushed the edison/fix/13029 branch 5 times, most recently from 468260b to 8b6b732 Compare May 23, 2025 07:38
@edison1105 edison1105 force-pushed the edison/fix/13029 branch 2 times, most recently from 5b13a9e to 84a0439 Compare May 23, 2025 13:11
@edison1105 edison1105 changed the title fix(custom-element): inject child style before parent component's style fix(custom-element): ensure child component styles are injected in correct order before parent styles May 23, 2025
@vuejs vuejs deleted a comment from edison1105 May 23, 2025
@edison1105 edison1105 marked this pull request as ready for review May 23, 2025 13:33
@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 May 23, 2025
Copy link
Copy Markdown

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/runtime-dom/src/apiCustomElement.ts`:
- Around line 602-615: The root-style insertion unconditionally uses
root.prepend(s) which moves reloaded parent styles ahead of already-inserted
child styles; instead, when parentComp is falsy (owner/root styles) insert each
style node before the first non-STYLE child (or before the first existing style
anchor for this._def) so HMR re-inserts parent styles in-place rather than
moving them before children; locate the root branch where root.prepend(s) is
used, use this._styleAnchors.get(this._def) / scan root.childNodes for the first
node whose nodeName !== 'STYLE' (or first style anchor) as the insertion point,
and continue setting this._styleAnchors.set(this._def, s) as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6a34b117-31ae-422f-92b1-b8e003b35724

📥 Commits

Reviewing files that changed from the base of the PR and between 41379c4 and 16bad68.

📒 Files selected for processing (2)
  • packages/runtime-dom/__tests__/customElement.spec.ts
  • packages/runtime-dom/src/apiCustomElement.ts

Comment thread packages/runtime-dom/src/apiCustomElement.ts Outdated
@edison1105
Copy link
Copy Markdown
Member Author

/ecosystem-ci run

Copy link
Copy Markdown

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/runtime-dom/src/apiCustomElement.ts (1)

610-640: ⚠️ Potential issue | 🟡 Minor

Minor type inconsistency between _applyStyles and _injectChildStyle.

The parentComp parameter is typed as ConcreteComponent & CustomElementOptions here, but _injectChildStyle passes just ConcreteComponent. Since parentComp is only used as a WeakMap key lookup (line 626), the intersection with CustomElementOptions is unnecessary.

Suggested fix
  private _applyStyles(
    styles: string[] | undefined,
    owner?: ConcreteComponent,
-   parentComp?: ConcreteComponent & CustomElementOptions,
+   parentComp?: ConcreteComponent,
  ) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-dom/src/apiCustomElement.ts` around lines 610 - 640, The
type of the parentComp parameter on _applyStyles is unnecessarily specific;
change its declaration from "parentComp?: ConcreteComponent &
CustomElementOptions" to just "parentComp?: ConcreteComponent" (or
ConcreteComponent | undefined) so it matches how _injectChildStyle calls it and
is correct for use as a WeakMap key; update the function signature for
_applyStyles accordingly and ensure any callers still pass the same values (no
other logic changes required).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/runtime-dom/src/apiCustomElement.ts`:
- Around line 610-640: The type of the parentComp parameter on _applyStyles is
unnecessarily specific; change its declaration from "parentComp?:
ConcreteComponent & CustomElementOptions" to just "parentComp?:
ConcreteComponent" (or ConcreteComponent | undefined) so it matches how
_injectChildStyle calls it and is correct for use as a WeakMap key; update the
function signature for _applyStyles accordingly and ensure any callers still
pass the same values (no other logic changes required).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5e86e7d0-4bb5-4b31-9871-87bcd57e5781

📥 Commits

Reviewing files that changed from the base of the PR and between bf25d0a and 41552d5.

📒 Files selected for processing (4)
  • packages/runtime-core/src/component.ts
  • packages/runtime-core/src/renderer.ts
  • packages/runtime-dom/__tests__/customElement.spec.ts
  • packages/runtime-dom/src/apiCustomElement.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/runtime-core/src/renderer.ts

@vuejs vuejs deleted a comment from edison1105 Mar 9, 2026
@vue-bot
Copy link
Copy Markdown
Contributor

vue-bot commented Mar 9, 2026

📝 Ran ecosystem CI: Open

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

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
packages/runtime-dom/src/apiCustomElement.ts (1)

625-639: Style insertion logic is correct.

The anchor-based insertion properly ensures child styles are placed before parent styles in the DOM, matching the expected CSS cascade behavior for scoped style overrides.

One minor observation: the this._styleAnchors.set(this._def, s) on line 636 executes on every loop iteration when !parentComp, but only the final iteration's value matters. This is functionally correct but slightly wasteful.

,

Optional: Move anchor assignment outside the loop
     for (let i = styles.length - 1; i >= 0; i--) {
       const s = document.createElement('style')
       if (nonce) s.setAttribute('nonce', nonce)
       s.textContent = styles[i]

       root.insertBefore(s, last || insertionAnchor)
-      if (!parentComp) {
-        this._styleAnchors.set(this._def, s)
-      }
       last = s
-      if (owner && i === 0) this._styleAnchors.set(owner, s)
+      if (i === 0) {
+        if (!parentComp) this._styleAnchors.set(this._def, s)
+        if (owner) this._styleAnchors.set(owner, s)
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-dom/src/apiCustomElement.ts` around lines 625 - 639, The
loop sets this._styleAnchors.set(this._def, s) on every iteration when
!parentComp, which is redundant; move the anchor assignment out of the for-loop
so that after the loop completes (and last holds the final style element) you
call this._styleAnchors.set(this._def, last) when !parentComp, keeping the
existing behavior for owner (this._styleAnchors.set(owner, s) when i === 0)
unchanged; update the code around the for-loop in apiCustomElement.ts
(references: insertionAnchor, last, styles, nonce, this._styleAnchors,
this._def, owner) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/runtime-dom/src/apiCustomElement.ts`:
- Around line 625-639: The loop sets this._styleAnchors.set(this._def, s) on
every iteration when !parentComp, which is redundant; move the anchor assignment
out of the for-loop so that after the loop completes (and last holds the final
style element) you call this._styleAnchors.set(this._def, last) when
!parentComp, keeping the existing behavior for owner
(this._styleAnchors.set(owner, s) when i === 0) unchanged; update the code
around the for-loop in apiCustomElement.ts (references: insertionAnchor, last,
styles, nonce, this._styleAnchors, this._def, owner) accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0aa9c11a-8cc1-44b6-9bff-ac4d4c79be49

📥 Commits

Reviewing files that changed from the base of the PR and between 41552d5 and ba1b246.

📒 Files selected for processing (1)
  • packages/runtime-dom/src/apiCustomElement.ts

@edison1105 edison1105 merged commit 1398bf8 into main Mar 9, 2026
15 of 16 checks passed
@edison1105 edison1105 deleted the edison/fix/13029 branch March 9, 2026 08:12
edison1105 added a commit that referenced this pull request Mar 16, 2026
edison1105 added a commit that referenced this pull request Mar 16, 2026
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 Mode - Styles applied in wrong order

2 participants