Vue: Extra Blank Spaces Between Elements In Rendered Template

Created on 23 Feb 2018  ·  9Comments  ·  Source: vuejs/vue

Version

2.5.13

Reproduction link

https://jsfiddle.net/aoxo2rod/8/

Steps to reproduce

Run the JSFiddle

What is expected?

The background should be green, styled by ul:empty, as the children of ul are all conditionally not rendered: empty v-for and v-if that evaluate to false.

What is actually happening?

The background is red, ul:empty is not used because there is a blank space inside the ul tags, which CSS doesn't consider to be :empty.


The multiple children elements are important, a single li element with v-if="false" will produce the correct result. It appears Vue is putting a blank space between the elements, even if they are conditionally not rendered (appearing as <!----> instead). It's worth noting that if you manually remove the extra spaces using developer tools, everything looks as expected, <!----> does not affect :empty.

Furthermore, this only reproduces if there is a newline between the elements. Modifying the provided JSFiddle to only have the two li with v-if="false" will produce the correct result if there's no newline between them. So Vue seems to be turning the newline between the elements into a single space, which is causing the issue.

Most helpful comment

If you want to have no spaces you can, however, remove any white spaces in the template:

That's not really a practical solution, though. Putting everything on one line turns it into an unreadable mess for anything more than the most trivial of templates.

Not closing because I'm not sure about the new line being replaced by an empty space but to me, this behaviour is more consistent than automatically removing whitespaces as using newlines in regular HTML would yield the same result

But Vue does collapse everything inside the ul into a single line, it's already chomping the newlines, it's just turning them into spaces instead. If it wants to preserve newlines, then sure, that would be consistent with how vanilla HTML acts, but it doesn't currently do that, and as such the results are unintuitive.

@Justineo, using preserveWhitespace does seem to get the intended result, but as you said, it's only available via vue-loader at the moment. Is there any way to activate this for a single node? It's a bit icky to have to turn it on for an entire project if you only want that behavior for a single template, or even just a single part of a single template. It'd be great if there was a directive like v-preserve-whitespace="false" that could be used on an individual node. I briefly looked at writing my own directive to get that end result, but the only reasonable hook to use seemed to be componentUpdated and changing the element's innerHTML at that point led to inconsistent results, it's probably not a good place to touch the HTML.

All 9 comments

Currently there is an option called preserveWhitespace for Vue Loader so you can choose to strip “empty” text nodes when using single file components. It's actually an option for vue-template-compiler but AFAIK it's not exposed as a runtime API (yet).

The same thing happens with regular html and empty lines, the css does not apply:

<ul>
</ul>

If you want to have no spaces you can, however, remove any white spaces in the template:

<ul><li v-for="item in items">
  <span>{{ item }}</span>
</li><li v-if="false"></li><li v-if="false"></li></ul>

Not closing because I'm not sure about the new line being replaced by an empty space but to me, this behaviour is more consistent than automatically removing whitespaces as using newlines in regular HTML would yield the same result

If you want to have no spaces you can, however, remove any white spaces in the template:

That's not really a practical solution, though. Putting everything on one line turns it into an unreadable mess for anything more than the most trivial of templates.

Not closing because I'm not sure about the new line being replaced by an empty space but to me, this behaviour is more consistent than automatically removing whitespaces as using newlines in regular HTML would yield the same result

But Vue does collapse everything inside the ul into a single line, it's already chomping the newlines, it's just turning them into spaces instead. If it wants to preserve newlines, then sure, that would be consistent with how vanilla HTML acts, but it doesn't currently do that, and as such the results are unintuitive.

@Justineo, using preserveWhitespace does seem to get the intended result, but as you said, it's only available via vue-loader at the moment. Is there any way to activate this for a single node? It's a bit icky to have to turn it on for an entire project if you only want that behavior for a single template, or even just a single part of a single template. It'd be great if there was a directive like v-preserve-whitespace="false" that could be used on an individual node. I briefly looked at writing my own directive to get that end result, but the only reasonable hook to use seemed to be componentUpdated and changing the element's innerHTML at that point led to inconsistent results, it's probably not a good place to touch the HTML.

Is there any way to activate this for a single node?

Like Evan said here (except that this might not be solved by CSS), I think it's better that we keep it consistent across the whole project. Custom directives may work if you just remove whitespace-only text nodes instead of setting innerHTML directly.

Like Evan said here (except that this might not be solved by CSS), I think it's better that we keep it consistent across the whole project.

That doesn't really work for reusable components, a library can't (or at least shouldn't) force users to change project-wide settings. It's also not applicable if they're not using vue-loader, at the moment.

Custom directives may work if you just remove whitespace-only text nodes instead of setting innerHTML directly.

You're right, that seemed to make it work nicely. Example directive below for anyone who comes across this looking for a solution:

function trimEmptyTextNodes (el) {
  for (let node of el.childNodes) {
    if (node.nodeType === Node.TEXT_NODE && node.data.trim() === '') {
      node.remove()
    }
  }
}

Vue.directive('trim-whitespace', {
  inserted: trimEmptyTextNodes,
  componentUpdated: trimEmptyTextNodes
})

So the open question remains regarding Vue chomping the newlines into a single space. Is there a rationale for this behavior? If it chomped the newlines into nothing, rather than a single space, this would work out of the box.

Collapsing new lines and white spaces is consistent with the behavior of HTML.

HTML renders any consecutive whitespaces (including newlines and everything) as a single space. So chomping consecutive whitespaces into a single space results in the exact same render output while reducing the payload. This is intended and is in fact consistent with what you'd expect when styling plain HTML with CSS.

@yyx990803 Unless you use <pre> tag (or, in general, white-space: pre-wrap;).

I agree that a way to disable (on demand like a directive or something else) newline to space transform may be realy usefull. The template code would still look nice and indented but would fix a major issue when working with inline element in css.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

aviggngyv picture aviggngyv  ·  3Comments

hiendv picture hiendv  ·  3Comments

bfis picture bfis  ·  3Comments

fergaldoyle picture fergaldoyle  ·  3Comments

loki0609 picture loki0609  ·  3Comments