FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

[normalize-whitespace]: removal of whitespace in at-rule and params by anikethsaha · Pull Request #921 · cssnano/cssnano · GitHub

[normalize-whitespace]: removal of whitespace in at-rule and params - #921

Open
anikethsaha wants to merge 5 commits into
cssnano:mainfrom
anikethsaha:whitepace-enhancement-428
Open

anikethsaha wants to merge 5 commits into
cssnano:mainfrom
anikethsaha:whitepace-enhancement-428

Conversation

anikethsaha commented Jun 28, 2020
edited
Loading

Copy link
Copy Markdown
Member

ref #428

Some of the transforms

@media and (...) => @media and(...)
@media screen and () => @media screen and()
@media ( max-width:   50px ) => @media (max-width:50px)

codecov-commenter commented Jun 28, 2020
edited by codecov Bot
Loading

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.28%. Comparing base (2cd35b7) to head (c1e2049).
⚠️ Report is 1029 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #921      +/-   ##
==========================================
+ Coverage   97.26%   97.28%   +0.01%     
==========================================
  Files         119      119              
  Lines        3478     3493      +15     
  Branches     1048     1051       +3     
==========================================
+ Hits         3383     3398      +15     
  Misses         87       87              
  Partials        8        8              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

anikethsaha changed the title [normalize-whitespace]: removal of whitespace in at-rule and paarams [normalize-whitespace]: removal of whitespace in at-rule and params Jun 28, 2020

Copy link
Copy Markdown
Member

@media () => @media() -> need test on IE
@media and () => @media and() -> invalid
@media ( max-width: 50px ) => @media (max-width:50px) -> good idea, but we should uses parser or very very very strict regex + many tests
@support () , @support () => @support(),@support() -> i think it is invalid syntax, no examples in spec, but maybe I wrong

Copy link
Copy Markdown
Member Author

@media () => @media() -> need test on IE

ok

@media and () => @media and() -> invalid

changed the PR description,

@support () , @support () => @support(),@support()

yes invalid, I removed it from the PR description, I couldn't find any example in the spec, but postcss should throw parsing error right if this is an invalid syntax ?

@media ( max-width: 50px ) => @media (max-width:50px) -> good idea, but we should uses parser or very very very strict regex + many tests

I think the current regex will only convert multiple spaces to a single one. I think it is safe, it won't do any changes to the newlines, neither to tabs

Copy link
Copy Markdown
Member

yes invalid, I removed it from the PR description, I couldn't find any example in the spec, but postcss should throw parsing error right if this is an invalid syntax ?

If it is invalid syntax - yes, but I am not usre we need read spec

Copy link
Copy Markdown
Member

I think the current regex will only convert multiple spaces to a single one. I think it is safe, it won't do any changes to the newlines, neither to tabs

I am afraid about var() function, it can be part of @supports

Copy link
Copy Markdown
Member

We can use postcss-value-parser for this, because syntax is same

anikethsaha commented Jun 30, 2020
edited
Loading

Copy link
Copy Markdown
Member Author

If it is invalid syntax - yes, but I am not usre we need read spec

the spec says,

@supports <supports-condition> {
  <stylesheet>
}

so I suppose there can be nested at-rules, but no atules in <supports-condition>

I am afraid about var() function, it can be part of @supports

var does supports spaces inside of the (..) so even removing them should not be unsafe. I will add some test to show that.

We can use postcss-value-parser for this, because syntax is same

does it supports parsing at rules ?

yea it does, though I will check it if it is better to use this or the regex.

Copy link
Copy Markdown
Member Author

@evilebottnawi I think there is some issue if we go with AST.
In AST, it parse the spaces as a node, so for example

@media screen and ( max-width: 500px) {
}

this is the tree

    ValueParser {
      nodes: [
        { type: 'word', sourceIndex: 0, value: 'screen' },
        { type: 'space', sourceIndex: 6, value: ' ' },
        { type: 'word', sourceIndex: 7, value: 'and' },
        { type: 'space', sourceIndex: 10, value: ' ' },
        {
          type: 'function',
          sourceIndex: 11,
          value: '',
          before: ' ',
          after: '',
          nodes: [
            { type: 'word', sourceIndex: 13, value: 'max-width' },
            { type: 'div', sourceIndex: 22, value: ':', before: '', after: '' },
            { type: 'word', sourceIndex: 23, value: '500px' }
         ]
        }
      ]
    }

it would be unsafe to remove all the space.

we can remove the whitespace inside of the function i.e inside of the parens (...) but not it would be kind of hacky for and (
we need to check spaces before ( so while traversing the AST, if we encounter ( we can't go back to the previous node.

alexander-akait left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Can you test this compression on IE8/IE10/IE11, need to investigate

Copy link
Copy Markdown
Member

I am afraid @media () => @media() is not valid

Copy link
Copy Markdown
Member Author

Can you test this compression on IE8/IE10/IE11, need to investigate

I have added test for ie11. should we still support ie10, 8?

anikethsaha commented Jul 7, 2020
edited
Loading

Copy link
Copy Markdown
Member Author

I am afraid @media () => @media() is not valid

opz, might have forgot to add and I changed it 👍

Copy link
Copy Markdown
Member

I mean we should test it manually #921 (comment) 😄

Copy link
Copy Markdown
Member Author

I dont have ie in my machine.

I think we can add a check using browserlist + caniuse-lite. It would be a breaking change then.

Copy link
Copy Markdown
Member

@anikethsaha I think it is time to learn virtual box 😄 I'm not sure if this does not work, maybe everything is fine, just need test

Copy link
Copy Markdown
Member Author

I have some bad experience with Virtual box earlier, I do have it installed and my config for that is messed up.

I will try to look for some online VM to test them.

trinitronx commented Aug 22, 2025
edited
Loading

Copy link
Copy Markdown

@support () , @support () => @support(),@support() -> I think it is invalid syntax, no examples in spec, but maybe I wron

At least for @media queries, the spaces appear to be necessary for Chrome and Chromium-based browsers (e.g. Edge, etc...) to parse and apply them correctly.

For example, the current cssnano (7.1.0) transforms this:

@media screen and (prefers-contrast:more) and (-ms-high-contrast:active) and (prefers-color-scheme: light),
       screen and (prefers-contrast:more) and (-ms-high-contrast:black-on-white) { 
/* some rules */
}

To this without spaces between ) + and, and between , + screen:

@media screen and (prefers-contrast:more)and (-ms-high-contrast:active)and (prefers-color-scheme: light),screen and (prefers-contrast:more)and (-ms-high-contrast:black-on-white), { 
/* some rules */
}

When using Chrome's "Rendering" -> "Emulate CSS media feature prefers-contrast", the media queries don't match after cssnano processing.

Turning off the normalizeWhitespace and minifyParams optimizations fixes the issue, and the @media queries match properly again:

module.exports = {
	plugins: [
// ... possibly other plugins ...
        ...process.env.NODE_ENV === 'production'
            ? [cssNano({preset: [
                "default",
                {
                  "normalizeWhitespace": false,
                  "minifyParams": false
                }]})
              ]
            : []
	]
}

Copy link
Copy Markdown
Member

@trinitronx Sounds like a bug in chrome...

trinitronx commented Aug 22, 2025
edited
Loading

Copy link
Copy Markdown

@trinitronx Sounds like a bug in chrome...

🤔 Hmm... It's not clear whether it is a bug or not...

In the W3C recommendation spec for "Media Queries Level 3", there is "Example 21" under "Malformed media query" section:

EXAMPLE 21

The following is an malformed media query because having no space between ‘and’ and the expression is not allowed. (That is reserved for the functional notation syntax.)

@media all and(color) { … }

Reference: W3C Recommendation: Media Queries Level 3 - § 3.1. Error Handling

It makes no mention of the preceding space before an "and" whether it's required or not. So that's inconclusive.

Then, in the W3C spec for "Media Queries Level 4" they have a more formal Backus-Naur definition of the media query syntax, which does mention how a comma-separated <media-query-list> is parsed, as well as defining the <media-and> = and <media-in-parens> operator. There is also a similar section about the space after the and and before an opening paren (:

Note

Whitespace is required between a not, and, or or keyword and the following ( character, because without it that would instead parse as a . This is not made explicitly invalid because it’s already covered by the above grammar. It’s fine to have whitespace between a ) and a following keyword, however.

Reference: W3C Recommendation: Media Queries Level 4 - § 3. Syntax

That still makes no mention of omitting the whitespace between a closing paren ) and a boolean operator and, or, or not. Yet, since the BNF definition for <media-and> has spaces on both sides of the and ("<media-and> = and <media-in-parens>")... perhaps the Chromium developers interpreted this as being required, or added it into the parsing in such a way as to effectively make it required?

Just speculation: It might be parsing something as a not all or false, or unknown... It's unclear which, because there is no useful output in the developer console saying what is going on. We also have -ms-high-contrast which is vendor-specific to older versions of Microsoft Edge (now deprecated in favor of forced-colors). So that portion in Chrome should evaluate to not all, triggering the grammar mismatch logic or perhaps the <general-enclosed> future grammar logic branch. It could also be discarding the media query list portion up to the next comma , given the following:

A media query that does not match the grammar in the previous section must be replaced by not all during parsing.

Note

Note: Note that a grammar mismatch does not wipe out an entire media query list, just the problematic media query. The parsing behavior defined above automatically recovers at the next top-level comma.

Reference: W3C Recommendation: Media Queries Level 4 - § 3.2 Error Handling

Since the broken example has ),screen and ... it could be that the ,screen portion is breaking it because a typical @media screen requires the space in front of screen <media-type> (as in: <media-type> = <ident>)

If it's a Chromium bug, then it's broken out in the wild and places limitations on what minification transforms are "safe" effectively due to the bug. If it's not a bug, then it's not "safe" to do the transform in any case.

Whatever the case, current Chrome versions (tested on 137.0.7151.119 and above) seem to choke on the minified @media queries produced by cssnano's current 'default' preset. So, in the practical sense, those particular transforms shouldn't be considered "safe".

trinitronx commented Aug 22, 2025
edited
Loading

Copy link
Copy Markdown

Ok, so I have found something that works while digging more into the ,screen spacing issue for Chrome.

It turns out that it works if I prefix the nested source CSS @media query rules with only (e.g. only screen and (...) ).

For example, these nested @media queries:

@media (prefers-contrast: more) {
	@media (forced-colors: none) and (prefers-color-scheme: dark),
		   only screen and (-ms-high-contrast: active) and (prefers-color-scheme: dark),
		   only screen and (-ms-high-contrast: white-on-black)  {
/* ... some rules ... */
	}
}

Transforms into:

/* 
   Note that the PostCSS plugin prefers-color-scheme-query, is run here before CSSNano.
   The corresponding JS polyfill has been added to the page also.
   The previous example had it turned off and it still did not parse correctly in Google Chrome.
*/

@media (prefers-contrast:more) and (forced-colors:none) and (color: 48842621),only screen and (prefers-contrast:more) and (-ms-high-contrast:active) and (color:48842621),only screen and (prefers-contrast:more) and (-ms-high-contrast:white-on-black) {
/* ... some rules ... */
}

@media (prefers-contrast:more) and (forced-colors:none) and (prefers-color-scheme: dark),only screen and (prefers-contrast:more) and (-ms-high-contrast:active) and (prefers-color-scheme:dark),only screen and (prefers-contrast:more) and (-ms-high-contrast:white-on-black) {
/* ... same rules as above block duplicated ... */
/* ... for browsers that support prefers-color-scheme ... */
}

Note that now we have (...),only screen and (...) instead of just (...),screen and (...). That seems to parse OK in Chrome, and the styles are applied when prefers-contrast emulation is set to "more" and prefers-color-scheme is set to "dark".

Therefore, it seems that before we had a syntax construct ,screen and (-ms-high-contrast:active) where the Microsoft Edge-specific vendor media query would probably evaluate to an unknown value, or perhaps as ,screen and not all,. Yet when prefixed with ,screen and it seemed to break the entire query, even for previous ,-separated queries that should have been processed as if a logical OR were used.

Yet, now with the prefixed ,only screen and , it probably evaluates to a ,true and (unknown). The W3C "Media Queries Level 4" spec says this:

If the result of any of the above productions is used in any context that expects a two-valued boolean, “unknown” must be converted to “false”.

Note

This means that, for example, when a media query is used in a @media rule, if it resolves to “unknown” it’s treated as “false” and fails to match.

Media Queries use a three-value logic where terms can be “true”, “false”, or “unknown”. Specifically, it uses the Kleene 3-valued logic. In this logic, “unknown” means “either true or false, but we’re not sure which yet”.
In general, an unknown value showing up in a formula will cause the formula to be unknown as well, as substituting “true” for the unknown will give the formula a different result than substituting “false”. The only way to eliminate an unknown value is to use it in a formula that will give the same result whether the unknown is replaced with a true or false value. This occurs when you have “false AND unknown” (evaluates to false regardless) and “true OR unknown” (evaluates to true regardless).

This logic was adopted because <general-enclosed> needs to be assigned a truth value. In standard boolean logic, the only reasonable value is “false”, but this means that not unknown(function) is true, which can be confusing and unwanted. Kleene’s 3-valued logic ensures that unknown things will prevent a media query from matching, unless their value is irrelevant to the final result.

Reference: W3C Recommendation: Media Queries Level 4 - § 3.1. Evaluating Media Queries

So, if we were dealing with an unknown here... then the transform of ,screen and (unknown) may have been parsed incorrectly or differently than ,only screen and (unknown).

It would seem as if the only prefix is not optional when omitting the space.

trinitronx added a commit to trinitronx/cv that referenced this pull request Aug 22, 2025
Note: This implements Chrome syntax findings from my testing exploration

Reference:

  - cssnano/cssnano#921 (comment)

yisibl commented Nov 4, 2025

Copy link
Copy Markdown
Contributor

@trinitronx You should submit a bug report to Chrome.

This branch has not been deployed

No deployments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants


Back | FazBrowse Home | New Git URL