Skip to content

Conversation

@skirtles-code
Copy link
Contributor

@skirtles-code skirtles-code commented Jul 1, 2025

This was intended as a refactoring, reusing logic for detecting comments and whitespace, but as it makes small modifications to how templates are compiled (especially for non-breaking spaces) I've marked it as a fix.

This PR only targets the compiler. I think similar changes should be made to the runtime, but those can be added later.

Background

While reviewing other PRs, I noticed a common pattern. There are a lot of places where we need a single child/root node, but we typically need to skip comments and whitespace text to find the node we want. Each place where this logic occurs implements it separately and I wanted to introduce some helper functions to reduce the duplication. Hopefully that will help to reduce inconsistencies, bugs and missed edge cases too.

There are a few common sources of problems:

  1. Not considering comments at all.
  2. Assuming comments are removed in production builds. This used to be true, but is now configurable.
  3. Not accounting for whitespace with whitespace: 'preserve'. This is closely related to handling comments as, in several cases, comments will also introduce whitespace text nodes that would otherwise be stripped.

We also have an inconsistent definition of 'whitespace'.

Both trim() and /\s/ will treat many different characters as whitespace, including non-breaking spaces. For our purposes, this isn't really what we want, but we get away with it in practice.

The template parser includes a function isWhitespace that only considers 5 characters to be whitespace: space, tab, newline, carriage return and form feed. From the perspective of collapsing and discarding whitespace nodes, this is the appropriate definition. It accounts for the characters that are typically used to format HTML code and that shouldn't be treated as meaningful content.

A key part of this 'refactoring' is that trim() is no longer used to identify whitespace text, with isWhitespace being preferred instead. The result is that some whitespace characters, most notably non-breaking space, will no longer be ignored in some contexts where they were being ignored previously. Non-breaking spaces will be treated just like any other normal text character.

Examples

The examples below illustrate cases where something has changed in how this PR handles non-breaking spaces.

I don't think anyone will be intentionally using non-breaking spaces in the ways that are affected. If anyone is using them in these edge cases then it seems much more likely they've been included by accident and nobody noticed because the compiler ignored them. I believe these cases are rare, and it should be trivial to fix for anyone impacted.

The examples below use   for a non-breaking space, but it could also be included directly as a character, it doesn't need to be encoded as an entity.

v-if

This code would previously compile successfully, discarding the non-breaking space. With the changes in this PR it will throw an error, just as it would for non-whitespace text:

<div v-if="foo"></div>
&nbsp;
<div v-else></div>

v-slot

<MyComponent>
  &nbsp;
  <template #header>
    ...
  </template>
</MyComponent>

Previously, the &nbsp; would have been discarded. After this PR it will be treated as content of the default slot.

<Transition>

Similarly, this will now fail to compile because the non-breaking space will be considered a child node:

<Transition>
  &nbsp;
  <component :is="foo" />
</Transition>

Previously, the non-breaking space would have just been discarded.

Testing

The existing tests should all pass.

The tests I've added cover a variety of cases, many of which were already working previously. The tests that mention non-breaking spaces are typically testing an actual change.

Some parts of this are a bit tricky to test effectively, as they require specific combinations of compiler options and transforms. In particular, testing cases involving TEXT_CALL nodes, which are only created by transformText.

Related PRs

There are various open PRs that attempt to fix problems related to the handling of comments and whitespace nodes. In particular, these two may benefit from using the utility functions added in this PR:

Summary by CodeRabbit

  • New Features

    • Added exported utilities to detect whitespace-only text and comment nodes in templates.
  • Bug Fixes

    • Improved handling of whitespace and non‑breaking spaces and comments in slots, conditionals, and transition components to ensure correct parsing and warnings.
  • Tests

    • Added tests covering whitespace, non‑breaking spaces, comments, and adjacency rules for conditional branches.
  • Refactor

    • Unified whitespace/comment detection logic across compiler modules.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jul 1, 2025

Walkthrough

Adds utilities to detect whitespace/comment nodes, updates parser and several transforms (v-if, v-slot, transition) to use them, and extends tests to cover whitespace and comment handling in text, slots, v-if adjacency, and transition children.

Changes

Cohort / File(s) Change Summary
Utilities
packages/compiler-core/src/utils.ts
Added isAllWhitespace, isWhitespaceText, and isCommentOrWhitespace exports and imported isWhitespace from tokenizer.
Parser
packages/compiler-core/src/parser.ts
Removed local isAllWhitespace implementation and now imports the function from ./utils.
v-if transform
packages/compiler-core/src/transforms/vIf.ts
Replaced separate COMMENT/whitespace sibling checks with isCommentOrWhitespace in processIf; preserved dev-only comment re-add behavior.
v-slot transform
packages/compiler-core/src/transforms/vSlot.ts
Replaced local helper with isCommentOrWhitespace and isWhitespaceText; changed implicit default slot whitespace detection to use isWhitespaceText.
Transition transform
packages/compiler-dom/src/transforms/Transition.ts
Updated hasMultipleChildren filtering to use isCommentOrWhitespace to exclude comments and whitespace-only text nodes.
compiler-core tests
packages/compiler-core/__tests__/transforms/transformText.spec.ts, .../vIf.spec.ts, .../vSlot.spec.ts
Added "whitespace text" test; added non-adjacent v-else / v-else-if tests (including NBSP cases); wired optional transformText into slot test helper and added NBSP/slot tests. Adjusted error assertion indices where necessary.
compiler-dom tests
packages/compiler-dom/__tests__/transforms/Transition.spec.ts
Added tests for NBSP inside <transition> (warns about multiple children) and for comments/preserved whitespace being ignored.

Sequence Diagram(s)

sequenceDiagram
    participant Parser
    participant Utils
    participant Transforms
    participant Tests

    Parser->>Utils: call isAllWhitespace(text)
    note right of Utils: provides centralized whitespace test
    Transforms->>Utils: call isCommentOrWhitespace(node)
    Transforms->>Utils: call isWhitespaceText(node)
    note right of Transforms: vIf / vSlot / Transition use utilities to\nfilter/identify whitespace and comments
    Tests->>Parser: parse templates with whitespace/comments
    Tests->>Transforms: run transforms (with transformText enabled in some tests)
    note over Tests,Transforms: assertions updated/added to validate behavior
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Potential hotspots to inspect:

  • packages/compiler-core/src/utils.ts — correctness and edge-cases of isAllWhitespace and recursive isWhitespaceText.
  • packages/compiler-core/src/transforms/vIf.ts and vSlot.ts — ensure adjacency logic unchanged semantically when switching to isCommentOrWhitespace.
  • Test diffs in vIf.spec.ts — error indices adjusted; verify error locations and test stability.

Possibly related PRs

Suggested labels

:hammer: p3-minor-bug

Suggested reviewers

  • sxzz
  • baiwusanyu-c

Poem

🐇 I nibble spaces, sniff each line,

I hunt the gaps where tokens hide.
With tiny tools I mark and grep,
So comments and blank hops are kept.
Hop, test, transform — tidy and spry!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(compiler): share logic for comments and whitespace' accurately and concisely summarizes the main refactoring effort—centralizing whitespace and comment detection logic across the compiler.
✨ 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.

@github-actions
Copy link

github-actions bot commented Jul 1, 2025

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 103 kB 38.9 kB 35.1 kB
vue.global.prod.js 161 kB (-35 B) 58.8 kB (+1 B) 52.4 kB (+12 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 46.9 kB 18.3 kB 16.8 kB
createApp 55 kB 21.4 kB 19.6 kB
createSSRApp 59.3 kB 23.1 kB 21.1 kB
defineCustomElement 60.6 kB 23.1 kB 21.1 kB
overall 69.3 kB 26.6 kB 24.3 kB

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jul 1, 2025

Open in StackBlitz

@vue/compiler-core

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

@vue/compiler-dom

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

@vue/compiler-sfc

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

@vue/compiler-ssr

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

@vue/reactivity

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

@vue/runtime-core

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

@vue/runtime-dom

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

@vue/server-renderer

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

@vue/shared

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

vue

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

@vue/compat

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

commit: b6d83dd

@edison1105 edison1105 added ready to merge The PR is ready to be merged. scope: compiler 🍰 p2-nice-to-have Priority 2: this is not breaking anything but nice to have it addressed. labels Jul 2, 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 (1)
packages/compiler-core/__tests__/transforms/vSlot.spec.ts (1)

31-51: transformText toggle in parseWithSlots is sound; optional cleanup of options spread

Conditionally appending transformText to nodeTransforms via an extra transformText?: boolean flag lets tests opt into the full pipeline without affecting existing cases. If you want to keep this flag purely test-local, you could optionally destructure it out before passing ...options into parse/transform so it never reaches compiler APIs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b8f1f2 and b6d83dd.

📒 Files selected for processing (6)
  • packages/compiler-core/__tests__/transforms/vIf.spec.ts (3 hunks)
  • packages/compiler-core/__tests__/transforms/vSlot.spec.ts (5 hunks)
  • packages/compiler-core/src/parser.ts (1 hunks)
  • packages/compiler-core/src/transforms/vIf.ts (2 hunks)
  • packages/compiler-core/src/transforms/vSlot.ts (3 hunks)
  • packages/compiler-core/src/utils.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/compiler-core/src/parser.ts
  • packages/compiler-core/src/transforms/vIf.ts
  • packages/compiler-core/src/transforms/vSlot.ts
  • packages/compiler-core/src/utils.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/compiler-core/__tests__/transforms/vSlot.spec.ts (2)
packages/compiler-core/src/transforms/transformText.ts (1)
  • transformText (18-119)
packages/compiler-core/src/ast.ts (2)
  • ObjectExpression (374-377)
  • SimpleExpressionNode (225-247)
🔇 Additional comments (5)
packages/compiler-core/__tests__/transforms/vIf.spec.ts (2)

269-293: Expanded v-else adjacency coverage (including NBSP) is accurate

The added cases for <div v-if="bar"/>foo<div v-else/> and the NBSP variant correctly assert X_V_ELSE_NO_ADJACENT_IF and use the right mock.calls[3] / mock.calls[4] indices, aligning tests with the new definition that non‑breaking spaces are not ignorable whitespace.


333-371: v-else-if non-adjacent and NBSP tests align with intended semantics

The mirrored v-else-if cases (with text and NBSP between branches) and the updated expectation at mock.calls[5] correctly exercise the non-adjacent branch error path and ensure the “else-if after else” case still targets the final branch’s loc.

packages/compiler-core/__tests__/transforms/vSlot.spec.ts (3)

315-347: NBSP before a named slot correctly forms an implicit default slot

The new “named slots w/ implicit default slot containing non-breaking space” test accurately asserts that a lone NBSP outside the named <template #one> becomes default slot content (TEXT with content: ' \u00a0 '), while the named slot still returns only foo. This matches the intent that NBSP is treated as real text rather than ignorable whitespace.


1053-1072: Preserve-whitespace + NBSP default slot behavior is well covered

With whitespace: 'preserve', asserting that a template containing &nbsp; plus a named #header template produces a default slot entry (and matching snapshot) cleanly captures the new behavior that NBSP-only content should not be discarded as whitespace-only.


1088-1104: transformText + comments between named v-if / v-else slots are now exercised

The “named slot with v-if + v-else and comments” test, using transformText: true and whitespace: 'preserve', is a good addition to ensure the combination of comments, preserved whitespace, and transformText doesn’t break v-if/v-else branching for slots; the snapshot-based assertion is appropriate here.

@edison1105
Copy link
Member

/ecosystem-ci run

@vuejs vuejs deleted a comment from edison1105 Nov 24, 2025
@vue-bot
Copy link
Contributor

vue-bot commented Nov 24, 2025

📝 Ran ecosystem CI: Open

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

@edison1105 edison1105 merged commit 2214f7a into vuejs:main Nov 24, 2025
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🍰 p2-nice-to-have Priority 2: this is not breaking anything but nice to have it addressed. ready to merge The PR is ready to be merged. scope: compiler

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants