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
Make css-declaration-sorter safer #580
Conversation
any estimates, when this get merged? 👼 |
/cc @Siilwyn what is status? |
Still in progress, mostly doing local stuff. Will try to push more often. Though I'm also thinking of implementing it at the module instead of here. |
thanks for your work on this. the "new" sort logic blocks us from upgrading to postcss v7 because the working version of |
@andyjansson ready for review! :) |
'sorts both short- and longhand properties', | ||
processCSS, | ||
'h1{animation-name:a;animation:b}', | ||
'h1{animation:b;animation-name:a}', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is wrong. The code needs to respect precedence. You can't just sort based on the relationship between the properties (what's a longhand or a shorthand of what), but you need to respect the order in which they are declared.
@@ -49,6 +51,9 @@ const defaultOpts = { | |||
normalizeCharset: { | |||
add: false, | |||
}, | |||
cssDeclarationSorter: { | |||
customOrder: path.join(__dirname, 'safe-order.json'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The declarations cannot be sorted based on a sort order defined in a json
file. They need to respect the order of declaration in the CSS, else the precedence will be different and the CSS will render differently in the browser.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that I'm leaving out the declarations that need to be kept in their original order, so this will work. Do you have an example that will fail?
processCSS, | ||
'h1{animation-name:a;z-index:0;animation:b;color:0;}', | ||
'h1{animation-name:a;animation:b;color:0;z-index:0}' | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a lot more tests here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you provide a bit more descriptive feedback? Like what should be tested & what kind of tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the module itself has tests too: https://github.com/Siilwyn/css-declaration-sorter/blob/master/tests/test.js
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a range of tests to ensure that the module functions as expected, and to detect any future regressions. I expect tests covering cases where declarations should be rearranged, cases where they shouldn't, and cases with declarations of mixed precedence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first test covers "cases where they shouldn't", the second has mixed precedence and declarations that should be rearranged right? I can split up the 2nd test to clarify the difference, that might be better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry but I still don't get what you're after. Both tests cover the cases you mentioned earlier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm after more tests so we can have confidence in the feature. We need tests to capture its function and any edge cases that might lurk about. Two test cases aren't nearly enough for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Siilwyn when can you add more tests?
\cc @evilebottnawi
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep need more tests, example with border/padding/margin, also all tests from related issues and prefixes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once all other comments and questions are resolved I'll add tests for the border/padding/margin. 👍
"all tests from related issues and prefixes", there is a test for the prefixing, any related issues I need to be aware of?
@@ -18,13 +18,13 @@ th{font-weight:700;text-align:left} | |||
td,th{line-height:inherit;padding:.25rem 1rem} | |||
th{vertical-align:bottom} | |||
td{vertical-align:top} | |||
body{font-size:100%;line-height:1.5} | |||
body{line-height:1.5;font-size:100%} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some strange reordering of declarations in all integration tests. Shouldn't these be grouped alphabetically?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, since font-size
could conflict with font
it is not being moved. (Check my comment on the issue too: "For now keep all properties that have a shorthand in the same order")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean? There' s no font
declaration here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't look at a selector by selector basis, the way this PR makes it safer is by not sorting properties that could conflict at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really don't know what you mean, and there's no tests to infer meaning from either. Can you expand on what you're saying?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah sorry I missed this reply.
I'll add an extra test, what I mean is that any property that has a conflicting property will not be sorted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what you mean by conflicting. Conflicting with what? The functionality, as it stands, is completely opaque and non-intuitive. The tests aren't communicating its behaviour, and that means we can't have any confidence in its result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clarified this behaviour with a test: move properties that are not in safe-order.json to the start and sort remaining
. Properties that are not 'safe', in other words: properties that have a shorthand/longhand are moved to the start. What I meant with conflicting is for a value that can be changed by multiple properties such as the font-size
in this example because it can also be changed with font
. These properties 'conflict', maybe it's a bad word to describe this, I'm not a native speaker. :)
thanks @Siilwyn for all your work on this ❤️ just one tiny input about sorting (not sure if you already covered this case), but in the current version of cssnano, all vendor prefixed properties are pushed to the beginning which breaks some code of us: working code:
broken after sorting:
would be very nice, if you could make sure that this works as well ❤️ and again - thank you very much!!! |
Thank you for your reply @zanettin your reply is much appreciated. :) This is covered by this PR, sharp! I'll add a test to clarify this. |
@@ -9,6 +9,9 @@ const defaultOpts = { | |||
autoprefixer: { | |||
add: false, | |||
}, | |||
cssDeclarationSorter: { | |||
customOrder: null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explain your reasoning behind having a different sorting order to the advanced preset and the consequences it'll have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The advanced order can make unsafe changes to save more bytes, so it orders everything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not really what the advanced preset is about, though. See the website on 'advanced transforms' for more info.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the advanced transform page: "Certain optimizations are not suitable for all use cases; unlike those that we bundle by default, advanced transforms all carry a certain amount of risk.". This exactly falls into this category. When using legacy code with double properties that override each other sorting everything comes with a certain amount of risk.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The risk with the advanced preset comes from mixing sources. When working within the same stylesheet, it should be safe. The advanced
preset is not "bug mode".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does not only come from mixing sources, to be specific quoting the same page:
This transform changes CSS semantically; it may remove styles based on certain characteristics of the CSS, or it might update values to make the CSS smaller overall. Depending on exactly what the transform does, this may be undesirable because it might change the appearance of your website in certain use cases.
Is what this optimization falls under, I think reducing the size here further is worth the tiny risk of breaking legacy code which other optimizations in this preset also do.
Thoughts @ai @evilebottnawi ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Certain optimizations are not suitable for all use cases [...] we've documented the assumptions that it must make about your CSS in order for the transform to be safe. [...] these transforms fall into either or both of categories.
Assumes concatenation
[...] assumes that the CSS passed through cssnano is all that is needed for a website to run [...]
may be problematic if the styles you are writing in some way interact with third party styles, or you are using multiple stylesheets instead of concatenating them
Changes semantics
may remove styles based on certain characteristics of the CSS, or it might update values to make the CSS smaller overall. [...] this may be undesirable [...]
A good example is autoprefixer
What you are going is against the spirit of the preset. It has clear qualifiers on what it takes to use the advanced preset safely. It's not supposed to be a roll of the dice on whether it works or not. Please respect that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not a "roll of the dice" though, it is not safe when the CSS contains conflicting properties in one selector. I don't see how this is different from e.g. autoprefixer that removes outdated properties that is unsafe for CSS that contains outdated properties.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is not safe when the CSS contains conflicting properties in one selector.
Yeah, we can't have that. It's a super-common thing in CSS to have properties that affect the same space. Bringing this into the advanced preset would destroy any use case it has, rendering it useless.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So being not super-common it would be useful... Is it super-common? For one, this issue took a while to surface which I think partly indicates this is uncommon. Secondly as far as I know CSS selectors normally do not contain properties that override each other, it's usually a mistake, e.g:
animation: bla;
animation-name: bla;
The animation
property also contains the name so instead of putting animation-name
after it normally I would just change the name in the animation
shorthand value.
I understand your concern though, this at least should be clarified in the docs. It comes down to if the benefit outweighs the risk, if it is common as you said then I see how it is not worth it. For me seeing CSS like this is pretty new, however I've also only been a front-end dev for 5 years and mainly worked with newish codebases.
@@ -76,7 +76,7 @@ test( | |||
h2, h1 { | |||
font-weight: 400; | |||
}`, | |||
`h1,h2{color:red;font-weight:400}` | |||
`h1,h2{font-weight:400;color:red}` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why it is happens?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clarified this behaviour with a test: move properties that are not in safe-order.json to the start and sort remaining
in bfe1257. Properties that are not 'safe', in other words properties that have a shorthand/longhand are moved to the start.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main idea of this plugin is to maximize the alphabetical order to better gzip
, here i find many strange cases where we broke alphabetical order without reasons. Maybe we need use better logic here.
|
||
```js | ||
{ | ||
customOrder: null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't think what we need this option, no one will constantly keep their list, it's too time consuming and takes a lot of time and effort, let's remove it from here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean? It's there so that the advanced preset does full sorting resulting in more optimization. Note that this is generated documentation too, no way to hide it.
@@ -4,7 +4,7 @@ | |||
"main": "dist/index.js", | |||
"description": "Safe defaults for cssnano which require minimal configuration.", | |||
"scripts": { | |||
"prepublish": "cross-env BABEL_ENV=publish babel src --out-dir dist --ignore /__tests__/" | |||
"prepublish": "cross-env BABEL_ENV=publish babel src --out-dir dist --ignore /__tests__/ --copy-files" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why it is here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So that the custom order will get copied, babel normally ignores JSON files.
processCSS, | ||
'h1{animation-name:a;z-index:0;animation:b;color:0;}', | ||
'h1{animation-name:a;animation:b;color:0;z-index:0}' | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep need more tests, example with border/padding/margin, also all tests from related issues and prefixes
@evilebottnawi please read my last replies on the related issue, this PR uses simple ordering for now. As you see in some cases it can't order alphabetically, this is intended. |
Unfortunately, it doesn’t work :(. We have an issue with this sorter right now in our current project (temporarily fixed with To be honest, this issue is really a high priority and I recommend to fix it ASAP. |
Maybe we should disable the rule, release a fix and then continue to discuss? CSS minifier must be safe. People should trust it. So it is very important to fix safety problems ASAP. |
@ai merging #599 would disable css-declaration-sorter. @evilebottnawi @ai should we proceed with this course of action? |
@andyjansson sorry, what do you mean about course of actions? (I am very bad in language ileomes) |
@ai whether we should proceed with disabling css-declaration-sorter |
I think we should disable this plugin at least for default preset |
@ai do you still have this issue with the changes made in this PR? If not, we can merge it with safe sorting in default and advanced, and continue the discussion about the advanced preset after since you want to fix it ASAP. 🐎 @andyjansson thoughts? |
@Siilwyn yeap, accepting this PR is an option too @andyjansson please, could we release it faster. It is a few months until the issue got reported. It is critical. We need to fix it ASAP. Disable plugin or accept this PR. |
Hey it seems it got disabled looking at |
Read the changes we've requested. You've marked the threads as "fixed" although they haven't been. |
@andyjansson it is unclear for me when conversations are resolved, I have unresolved and replied to all of them. Could you go through them to resolve or give feedback? That would be great. Edit: I've removed |
So @evilebottnawi if I understand it correctly this PR will not be merged because it is not optimal? Even though the current state of disabling the plugin is even worse in terms of optimizations. Or does it depend on Siilwyn/css-declaration-sorter#64? We can discuss it there too if you want. |
☝️ would love a reply. Will close and delete this PR if there is no chance of getting merged. |
@Siilwyn sorry, don't have time right now |
Don't change the order of short- & longhand properties with the exception of
margin
,padding
&border
since these are handled by themergeLonghand
optimisation.Closes #535