-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Better whitespace handling #189
Comments
I personally would like to avoid all inter-element whitespace as much as possible. Maybe do something similar to pug and jsx (at least I think thats what it does), Pug even has special syntax for inline tags: https://pugjs.org/language/interpolation.html So for example: <ul>
<li>
Some <strong>inline</strong> tags.
</li>
</ul> would generate the following html: <ul><li>Some <strong>inline</strong> tags.</li></ul> |
That actually happens already – whitespace inside either end of an element is removed: https://svelte.technology/repl/?gist=f4657520185203c009a9116568ac5ba2 The problems start when you have siblings separated by newlines: <ul>
<li>
Some
<strong>inline</strong>
<em>newline separated</em>
tags.
</li>
</ul> In that case you can't remove whitespace before the |
I thought about using a whitelist of inline elements as well, but I don’t think that’s a good idea. I rather think we should do the same as react/jsx and pug do here and remove all whitespace when elements are on a newline. |
Trouble is pug and JSX are different languages, so they can get away with different semantics. Svelte templates are just HTML (plus tags, blocks and directives), so from the user's perspective if markup behaves differently then it's a bug. Maybe it could be an opt-in thing? If we do collapse whitespace to a single character, then when we get round to implementing helpers it won't be quite so bad: // instead of this...
var text4 = document.createTextNode( '\n\t\t\t' );
// ...this
var text4 = whitespace(); |
Its really a question what the user expects I guess… More often things are breaking because of unintended whitespace. And depending on the users background, they might be used to template system that have a more aggressive whitespace handling. |
I'm quite sure it is a line break. |
What I'm currently doing is sending my templates through https://github.com/kangax/html-minifier before sending them through svelte - HTMLMinifier.minify(html, {
caseSensitive: true,
collapseWhitespace: true,
conservativeCollapse: true,
ignoreCustomFragments: [ /{{[^]*?}}/ ],
keepClosingSlash: true,
}) which seems to work well enough. |
I think if you look at the HTML 5 spec they have an interesting way of handling this. You can have different insertion modes where character tokens are handled differently. See "in-select" insertion mode for example: https://www.w3.org/TR/html5/syntax.html#parsing-main-inselect |
Removing whitespace between tags is important for layouts using I agree that preserving the HTML expectations is important, so I think that whitespace removal should be explicit. As @Ryuno-Ki mentioned, the Twig {% spaceless %}
<div>
<strong>foo</strong>
</div>
{% endspaceless %}
{# output will be <div><strong>foo</strong></div> #} |
Could we do something like export default {
keepWhitespace: true
} then check that at compile time? |
don't create whitespace nodes inside elements like <select>
It seems we can no longer naively pass Svelte components through Does anyone have a workaround for that? |
My comment here could probably be extended to use |
First time user, obvious question: why not just copy React? @Rich-Harris referring back to a comment you made in 2016:
I'm skeptical -- maybe because other frameworks have changed my expectations. I'm new to Svelte (and love it): I think of it as a transpiler. I don't feel I'm writing HTML: I feel I'm writing JavaScript with an abundance of When I first used React (very recently), I had no preconceptions about whitespace. I learned to my delight that JSX is not HTML. And now I've just learned Svelte, and I see myself writing huge statements on one line. Here's code from my second-ever Svelte component, which happens to use the dreaded <pre ref:pre class={{wrapText ? 'wrap' : ''}}>{{#each plainAndHighlightedPairs as [ plain, highlighted ], i}}{{plain}}{{#if highlighted}}<em class={{highlightIndex === i ? 'current' : ''}}>{{highlighted}}</em>{{/if}}{{/each}}</pre> Svelte is getting in the way. Can that line be sensible? If Svelte followed React's whitespace rules, it would be legible: <pre ref:pre class={{wrapText ? 'wrap' : ''}}>
{{#each plainAndHighlightedPairs as [ plain, highlighted ], i}}
{{plain}}
{{#if highlighted}}
<em class={{highlightIndex === i ? 'current' : ''}}>{{highlighted}}</em>
{{/if}}
{{/each}}
</pre> Svelte's choice is between "pretend to be HTML" and "pretend to be easy." They're mutually exclusive. People choose Svelte because HTML is too complicated: it could be easier here. And yes, changing whitespace rules is backwards-incompatible; but hey, React did it. This is an awfully long comment, so I'll finish with a rhetorical flourish: can any reader here describe HTML's whitespace rules from memory? |
Just rediscovered this issue via #1236. @adamhooper you make a strong case; the <p>
click
{' '}
<a href={`/foo/${this.props.bar}`}>here</a>
{' '}
for more information
</p> The Svelte/HTML equivalent: <p>
click
<a href='/foo/{{bar}}'>here</a>
for more information
</p> Having to insert Not suggesting that we have to comply strictly with HTML, just that we need to weigh up the consequences of deviating from it. |
I agree with @adamhooper on this and would love to see Svelte's HTML output with collapsed whitespace as React does it. Things like inline-block menus, tab headers, and breadcrumbs become a big mess of code when you want the whitespace eliminated. For example:
|
I type it out all on one line and then let Prettier do the |
Angular has similar feature of removing whitespaces. Key takeaways:
I'd like to note that as a longtime user of Angular I never had problems stemming from this particular feature. It's enabled by default and optimises generated code a lot. Behaviour becomes natural like |
Spaces between elements seems to be the exception rather than the rule, so you don't see too much
They're very intuitive. I've been caught out many times by extraneous spaces between and within HTML elements. I've had no such surprises with JSX. |
It looks like this one is still heavily under discussion. I can echo @tomblachut 's experience from Angular. What Angular does "just works" for almost all cases, producing efficient output by default. "Efficient by default" is an important trade-off against what @Rich-Harris mentioned earlier, behaving as much as possible like ordinary HTML by default. Every once in a while it causes trouble - rarely enough that switching to "preserve whitespace" for a subtree or entire component fixes it easily with only a tiny impact on an overall applications compiled output size. |
I saw this but I'm asking if this is the same for my problem: #2745 |
Can "Whitespace at the start and end of a tag is removed completely" be clarified? Or is there a reference somewhere? It isn't clear to me if it is talking about all around the start and end of a tag, or just inside of the tag. For example: <foo>
<bar></bar>
<bar> </bar>
</foo> Is this equivalent to A, where all whitespace around tags is removed. <foo><bar></bar><bar></bar></foo> Or B where only the whitespace inside of start and end tags are removed. <foo><bar></bar> <bar></bar></foo> |
Thank for the reply @dummdidumm ! I understand the problem now. Not sure about the solution other than using In my specific use case, I'm just using a pipe But it's good knowledge to have for the future when I come across this again! Thanks again 😄 |
@kevincox it would be B. Whitespace between tag siblings is trimmed to one whitespace. |
@dummdidumm there: It is not practical to always collapse whitespace between nodes to one white space. This will not resolve the main problem. If you do it this way, you can as well do nothing, because the browser anyway takes care of this type of whitespace folding. The relevant question is how to handle whitespace between elements. Consider my post above, how Fresh does it:
Example for 1
becomes
Example for 2
becomes
If the user wants to insert explicit whitespace, he uses
White space between text and elements is handled the same way. Remove all of it when a newline is contained, otherwise collapse it to one whitespace. |
To be honest, everyone makes up their own set of slightly different arbitrary rules. We picked this specific ruleset to be as much backwards-compatible as possible with Svelte 4 while making things easier to reason about. There's no correct answer here. |
@dummdidumm Your solution is just bad. The user will not be able to format his code intuitively without having unwanted whitespaces.
Will not become
as it should. It will become
And every CSS designer will curse. |
The implementation of my solution is not difficult, you search for |
But ok, I've raised my objections, now it's up to the developers. |
Here is a minimal grammar for Peggy.
Run this e.g. on ...
... and you get ...
Of course a few things are missing, it is just to show the basic idea. |
In the case of <h1>
<span>Hello</span>
<bold>everyone</bold>
<span>outside</span>
</h1> You can |
This might be a good situation where adherence to web standards (as much as possible, at least) would be highly advisable. Particularly when dealing with i18n and where you're encountering not only various contexts (e.g. inline vs. block) and adjacencies but also directionality (e.g. left to right vs. right to left and etc). Seems complicated to reinvent. Here's what I found:
Here's what ChatGPT summarized for me (so take with grain of salt!): HTML, as defined by the W3C, has rules for whitespace handling, and these rules are generally consistent across elements. Here are some key points:
|
Your example is the much less common use case. When was the last time, where you actually wrote inline spans like a block element in different lines? You can do two things:
Further, it is easy to add an option in my above grammar to switch between your model and mine. At the top of the grammar, add:
and replace the return block in the
Now, if Now you have an option for users to set. @patricknelson The W3Cs model is made for all kind of people, like writers. Often they do not care about specific whitespace rules. But this here are developers, and they want to have full control. If we stick to W3C rules, we can as well do nothing. |
@GauBen try this:
|
We recently encountered this issue when creating a recursive component for rendering a rich text format, where unwanted spaces where added between rendered sibling elements. We have a temporary workaround using a preprocessor which inlines the elements by simply removing tabs and new-lines in the template code but it feels rather blunt in it's current form. I would love to be able to control this when rendering snippets in Svelte 5. Something similar to this (the options parameter could also support other compiler options): I also think it's reasonable to keep the default as it is today to adhere the web standards. |
Wanted to also jump in after a fair amount of google struggle led me here, because text formatting using span tags broken into new lines was creating unintended spaces.
...gives me First Name, instead of the expected FirstName, and in this case, with many Tailwind classes on sibling span tags, the only option here is to run them all on the same line. I agree with the previously mentioned unordered list example:
...being another demonstration of this behavior causing CSS headaches that I would not expect to have. I don't personally have much of a preference for what the default behavior is, as long as there is an available parameter to allow choosing the intended new line space behavior. |
I'm going to close this as the thread has become a bit too chaotic to actually convert into actionable items, and because Svelte 5's whitespace handling is much more consistent and logical than Svelte 4's. Obviously there's no silver bullet, given that browser whitespace handling takes account of the CSSOM, but I think we've landed on the least worst set of options (and cases like #189 (comment) are handled). For dealing with unwanted spaces between list items (the If there are specific bugs or things that need further discussion, feel free to open new issues. |
@Rich-Harris, I can not help having the impression that you did not understand my issue. I guess a lot of people here did not, or had other problems. So let me point it out a last time: you preserve whitespace between nodes, which is good, but you should only preserve inline whitespace between nodes. Whitespace between nodes, which also contains one or more newlines, should be completely deleted. That, because usually we format HTML in a way that inserts newlines between (often block element) siblings, where we don't want to have whitespace in between in the output. But your way will insert the whitespace (which is good if it is just inline whitespace!), and it will cause problems which can usually be solved with |
I understood perfectly well, I just don't agree with this statement. There are two options here: we can make Svelte behave as much like HTML as possible, or we can make Svelte behave differently to HTML in subtle ways and force people to do horrible stuff like this: <p>
If this text is long enough,{' '}
<strong>this element</strong>{' '}
ends up on a different line
</p> I see that a lot in React apps, and it's unforgivably ugly.
|
What about giving an option though? |
@Rich-Harris In my opinion that is an unusual code formatting. I would never do that in HTML or markdown, unless forced to. But I do not read as much code as you do, maybe people indeed format their code like that a lot. Still, I like the syntax with
You may want to help people with a more elegant syntax, but it backfires for some. Indeed, I would appreciate an option: |
Another comment: It may be that these |
I'm about 9 months into learning Svelte and I only realised this week that this topic is the root cause of several "strange layout" issues I've worked around over that time. It is definitely a "trap for young players", and it isn't entirely obvious to me now how to backtrack from seeing the whitespace in the browser to the offending Svelte component. Using 'flex' works in some places but in others has side-effects. A notable one is when using styles (in my case tailwind) to position absolute items to a relative parent using styles, e.g. positioning a dropdown popup. With 'flex' enabled it simply doesn't work. It's possible I could find another workaround, but if that is just a spiral towards styling everything that is dynamic in Javascript I don't think that's the future we all want. Perhaps there is more knowledge on how to create components that don't emit this kind of whitespace or how to troubleshoot it when it happens. Failing that, it'd be great to manage it by exception somehow (<svelte:whitespace emit="false"> or some sort of component/page directive?). No need to label it 'react' either just describe what it does canonically. For now, my preferred hack is to put these tailwind utilities on the offending container when I don't want flex side effects: text-[0px] leading-[0px]. Update: The above hack has it's own side effects (you forget you use it and wonder why text disappears in places 🤦). Better to use whitespace-nowrap (tailwind) on containers by default and reverse that when you need to. |
While fixing #178 it occurred to me that there are some nodes, like
<datalist>
, where it doesn't make sense to have text node children. The browser agrees:Not sure what the first text node is doing there in the second case. Anyway, Svelte should be smart enough not to create text nodes inside elements where they're meaningless.
Additionally, we could collapse excess whitespace between nodes that aren't inside a
<pre>
element, since these are equivalent:(That's not strictly true, since it's dependent on CSS, so there probably needs to be a
preserveWhitespace: true
option if we did that.)The text was updated successfully, but these errors were encountered: