
Today I want to talk about why and how we came to use the Stylus preprocessor in the development of Yandex. Mail, and also describe the method we use with styles for IE. It is very easily implemented using preprocessors and makes IE support easy and convenient. We have developed a special library for this, which we also share -
if-ie.styl .
This is only the first article in a series of articles on the use of the Stylus preprocessor in Yandex.Mail, which we are preparing for publication.
How we come to use preprocessors
Although externally, Yandex.Mail looks like a one-page application, inside it contains a huge number of various blocks, their modifications and contexts, in which these blocks and modifications can be.
')
In addition, she already has more than thirty themes. There are themes with a light background and a dark one, there are themes that differ only in colors, and there are also those in which almost the entire interface is molded manually from plasticine (
http://habrahabr.ru/company/yandex/blog/110556/ ). In some themes, only one background image, and in others, the background may change - randomly or depending on the time of day and weather.
Because of all this, there are many variations of the visual presentation of the interface, which makes it a little different to relate to the development process, to look for tools that are more suitable for solving the problem.
When we just started the “neo2” interface, we chose a familiar solution - Template Toolkit 2 template engine, with a somewhat non-standard script to use it to generate CSS, rather than HTML. At first, we only needed variables, but over time the topics became more complicated, and as a result it turned out that such a tool was inconvenient. The cumbersome syntax, the lack of specialized CSS functions and the general sense of misusing the tool made it necessary to look for other options. We realized that we can not do without a preprocessor.
Preprocessor selection
Choose between three options: Sass, Less and Stylus. The process was quite simple: we took several of the available blocks, after which we tried to reverse them using each of the preprocessors.
Less seemed at first very simple and convenient: it uses the familiar CSS syntax and it can be used in the browser, which is convenient for debugging. But when trying to do something complicated, its capabilities are no longer enough. What can be done without arrays, cycles and normal conditional constructions? Not so much.
After Less
Sass was very pleasant: powerful, with a good community and all sorts of additional libraries like
Compass . However, there was one serious drawback: Sass has a very inflexible parent reference (using the
&
symbol in selectors to point to the parent selector). Because of this, several problems arise, and all of them consist in the fact that
&
cannot be used for the prefix definition of multiple classes, the refinement of an element or the concatenation of classes. Here are some examples of what other preprocessors can do, but which can cause an error in Sass:
&__bar
, used for the .foo
selector, should give .foo__bar
- such constructions are needed to simplify the use of BEM names and are very convenient when you need to generate many modifiers in a loop..baz&
, applied to the .foo .bar
selector, should give the multiclass .baz.foo .bar
, but this will not work in Sass: you can only give the multiclass to .bar
if you write &.baz
, but not vice versa.button&
applied to .foo
should have specified the selector to button.foo
, but alas.
And everything would not be so bad if Sass could have used a pointer to the parent selector in interpolation. But no - Sass first expands the interpolation and only then tries to apply the selector, producing an error.
The most annoying thing is that this behavior is not a bug, but a
feature . And it
will not change. This is the ideology of the Sass syntax.
Stylus was the newest preprocessor, and in the end our choice fell on it. Yes, it is damp, there are unpleasant bugs in it, its community is not so big, and the development is not going as fast as we would like. But for our tasks, it was best suited, and this is why:
- Stylus is a very flexible preprocessor, and often it is much more flexible than the same Sass. For example, parent references Stylus reveals perfectly.
- In Stylus, there is such a thing as transparent mixins - the ability to call a function as a regular CSS property. That is, first define
foo(bar)
, and then call it as foo: 10px
. Such a record would be equivalent to calling foo(10px)
. For ordinary functions, this is not always convenient, but it allows you to override any existing property. Let's say you can override the margin
in padding
. But seriously, when using this functionality, you can easily get confused and complicate the understanding of what the code does: it will not always be clear what functions were defined above by the code and what will happen as a result of calling the next property.
However, transparent mixins make it very easy to support, for example, browser prefixes. You do not need to remember which prefixes a particular property has, and whether you need to write a special construction for the next property. It is enough just to always write without prefixes, and the connected library will take care of adding them (we use our own nib for this).
- Stylus is written in JS. This means that it is easier to maintain and edit bugs in it (all developers of the Yandex.Mail interface know JS much better than Ruby). In addition, it makes it easier to use Stylus in a chain with other tools on node.js (for example, CSSO ).
In the Stylus, there are still a lot of very different useful things, but the ones listed above made us make a choice in his favor.
Of course, besides the benefits, Stylus has some drawbacks. And the main one is the flexible syntax - the authors of the preprocessor consider it the main advantage. Chasing the flexibility, they only implemented the indentation based syntax, while the “a la CSS” option was somehow screwed on top, and it would not be possible to just take and rename
.css
to
.styl
— not all the CSS spellings will work and in Stylus. But we decided that the possibilities that this preprocessor gives us make its shortcomings not so significant, so we had to put up with some of the capriciousness of the parser (and start using syntax based on indents).
Summing up the story about the choice, it is worth noting that Sass and Stylus are two almost equivalent options. Each of them has its own advantages and unique features, as well as disadvantages. If you already use one of these preprocessors and you are satisfied with everything - great, you can not think about finding a new one. But if you are only suited to the choice, or if the preprocessor you are using becomes cramped, try to compare all the options. The best way to do this is to try each preprocessor on its task. By completing a part of your project on each of the preprocessors, you will understand which features are important to you and which are not. Just do not forget that the preprocessor is not just a different syntax, but also a different approach: with such an overhaul, you can at the same time refactor the code by doing something better than it was with simple CSS.
During the translation of Yandex.Mail to Stylus, it became clear that the preprocessor can provide features that we did not have using regular CSS. We have long used in the Post separation of styles for conventional browsers and for older versions of IE. All styles were decomposed according
to the BEM file structure, and for each block two files were created side by side:
b-block.css
and
b-block.ie.css
.
Styles for all browsers went to the first file, and only those that were needed for Internet Explorer, respectively. It should be noted here that for a number of reasons we are switching IE8 to IE7 compatibility mode, and also, having ceased to support IE6, we are sending it to the “lightweight” version of Mail. Thus, we have two different versions of styles: one for all browsers, the second for all older versions of IE. Everything was collected automatically - first a “for all” style sheet was collected, after which all styles from the
.ie.css
files were added to it - and the style sheet for IE was obtained.
Each browser receives only its own style sheet - for this we use conditional comments, like this:
<link rel="stylesheet" href="style.css" />
At first, this separation of styles worked well, but with the advent of the preprocessor, the question arose: Is it possible to do better?
It turned out that you can.
This is how the library for Stylus appeared - “
if-ie.styl ”. In fact, this is not exactly a library, but rather a methodology of how to work with styles for IE. There is nothing secret in this methodology, so we decided to post it on GitHub under the MIT license. You can easily use it for your project, report any errors found or even correct them yourself - Open Source, everything. Or maybe someone will be able to rewrite it for another preprocessor? Fork a project or create a new one, taking the methodology as a basis - it will be great.
The basis of the methodology
The methodology is based on a very simple idea: we create a variable
ie
, which will be equal to
false
for all regular browsers, and
true
when we want to get styles for IE. After that, you can use only one main style sheet, in which you can delineate styles for different browsers under normal conditions. A simple example: take the
style.styl
file:
ie ?= false .foo overflow: hidden zoom: 1 if ie
This default Stylus code - for regular browsers - will be like this CSS:
.foo { overflow: hidden; }
Since the
ie
variable was
false
, the
if ie
condition did not work, and the
zoom
property was not included in this style sheet.
Now create the next
style_ie.styl
:
ie = true @import style.styl
If we feed such a Stylus code, we get:
.foo { overflow: hidden; zoom: 1; }
This is exactly what we needed - a separate table, in which there are styles needed only in IE.
At the same time, if we want to write some styles only for ordinary browsers, we will be able to use the condition
if !ie
- and filtering styles will become very simple.
Additional features if-ie.styl
Of course, this seemed to us a little and we decided to add all sorts of useful functions that would optimize much in our code. So it turned out "library".
To use it, connecting styles will look a little different.
The file
style.styl
will be like this:
@import if-ie.styl .foo overflow: hidden zoom: 1
A
style_ie.styl
so:
ie = true @import if-ie.styl @import style.styl
There are two differences in comparison with the version without additional functions:
- In both files you need to connect the library to all other styles, and be sure to both times. (It may seem that only the first would suffice - after all, the style sheet for IE already contains the main style sheet - but then some subsequent features will not work). In this file,
ie ?= false
already defined, and therefore it is not necessary to explicitly state this in the main style sheet. - If IE condition is missing in the style sheet for IE, the first feature of the library is shown: the
zoom
property automatically appears only in the style sheet for IE. Of course, this property can also be used in ordinary browsers, but in practice this need happens extremely rarely, so you can ease the main style sheet at least a little.
In the Stylus code, such functions are very easy to define. In this case, the new transparent mixin will look like this:
zoom() zoom: arguments if ie
With this code, we create a
zoom
function that assigns the corresponding property with any passed arguments only if we are currently collecting styles for IE.
inline-block
It is widely known that in older versions of IE, the out-of-box
display
property does not support
inline-block
values. In IE there is a similar mechanism, but it works only for inline elements and only if they have the hasLayout mechanism enabled. A completely random
display: inline-block
in IE just turns it on, but “forgets” to switch an element to
display: inline
, so such an entry will not work for initially block elements like
<div>
. So the easiest way to guarantee to apply inline block behavior to any element in IE is to register only a
zoom: 1; display: inline;
for it
zoom: 1; display: inline;
zoom: 1; display: inline;
.
In the if-ie.styl property, the
display
property will do this automatically and only for IE. Regular browsers will see
display: inline-block
, while IE will only receive a
zoom: 1; display: inline;
zoom: 1; display: inline;
. Then someone might have noticed that the second entry is longer than the first one, and for the initially inline blocks it would be possible to save ... But if you carefully calculate, the savings will be just one byte. This is not worth keeping an eye on - whether the block is inline or not initially. Moreover, ideally, the layout should not depend on which element it is applied to.
Override CSS3 properties
If by default we don’t give normal browsers a
zoom: 1
, dividing all styles into two files (as it was, in general, before the introduction of preprocessors), then with preprocessors we can think about how to facilitate the style sheet for IE.
With the help of Stylus, this is done very simply, while you can save quite a lot of bytes: after all, what we don’t need in old IE is the properties with prefixes.
Above, I mentioned in the article that we use the
nib library — this library defines transparent mixins for most new CSS properties, for example, for transients. As a result, in styles for Stylus we write simply
transition: opacity .3s
and as a result we get this property with all the necessary prefixes. But in IE, we not only do not need prefixes, but the property itself! So, we can redefine this and many other properties in our library so that they do not return anything.
This is done like this:
if ie transition() z if 0 transition-property() z if 0 // …
Everything is almost clear here: with one condition, we only redefine all necessary properties at once for IE. However, due to the peculiarities of Stylus, it is necessary to write at least one rule in the function definition —
z if 0
is pretty short.
As a result, IE, instead of the mixin defined in nib
transition
sees the one that we defined in if-ie.styl, and the resulting style sheet will be much easier than before - almost everything unnecessary from it will be cut.
rgba-ie
We will not inflate the article and describe everything that is in the if-ie.styl - this is already
described in the project
documentation .
However, we need to tell you about one more function, which turned out to be very useful to us in the framework of the thematisation of Yandex.Mail. This is the
rgba-ie
function. In fact, this function could simply be called
rgba
, but Stylus has a bug: the functions defined in JS cannot be redefined in the same way as those defined in Stylus, so I had to create a new one.
What does she do? Older IEs do not support color values ​​specified in rgba format. Therefore, usually the developers either prescribe the corresponding colors twice - first for old IE in the usual hex-format, and then to all normal browsers in the desired
rgba
- or use modernizr and use it and the
.rgba
class
.rgba
set the corresponding colors where it is needed. But for folbek in IE, every time you still have to calculate the approximate color of what we will degrade into it. Most often it will be the desired color superimposed over the background of the page or the middle background of the element over which the color will be applied in
rgba
.
The
rgba-ie
function from if-ie.styl greatly simplifies this task: duplicating the capabilities of the usual
rgba
function, we get one more optional parameter that can be passed to the function — the background color for the foldback. By default, this parameter is set to
#FFF
.
A simple example:
.foo color: rgba-ie(0,0,0,0.5)
In normal browsers, this color will be normal
rgba(0,0,0,0.5)
, but in IE it will turn into
#808080
- that is, into the corresponding color superimposed over white.
A more complex example, with a target background as the last argument (and using one of the Stylus features - the ability to specify a color in hex instead of three digits
r
,
g
and
b
):
.foo background: rgba-ie(#FFDE00, .42, #19C261)
In this example, normal browsers will have the color
rgba(255,222,0,0.42)
, but IE will get the correct
#7ace38
.
At the same time, it is possible to set the default folback using the
$default_rgba_fallback
variable.
As a result, you can greatly simplify your life if you use the function
rgba-ie
instead of the usual
rgba
- in this case, you can almost not remember about IE.