Skip to content

Conversation

alex-snezhko
Copy link
Contributor

@alex-snezhko alex-snezhko commented Oct 8, 2025

Fix SSR on textarea elements with a v-text directive value
Playground

Related to #12311 but slightly different as this bug would be triggered for textarea even without custom user directives.

Summary by CodeRabbit

  • Bug Fixes

    • Corrects server-side rendering to avoid merging props when content-overriding directives (v-text/v-html) are present, ensuring elements render expected inner text/HTML and textareas preserve correct inner content.
  • Tests

    • Added SSR tests covering v-text and v-html on standard elements and textareas to validate rendering and prevent regressions.

Copy link

coderabbitai bot commented Oct 8, 2025

Walkthrough

Adds a helper to detect content override directives (v-text/v-html) in the SSR element transform and updates branching to account for overrides when merging props and deriving inner content (notably for textarea). Also adds SSR tests for v-html on span and v-text/v-html on textarea.

Changes

Cohort / File(s) Summary
SSR compiler transform updates
packages/compiler-ssr/src/transforms/ssrTransformElement.ts
Added hasContentOverrideDirective(node); updated conditions to skip or allow merged props based on v-text/v-html presence and textarea interpolation; replaced explicit v-text checks with the generic override detector; preserved runtime tag handling comments.
Server renderer directive tests
packages/server-renderer/__tests__/ssrDirectives.spec.ts
Added tests for v-html on a span and v-text/v-html on textarea (duplicated blocks inserted) to validate SSR inner content behavior for override directives.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as SSR Compiler Transform
  participant N as Element Node
  participant M as MergeProps/Emit
  participant R as SSR Output

  C->>N: inspect tag, props, children
  C->>N: call hasContentOverrideDirective(node)?
  alt node is textarea
    alt override present OR no interpolation
      C-->>M: skip merging children into value
      C->>R: set textContent/innerHTML from directive
    else interpolation present and no override
      C->>M: merge props and textarea value
      M->>R: emit with interpolated value
    end
  else non-textarea
    alt override present and no children
      C-->>M: skip children merge
      C->>R: set innerHTML/textContent from directive
    else normal flow
      C->>M: merge props and children
      M->>R: emit with children
    end
  end
  note right of C: Decision now uses hasContentOverrideDirective(node)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

scope: ssr, :hammer: p3-minor-bug

Poem

I twitch my whiskers at directives bright,
v-text and v-html guiding SSR night.
In textarea tunnels I check and abide,
Overrides steer what children provide.
A rabbit nods—merge with gentle care. 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly identifies the core change of fixing SSR output for textarea elements using a v-text directive, which aligns with the main bug addressed in the PR and concisely communicates the primary developer intent.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 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.

Copy link

github-actions bot commented Oct 8, 2025

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 102 kB 38.6 kB 34.8 kB
vue.global.prod.js 160 kB 58.7 kB 52.2 kB

Usages

Name Size Gzip Brotli
createApp (CAPI only) 46.7 kB 18.3 kB 16.7 kB
createApp 54.7 kB 21.3 kB 19.5 kB
createSSRApp 58.9 kB 23 kB 21 kB
defineCustomElement 60 kB 23 kB 21 kB
overall 68.8 kB 26.5 kB 24.2 kB

Copy link

pkg-pr-new bot commented Oct 8, 2025

Open in StackBlitz

@vue/compiler-core

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

@vue/compiler-dom

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

@vue/compiler-sfc

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

@vue/compiler-ssr

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

@vue/reactivity

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

@vue/runtime-core

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

@vue/runtime-dom

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

@vue/server-renderer

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

@vue/shared

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

vue

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

@vue/compat

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

commit: 1bab336

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

🧹 Nitpick comments (1)
packages/compiler-ssr/src/transforms/ssrTransformElement.ts (1)

456-458: LGTM! Clean helper function.

The helper function is clear and concise. It correctly identifies whether an element has a content override directive (v-text or v-html).

Optional micro-optimization: The function calls findDir twice. If performance becomes a concern, you could reduce this to a single call:

function hasContentOverrideDirective(node: PlainElementNode): boolean {
  return !!node.props.some(
    p =>
      p.type === NodeTypes.DIRECTIVE &&
      (p.name === 'text' || p.name === 'html'),
  )
}

However, given that the props array is typically small, the current implementation is fine and arguably more readable by leveraging the existing findDir utility.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c16f8a9 and f7fd89f.

📒 Files selected for processing (2)
  • packages/compiler-ssr/src/transforms/ssrTransformElement.ts (3 hunks)
  • packages/server-renderer/__tests__/ssrDirectives.spec.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/compiler-ssr/src/transforms/ssrTransformElement.ts (2)
packages/compiler-core/src/ast.ts (1)
  • PlainElementNode (140-149)
packages/compiler-core/src/utils.ts (1)
  • findDir (282-297)
⏰ 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 (3)
packages/server-renderer/__tests__/ssrDirectives.spec.ts (1)

197-217: LGTM! Tests adequately cover the bug fix.

The tests for v-text and v-html on textarea elements correctly validate the SSR output, ensuring the content override directives work as expected.

Note: These tests should be moved to a separate describe block as noted in the earlier comment.

packages/compiler-ssr/src/transforms/ssrTransformElement.ts (2)

125-131: LGTM! Condition correctly handles content override directives.

The updated condition properly skips the merged props assignment logic when a content override directive (v-text/v-html) is present on a textarea element. This ensures that v-text/v-html directives take precedence over dynamic v-bind values, which aligns with the PR objective.

The logic flow is correct:

  • Skip special handling if hasContentOverrideDirective(node) returns true
  • OR skip if there's no existing text or the existing text is not an interpolation

184-185: LGTM! Generalized condition correctly covers both v-text and v-html.

Replacing the explicit check with hasContentOverrideDirective(node) is the right approach. This ensures that both v-text and v-html directives prevent the fallback textContent/innerHTML logic from being applied, since these directives will be handled separately later in the transform.

}

function hasContentOverrideDirective(node: PlainElementNode): boolean {
return !!findDir(node, 'text') || !!findDir(node, 'html')
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This existing line here: https://github.com/vuejs/core/pull/13975/files#diff-40b0d34e7fda1abbad1716852aa8a9db97fdbfce639710889dc62423f247a834R230 actually already handles v-html output being correct since it would result in rawChildrenMap always being updated. Because of this, the v-html lookup done here is not strictly necessary for v-html SSR output to work but including it here would at least short-circuit past the _ssrInterpolate expression node creation which is desired in this case; I also think including it here makes the logic more expressive.

@edison1105
Copy link
Member

/ecosystem-ci run

@edison1105 edison1105 added scope: ssr 🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. labels Oct 9, 2025
@vue-bot
Copy link
Contributor

vue-bot commented Oct 9, 2025

📝 Ran ecosystem CI: Open

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

@edison1105 edison1105 added the ready to merge The PR is ready to be merged. label Oct 9, 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: ssr

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants