📜 ⬆️ ⬇️

How to refactor 17 thousand lines of CSS

Many of us are working on big projects. The one I’m talking about has been living for 15 years now and has a couple of dozen web applications on ASP.NET WebForms, the main one of which contains about 1,500 aspx pages.

Why so much? The desire to adapt to customers with different requirements makes itself felt. Everyone wants some of their own special functionality and eventually gets it. But it's not about that. In addition to a large number of pages, we had a lot of CSS styles. Lots of.

Picture to attract attention

Source: Ursus Wehrli. The art of clean up
')

Initially, there was one web project. He had one CSS file. The file was not too large, and its readability did not cause anyone questions. Time went by, people came, created new functionality and added selectors to the CSS file. It didn’t look very scary, the contents could still be sorted out.

Then the time came for complex functionality, when the styles of controls, located on certain pages, on which a certain class was hung, overlapped. UI was liked by designers and users, and the class moved to other pages without any semantic connection with the original place. At some point, the file turned into a giant garbage dump of 17 thousand lines.

In order not to seem too simple, the system invented custom-made skins (separate styles) for individual customers. These 17 thousand lines for all are common, and here we’ll render a little more to customize text and background colors. In fact, it was not at all a bit, but very much. Kilometers of CSS, and some of them could be quite common for everyone.

Problem


We knew that despite the apparent visual integrity of the design, there are a lot of exceptions to the rules in the project. Here, for example:

Here we must say one more thing. We have more than one web project. Controls and styles for each of them developed in parallel for quite a long time, when, finally, someone had no bright idea to combine all this and bring to the library of common controls with some of the common styles (which can then be overlapped in each of the projects). The writing of this library did not mark the unification of everything and everything and the removal of duplicates. Instead, the controls and styles became even more. A common grid in a shared library is a useful thing, but you cannot delete the 12 available from three hundred places throughout the project - not a single normal team will subscribe to it. In general, at some point, the people no longer understand which of our many controls should be used in this or that case. Each of them can implement any specific behavior that occurred to the designer at the time of design of a specific functionality.

And we also stumbled upon a known problem in Internet Explorer 9, which for some reason refused to accept more than 4095 selectors in our glorious file.

And so we had to completely change the entire design of the application. It was clear that simply changing the design would be more expensive for us - we need to refactor CSS first. All CSS. Product owners understood the situation and we got the go-ahead for refactoring.

Getting started


So, we all agreed and got freedom of action - we begin to rake rubble. What we have in stock?

The first thing we started with was the removal of unused styles. Everything is simple with the C # code, the analyzer included the solution, and it showed which public methods can be removed. With styles, the only way is a text search by solution. If you are ever asked to clean 17 thousand lines in a CSS file in a solution from two hundred and fifty projects in this way, ask yourself an SSD disk. The process will go a little faster.

But if you are only planning to write such a giant project from scratch, here are some tips for you:

Always use the selector name in the code completely.


Never break it apart, for example:

public const string ProgressBar = "progressbar"; public const string ProgressBarSmall = ProgressBar + "small"; 

You will find a large progress bar, and remove a small progress bar as unused when refactoring. Then you have to restore. Do you think, and so remember what is used and what is not? In the project from one and a half thousand pages? And with hundreds of settings that enable and disable various features?

Also, do not be smart with designs of this type:
 public class Feedback { public static string CssClass = GetType().ToString(); } 

Firstly, for class heirs it still does not work ( GetType() should be changed to typeof(Feedback) ), secondly, it is impossible to look for.

Just store the text. Search in the text in the solution with preserve case and whole word. Do not complicate your life.

Use prefixes.


About this is to tell separately below. For now, let us omit the possibility of conditionally dividing selectors belonging to different modules, giving them separate prefixes.

Using CSS classes with the following names is impossible to search:
 .hidden .visible .error .text 

Much nicer with prefixes. The first two can be conditionally attributed to helpers - helpers. Give them the prefixes: .h-hidden and .h-visible . Already you can search! If the classes are specific, then the project name can be used. MyProject ? Let it be .mp- . Say .mp-login-page . Are controls common? - .ct-

Some third party libraries also use prefixes. By prefix it becomes easy to distinguish what the code belongs to. In addition, a small risk of crossing names is eliminated.

Do not use the same selector names for different needs.


Let's say .error and .text from the previous example is a completely neglected case. Selectors are not always unique and can be used quite differently in different cases.
 .mp-login-page .error { color:red; } .ct-feedback .error { background-color: red; } 

I would replace it with the following selectors:
 .mp-login-page-error { color:red; } .ct-feedback-error { background-color: red; } 

Bulky? Sure. But well searched and removed.

Make yourself a list of styles


Make yourself a static class indicating if not all, then at least often used styles. It will be much easier to search and then rename.

Acting


My uncle always said that even at the construction stage, it is necessary to think about how to make it so that, if desired, all this could be most easily broken.

Do you think your project is small and you don’t have to? We did not think either, and the project turned into a very large monster . In general, the removal of unused styles is quite a normal situation. There were a lot of unused classes, quite often it was possible to delete several text screens in a row. As a result, in our file of 17 thousand lines there are only ... 16 thousand. Is it true that we use all of this? Yes! This is our all acquired over the years. All you need!

What's next? It turned out that we still have a lot of selectors, and after cleaning all 100% of them are used. When we deleted unused styles, we paid attention to such selectors, in which the class name was found only once.
 .personal-report-bottom-section { margin-top: 10px; } .users-overview-header { padding-left: 15px; } 

It turned out somehow quite stupid. A bunch of similar styles in single-use classes. The idea was borrowed from Bootstrap - use helper classes. Let's say for margin-top: 10px you can use the name .h-mt10 , for padding-left: 15px; - .h-pdl15 . This helped to clear out many more places.

Then they began to look for repeating pieces. The most popular was to have a hyperlink or plain text with a picture on the left ():
 a.ct-imagewithtext { text-decoration: none; } .ct-imagewithtext img, .ct-imagewithtext span { vertical-align: middle; } .ct-imagewithtext img { margin-right: 4px; } .ct-imagewithtext span { text-decoration: underline; } 

I think that there were 20 similar styles in the code, no less. But each time the class names were new, the styles sometimes differed slightly, and sometimes quite seriously. In the process of transformation, the project began to improve visually - small but noticeable differences on different pages began to wear out.

Later we managed to refactor similar controls with completely different styles - we removed rarely used ones, changing them for analogues. If there were too many uses, they took the most successful from the point of view of the CSS control and “put on” everything else for it. Not everything was able to remove or make up, but more on that later.

Finally, the hands reached the library of common controls. As I already wrote, only a part of styles was brought to the general part. Then these styles already overlapped in each of our web applications. The idea was not very successful - everything that overlapped overlapped in each application in the same way, i.e. It was actually copy-paste. However, not quite. When the design needed a bit of color change, it was changed only in one place, forgetting about the rest. We carried it all into a common part and then killed copy-paste for a long time. Applications are good - toolbars, grids and other controls of different colors became similar to each other.

At some point, we realized that it would be nice to bring some kind of CSS Reset to this library. Prior to this, CSS Reset was presented in only two applications, and each has its own. As a result, it was decided to use Normalize.css , which we included at the very beginning. There we also added basic styles for our application - the size and typeface (also different everywhere) and many other things.

At that moment, we were still doing some sort of copy-paste removal and style unification. Finally, the hands reached the custom skins. In fact, they didn’t change the interface much, but for some reason they contained a huge amount of styles. The skins were about 50, some older ones were handwritten, the newer ones used LESS. Despite this, there was no common pattern, the generation was done manually, and the result was then docked to some common part. We started with them. First of all, the general pattern was taken out (not all coincided) and the repeating part. Secondly, we configured the generation of CSS files during the project build using the lessc utility. Then they proceeded to the old skins, where LESS was not used.

Despite the large amount of code, everything turned out to be not much changed by copy-paste. In the end, we got the same common part for everyone, a 1.5 screen template and 50 less-files with 15 variables for individual customization of the skin.

The common piece was carried to the end of our giant file. Since, with equal weights of CSS selectors, the priority will be beyond what is below in the text, this is important. At a further stage of refactoring, the add-on to Visual Studio - Web Essentials helped us a lot. A piece is very useful, if briefly, a kind of resharper for CSS. In addition to finding syntax errors and tips to add the missing vendor prefix, Web Essentials helps you look for the same classes inside a file. And it turned out that such a situation often occurred in our code.

The selector is defined, say, on the 6255th line:
 .topmenu-links { margin-top: 15px; background-color: blue; } 

Then somewhere on the 13467th:
 .topmenu-links { margin-top: 10px; background-color: green; } 

I exaggerate a little, but it was like this. And the case was not a single, but a massive one. Reached and to four overlappings. Web Essentials mercilessly swears at such things, so that all of them were found and deleted. As I said, when the selector weights are equal, the priority is lower, so we remove the selectors from above and combine them. The process is a bit risky. With a large number of different styles, hung on the same element and the scatter of selectors in the file, movement is fraught with a change of priority. But there's nothing you can do. During the whole work, our QA periodically walked through all the pages of the system and compared the view with the production.

At some point, we were ripe to break our huge CSS into pieces by segment. It turned out 120. With the build, the file was going back to one. And after a while we switched to LESS.

How is it now


Let's see how it all looks like an example .

Let's slightly simplify the task and imagine that we have a library of common controls ( CommonControls ) and a project for static content (CDN), which is used in the main web project.

Screenshot of solution with projects


In the library with controls, LESS-files that are built into one ( common-controls.less ) during a build and then translated into CSS ( common-controls.css ).

CommonControls Project Screenshot


Consider a little more detail what is stored.

Customize Build Events for the CommonControls project. I put everything in a separate file so as not to edit and merge the project file each time the contents of the script change.

The build events settings window


The script code is very simple. Putting together all the LESS files in the Stylesheets folder and transferring the result to the CombinedStylesheets folder. Then we run the preprocessor and get the finished CSS.
 set ProjectDir=%~1 copy "%ProjectDir%Stylesheets\*.less" %ProjectDir%CombinedStylesheets\common-controls.less call "%ProjectDir%..\Tools\lessc\lessc.cmd" %ProjectDir%CombinedStylesheets\common-controls.less %ProjectDir%CombinedStylesheets\common-controls.css 

Now look at the styles of the Cdn project. In the _cssparts folder are project styles, which are then _cssparts into the combined.less file. There are a lot of files in a real project. In the screenshot, everything is a bit simplified.

Cdn Project Screenshot


The sequence of files does not really matter, except for the first and the last one.

001-imports.less contains the following code:
 // Importing LESS template from CommonControls @import "../../CommonControls/Stylesheets/01-essentials.less"; // Usual CSS import @import "common-controls.css"; 

The first directive imports the contents of the LESS file, in this case 01-essentials.less . This is the same as if we were concatenating this file along with the rest when combined. Import allows you to use all the variables and mixins that we defined in the CommonControls library. The second directive - classic import - is generated in the resulting CSS as is. In general, CSS imports are not recommended, and the only reason why it is here is IE9.

z-ie9-special.less contains one single selector, which is the most recent in the combined file and is used on a special page to understand whether it is used or not. If the total number of selectors has exceeded 4095, the style will not apply. So you need to break the file into pieces. In fact, we had to not combine the resulting CSS of the control library and the actual CSS for the web project.

When building, the following things happen:
 @REM Copy common controls stylesheet COPY %ProjectDir%..\CommonControls\CombinedStylesheets\common-controls.css "%ProjectDir%Skins\common-controls.css" /Y @REM Combine CDN LESS files and run preprocessor copy "%ProjectDir%Skins\_cssparts\*.less" %ProjectDir%Skins\combined.less call "%ProjectDir%..\Tools\lessc\lessc.cmd" %ProjectDir%Skins\combined.less %ProjectDir%Skins\combined.css 

The combined Skins library of kontrol and CSS for the web project gets to the root Skins folder. In real projects, combining the resulting CSS can be made more elegant than file concatenation, but this is just an example.

Now let's look at the generation of custom skins.

Screenshot of the folder with custom skins in the Cdn project


In the _custom-parts folder _custom-parts is a template for generating custom-template.less . Suppose that for the time being we need only to customize the colors of the H1 and H2 headers (in reality, of course, there are a lot more things). custom-template.less will look like this:
 h1 { color: @h1Color; } h2 { color: @h2Color; } 

Default-values.less will contain the values ​​of variables by default (to be able to overlap in the skin, not everything, but only some of the values):
 @h1Color: #F58024; @h2Color: #E67820; 

In each of the skins ( skin.less ) there will be something like this code:
 @import "..\_custom-parts\default-values.less"; @h1Color: #000; @h2Color : #707050; @import "..\_custom-parts\custom-template.less"; 

Import the default values, overlap them with your values ​​and import the template.

To generate all this, we write such code in the pre build event:
 @REM Regenerate customskins using their LESS templates for /r "%ProjectDir%Skins\" %%i in (*.less) do ( if "%%~nxi"=="skin.less" call "%ProjectDir%..\Tools\lessc\lessc.cmd" "%%~dpnxi" "%%~dpni.css" ) 

At the output, next to each skin.less we obtain skin.css for an example above of this type:
 h1 { color: #000000; } h2 { color: #707050; } 

In general, at first, the contents of our LESS files (not counting custom skins) were no different from regular CSS. With a few exceptions, when the parser refused to accept an invalid code, let's say this:
 margin-top: -4px\0/IE8+9; 

Not sure what the hack for IE looks like, but God bless him. In LESS, you can escape a string using ~"" characters:
 margin-top: ~"-4px\0/IE8+9"; 

Everything else went without problems. Soon began to appear unpretentious variables:
 @tDefaultFontSize: 14px; 

Then the mixins are more complicated:
 .hAccessibilityHidden() { position: absolute; left: -10000px; top: -4000px; overflow: hidden; width: 1px; height: 1px; } 

The meaning of this mixin is that in addition to its use in the auxiliary class, it is also used in some others. In our case, it has become even more interesting. When at some point we realized that it was not possible to rewrite, and even “make up” the styles of 12 grids, it was a good idea to take out common colors and styles into variables and mixins. It turns out that for old projects LESS is even more interesting than for new ones. In general, there is where to roam. For example, background-image generation for buttons of various types in the presence of a sprite:
 .ct-button-helper(@index, @name, @buttonHeight: 30, @buttonBorderThickness: 1) { @className: ~".ct-button-@{name}"; @offset: (@buttonHeight - 2*@buttonBorderThickness - @buttonIconSize) / 2; @positionY: @offset - (@index * (@buttonIconSize + @buttonIconSpacingInSprite)); @{className} { background-position: 8px unit(@positionY, px); } @{className}.ct-button-rightimage { background-position: 100% unit(@positionY, px); } } 

Call something like this:
 .ct-button-helper (0, "save"); .ct-button-helper (1, "save[disabled]"); .ct-button-helper (2, "cancel"); .ct-button-helper (3, "cancel[disabled]"); 

It is also good to generate CSS for font descriptions, although the mixin implementation often depends on the specific font.

Summary


Let's take a quick look at what we did.

In general, we regained control over the code. Subsequent redesign was no problem. In addition, they made it smarter by changing component by component. Not to say that the code was perfect or that it was 10 times smaller. We still have a lot of things, and it can be difficult to understand. But on the other hand, the number of copy-paste has significantly reduced, and as a result there are fewer visual differences in different parts of the system, which in theory should look the same.

Source: https://habr.com/ru/post/265433/


All Articles