stylus icon indicating copy to clipboard operation
stylus copied to clipboard

Stylus should not overwrite CSS built-in functions, such as min and max

Open mwaeckerlin opened this issue 4 years ago • 25 comments
trafficstars

In CSS, there are built-in functions, such as min and max. Unfortunately, Stylus has the exactly same built-in functions, That's bad and error prone.

I wanted to write a CSS max to responsively, dynamically calculate the height of an image from the current view port:

.myclass
        height: max(30vh, 50vw)

But it did not work as expected. After quite some time and debugging the generated CSS-code and then searching the Stylus docs, I found out, that Stylus has an internal function of the same name maxthat is called here.

So, stylus produced the following output:

.myclass {height:50vw;}

This is crazy stupid! Stylus cannot know the size of 1vw nor 1vhat compile time! It should at least abort with an error, instead of silently producing rubbish!

So,to solve my problem, I finally got the correct result with the following work-around:

.myclass
    height: @css{max(30vh, 50vw)}

Please rename or remove the Stylus functions max and min, and any other Stylus function that shadows a CSS built-in function!

Why not prepend all Stylus built-in functions with a prefix, such as stylus_min, __min or _min?

mwaeckerlin avatar Apr 01 '21 21:04 mwaeckerlin

That's CSS4. Stylus dates back to CSS3 or even before.

vendethiel avatar Apr 01 '21 22:04 vendethiel

@vendethiel wrote:

That's CSS4. Stylus dates back to CSS3 or even before.

I see. Interestingly, the feature is already well supported in most browsers.

In that case, what about an upgrade of Stylus with regard to CSS4?

BTW, future changes of the standard and compiler specific extensions are the reason, why in C/C++, it is best practice to prepend precompiler macros with a prefix. I suggest to do the same here.

mwaeckerlin avatar Apr 04 '21 10:04 mwaeckerlin

Good catch, and a use case I certainly hadn't thought about!

Personally though, I prefer the dry syntax philosophy of Stylus, which would somewhat be undermined by some Stylus-specific prefix to memorise for some or all BIFs. Not to mention the problems around renaming BIFs now, when thousands of existing Stylus-based projects may rely on these being BIFs rather than CSS4 functions.

One solution could be to detect whether a min() and max() function contains run-time units like vw and vh, and return a CSS literal when it does, or compile it as a Stylus BIF when it doesn't.

Another solution which could give the author more certainty about expected behaviour would be to have a more compact CSS literal syntax, maybe with backticks (`)?

.myclass
    height: `max(30vh, 50vw)`

Would either of these be useful to you?

groenroos avatar Sep 05 '21 16:09 groenroos

@groenroos

Would either of these be useful to you?

Personally I like @css{ ... } better,

because 1) also readers not yet that familiar with Stylus, understand what it means. Whilst a backtick — I might have thought that 2) it got copied literally to the generated CSS, and might have wanted to remove it. After all, "..." is copied as is (the quotes intact, get copied too, right). And 3) maybe a future CSS feature will make use of backticks in some way? Which would then collide with Stylus.

One solution could be to detect whether a min() and max() function contains run-time units like vw and vh, and return a CSS literal when it does, or compile it as a Stylus BIF when it doesn't

Seems like a more complex solution to me. Then, if min & max somehow didn't do what I hoped, I'd need to wonder if maybe maybe there was something wrong with Stylus' heuristics for computing or keeping-as-is min(..) and max(..). Might break if new CSS versions introduce new units.

@mwaeckerlin

what about an upgrade of Stylus with regard to CSS4?

I like that idea. What about Stylus 4.0 for CSS 4+, where all built-in Stylus functions and names were prefixed by $. So it'd be $max and $min. But of course (?) it must still be possible for Stylus utility libraries or application specific Stylus styles, to define one's own functions without $ :- )

stylus_min is OK too — doesn't look so nice, but is new-developer-friendly, & better than bugs  :- ) — I just noticed, after some weeks, that min(...) had been computed & removed by Stylus, and was absent in my web app.

kajmagnus avatar Dec 01 '21 08:12 kajmagnus

Thanks for the thoughtful response!

Personally I like @css{ ... } better, because 1) also readers not yet that familiar with Stylus, understand what it means.

To be clear, I'm not suggesting this would replace the @css{ ... } syntax; I'm just suggesting an additional shorthand for it, which can be used in tandem with the current syntax, according to personal preference.

My motivation is that in my Stylus projects, I rarely want to include huge blocks of plain CSS (and even if I do, I can typically just paste/import it in as-is anyway). The functionality of @css{ ... } only comes in to play when typically just one rule in one selector goes wonky, and personally, adding a whole other nested block just for that feels like a very verbose/heavy-handed approach. Hence, I'm suggesting adding an "in-line" option for these one-line usecases of literal CSS.

  1. it got copied literally to the generated CSS, and might have wanted to remove it. After all, "..." is copied as is (the quotes intact, get copied too, right).

I see that - it doesn't have to be backticks specifically. We could do height: [max(1,2)] or height: @css max(1,2) or anything, really. I just want an option to do it inside the value without a nested block.

But anyway, that's all maybe beside the point of this thread.

And 3) maybe a future CSS feature will make use of backticks in some way? Which would then collide with Stylus.

Whatever syntax we decide, this type of thing will never not be a risk with Stylus, or with any other CSS preprocessor or language superset. CSS could of course at any time decide to do anything, and Stylus would have to adapt like all others. Sass recently had to deprecate division by slash because the underlying CSS syntax had evolved.

What about Stylus 4.0 for CSS 4+, where all built-in Stylus functions and names were prefixed by $. So it'd be $max and $min.

Personally, I'm not a fan of magic punctuation. $ in and of itself doesn't mean anything, and so it's just more finger yoga to type additional special characters while providing little if any benefit to stylesheet readability or functionality.

Additionally, while the prefix character could of course be anything, $ specifically is quite frequently an (author/linter specific preference of) prefix for variables, not BIFs. It might feel confusing if Stylus was suggesting it for both.

stylus_min is OK too — doesn't look so nice, but is new-developer-friendly, & better than bugs  :- )

Sure, but I definitely don't want to be typing stylus_ over and over again. ;)

If we are to move away from overriding the CSS native BIFs, I like the approach Sass introduced with the above mentioned deprecation; e.g. math.div(). For me, typing math. (or similar "category" of BIF) is more comfortable and faster than a special character, and the resulting stylesheet will be more meaningful and easier to understand (and therefore new-developer-friendly, too!), while avoiding the clash with CSS native BIFs.

What are your thoughts?

groenroos avatar Dec 02 '21 00:12 groenroos

@kajmagnus, prepending $ sounds good to me. I'd recommend some kind of prefix for built in Stylus functions, that prevents also conflicts with future CSS standards.

mwaeckerlin avatar Dec 06 '21 16:12 mwaeckerlin

In my opinion, this issue is not a 'feature' request of 'low' importance. This is a bug. Shadowing standard CSS functions is bad.

Stylus even doesn't do it very well. Take this:

min(
    calc(var(--unit) * 12),
    100%
)

Stylus returns 100%, but it should be the other value, as unit is 24px and 100% refers to the whole page width.

This Stack Overflow Answer gives a workaround.

nilslindemann avatar Jan 13 '22 23:01 nilslindemann

Stylus should just rename these specific functions, e.g. prepend 'stylus-' to them. Projects have to adapt. Which is not difficult, just run a regular expression over the files.

nilslindemann avatar Jan 13 '22 23:01 nilslindemann

This would be very welcome improvement for css functions in general—after some experiments in the past with calc I now usually just end up writing sprintf operators like @nilslindemann (ps: "calc(...)"%() also works, believe this is the shortest form).

Haven't looked at the parser code in a long time, and I don't remember how whitespace is handled between a function identifier and its call parens, but how difficult would it be to handle css builtin function calls with a ! postfix (or maybe the other way around)? e.g.:

// one calc
calc(...)
// another calc
calc!(...)

I like the separation—it'd be pretty explicit what you mean, and (at least for now, using normal function declaration syntax,) it'd be impossible to declare a similar <ident>! style function which is a nice, if not maybe overkill, namespacing. Its also entirely isolated from other identifiers unlike a $ sigil or stylus- prefix (the former of which I use on all my variables, so not my pick here). Otherwise math probably works

disco0 avatar Apr 30 '22 09:04 disco0

@disco0

handle css builtin function calls with a ! postfix

I like that idea. I think it should work the other way around: Stylus functions should get a ! suffix. Otherwise the original problem will remain — that Stylus unexpectedly does something different, when one types max(), than what one thought.

Because, if someone writes max(...) or calc(...), they typically have in mind the CSS built-in, not the Stylus function.

Like @nilslindemann wrote:

This is a bug. Shadowing standard CSS functions is bad.

***

someFunc!(...) is the approach Rust has taken for macros, and ... apparently ! works fine in their case. someFunc!(...) looks pretty nice too, syntax highlighting wise? With the ! in a bit red / different color. Maybe there could be a Stylus color scheme recommendation that the ! should be bold / a bit different color. So it was easy to notice.

Personally I like all of $someFn(...) and someFn!(...) and stylus.someFn(...). (And letting just someFn() be the CSS built-in.)

kajmagnus avatar May 18 '22 11:05 kajmagnus

@nilslindemann

Stylus should just rename these specific functions, e.g. append 'stylus-' to them. Projects have to adapt. Which is not difficult, just run a regular expression over the files.

Yes, and all these suggestions, I mean that someFn() is always the CSS built-in, and the Stylus fn would be some of: $someFn or someFn!() or stylus.someFn(), is a Stylus major version bump, right. — So one could do these regex-replace when one had time (and not get a minor version bump major surprise :- ))

kajmagnus avatar May 18 '22 11:05 kajmagnus

@groenroos

while the prefix character could of course be anything, $ specifically is quite frequently an (author/linter specific preference of) prefix for variables

Yes, at the same time, I'm thinking about Stylus as a higher order functions language, where functions themselves are values / variables. Like in Javascript: var someFn = () => ... — in $someFn(), the some-fn is a var :- )

(But the other ideas, e.g. stylus.someFn() and someFn!() are nice too I'm thinking. And stylus_someFn() is also ok — doesn't look so nice, but if it's a lot simpler to implement, maybe it's the right choice)

kajmagnus avatar May 18 '22 11:05 kajmagnus

Hello,

Where are we ? Can we hope to see these compatibility problems that accumulate between Stylus and modern CSS corrected?

I dug around a bit in the Stylus files, min() max() is here, but generally speaking all functions should be renamed. It would not be very difficult to start all over again (but time-consuming no doubt) and Stylus would become compatible with the future rules.

Of course, such a backward compatibility-breaking change would be the subject of a new major release. However, I think that this evolution is essential: not respecting the standard CSS as valid in Stylus is a certain guarantee for the death of the project.

We could also prefix with an underscore, which would be a quick and easy change:

.myclass
  height: _max(30vh, 50vw)

Indeed, I don't think that CSS functions are ever pefixed with an underscore, however I think that would correspond well to the spirit of the Stylus syntax.

Scriptura avatar Jan 10 '23 22:01 Scriptura

We could also prefix with an underscore

Which leads to the idea to use the underscore for the CSS function (if it otherwise would be shadowed by a Stylus function). E.g., min(...) calls the Stylus function, but _min(...) calls the min(...) CSS function.

That way, existing code (probably) does not have to change.

nilslindemann avatar Jan 12 '23 19:01 nilslindemann

@nilslindemann your position is optimal from a backward compatibility perspective but I believe it is very bad in the long run. To use a Sass maxim (a maxim that Sass itself does not always respect, for example with special selectors):

Valid CSS syntax should be for the preprocessor.

Admittedly, if we opted for transparent backward compatibility, the old projects would not be broken and the long-time developers would find their way around, on the other hand this behavior would be perceived as obscure for newcomers who in fact would then quickly turn away from Stylus.

If we want to see the Stylus project survive (because it's nothing less than that) we have to make decisions looking at the long term.

Scriptura avatar Jan 12 '23 21:01 Scriptura

@Scriptura An interesting point, but backwards compatibility is also not a bad goal. I am not sure how to do it, just wanted to point out this possibility.

nilslindemann avatar Jan 13 '23 16:01 nilslindemann

I think with a proper major version bump (and maybe a deprecation notice in the old 0.x branch), we needn't worry about 100% backwards compatibility, especially for such a relatively limited use case.

I still maintain that magic punctuation (be it $ or _ or whatever) is not intuitive, because it doesn't have any implicit meaning. In this case, Stylus should organise its BIFs the same way Sass does; with "category" prefixes that have inherent, semantic meaning; i.e. min() becomes math.min(), max() becomes math.max(), etc.

groenroos avatar Jan 13 '23 18:01 groenroos

especially for such a relatively limited use case.

min() becomes math.min(), max() becomes math.max(), etc.

@groenroos yes it would be even better that way.

Scriptura avatar Jan 14 '23 05:01 Scriptura

I’m not sure we need two functions. The stylus max (and min) functions could instead look at their parameters, and see if any cannot be computed at compile time, and if so output a css max/min call.

vendethiel avatar Jan 14 '23 14:01 vendethiel

@groenroos In my opinion, the task is to find something which separates the Stylus function from the CSS function. Category Prefixes by functionality may sound like a good idea, but when CSS starts doing this too, we are back at the same problem. A prefix like stylus. or stylus- will prevent that. The lazy _ and $ prefixes probably too.

$ suggested by @kajmagnus went a bit under my radar, but it is actually also a good idea. I do not like the character very much, but it is used quite a lot for such things. For example, Tiddlywiki uses it to separate widgets from normal HTML elements (e.g. <$button/> vs <button/>).

Another idea is to use different spelling. E.g. MIN(...) or Min(...) calls the Stylus function and min(...) calls the CSS function. That would be in sync with the naming in frameworks like React, Svelte, etc., where <Foo> is a framework component and <foo> is a normal HTML element.

I also like the backtick syntax, while the ! postfix is not my favorite, as it has not to do enough with macros :-)

@vendethiel what if it can be computed at compile time, but the user still wants the CSS call?

nilslindemann avatar Jan 15 '23 10:01 nilslindemann

@vendethiel we know that Sass went this route, but I believe it is potentially confusing.

Scriptura avatar Jan 15 '23 10:01 Scriptura

It helps me to write things side by side, just looking at them.

So here is an overview of the suggestions made so far, in order of appearance per category. The first is the Stylus call, the second the CSS call. "+" denotes my preference (which is not hard science).

These work currently:

  • max(30vh, 50vw) vs @css{max(30vh, 50vw)} +
  • max(30vh, 50vw) vs "max(30vh, 50vw)" % null
  • max(30vh, 50vw) vs "max(30vh, 50vw)" % ()

These change the CSS call:

  • max(30vh, 50vw) vs `max(30vh, 50vw)` +
  • max(30vh, 50vw) vs [max(30vh, 50vw)]
  • max(30vh, 50vw) vs @css max(30vh, 50vw)
  • max(30vh, 50vw) vs max!(30vh, 50vw)
  • max(30vh, 50vw) vs _max(30vh, 50vw)

These change the Stylus call:

  • stylus_max(30vh, 50vw) vs max(30vh, 50vw)
  • __max(30vh, 50vw) vs max(30vh, 50vw)
  • _max(30vh, 50vw) vs max(30vh, 50vw)
  • $max(30vh, 50vw) vs max(30vh, 50vw) +
  • math.max(30vh, 50vw) vs max(30vh, 50vw)
  • stylus-max(30vh, 50vw) vs max(30vh, 50vw)
  • max!(30vh, 50vw) vs max(30vh, 50vw) +
  • stylus.max(30vh, 50vw) vs max(30vh, 50vw) +
  • Max(30vh, 50vw) vs max(30vh, 50vw)
  • MAX(30vh, 50vw) vs max(30vh, 50vw) +

Other possibilities:

  • max(30vh, 50vw) vs max(30vh, 50vw) (call the proper function based on parameters)

nilslindemann avatar Jan 15 '23 13:01 nilslindemann

Okay. Pending a consensus on the side of the maintainers of the project:

Now that the topic on this subject is well developed, what would be the files (and, as a bonus, the lines) to modify? I started looking at the Stylus code, spotted a couple things, but I'm a bit lost on how things work.

The fact of proposing a solution, even half-functional, would undoubtedly help a lot for a faster implementation on a future version.

Scriptura avatar Jan 15 '23 14:01 Scriptura

@groenroos In my opinion, the task is to find something which separates the Stylus function from the CSS function. Category Prefixes by functionality may sound like a good idea, but when CSS starts doing this too, we are back at the same problem.

Is there any indication from the team working on the CSS spec that they plan or propose to do something like this?

groenroos avatar Jan 15 '23 14:01 groenroos

Is there any indication from the team working on the CSS spec that they plan or propose to do something like this?

Not that I am aware of.

But it is not just that. It appears a bit crude to me to denote the difference between these functions by dot-namespacing one of them. This does not indicate a functional difference, when it is actually the difference between a preprocessor directive and a run-time function, which, as already mentioned by @mwaeckerlin, usually is denoted with other syntax in other languages, for example a prefix for the preprocessor directive, for example $.

But I admit that I am not perfectly fluid with the Stylus syntax, and there may well be parts in its syntax which speak for dot-namespacing.

nilslindemann avatar Jan 15 '23 15:01 nilslindemann