Moving from Stylus to Sass

Moving from Stylus to Sass

My CSS preprocessors journey

I started my web development journey around the year 2012 as HTML & CSS developer. My first projects were more of an introduction to web development and discovering its possibilities, rather than preparing me to become a full-fledged developer.

I was more interested in the graphic design of web pages, which was booming at the time, and felt that knowledge of programming was more of a necessity if I wanted to apply my graphic designs.

Strange as it may seem today, at that time a significant proportion of websites were still laid out using HTML tables, the absolute minimum of websites had a responsive display and it was not uncommon to use inline styles.

At least that's how it seemed to me.

The thing is, it's not hard to learn the basics of any language these days. There are plenty of online courses, boot camps, YouTube tutorials and Q&A sites. The roads are trodden and paved.

Back then, it was much harder for a complete beginner like me to learn all the "best practices" and adapt to new trends as they emerged.

At the time, I was unfamiliar with module bundlers and javascript frameworks and usually worked with a single CSS file whose structure was determined only by clumsily written comments.

So it was almost a revelation when I discovered CSS preprocessors. I dabbled with Less for a while but quickly settled on the best of the holy trinity: Stylus.

Discovering Stylus

I've been using Stylus since 2014 for over seven years and have always been dismissive about any other CSS preprocessor.

Stylus was simply the best. Not only did it allow the nesting of selectors and the use of mixins. It radically stripped down CSS syntax to the bare minimum. It removed semicolons, compound brackets, and even colons, and replaced them with indentations and spaces.

It allowed the programmer to think spatially, in the context of the DOM. While chaining class names is hell in pure CSS, in Stylus it's all about structured and logical code. I was also able to write the style first and then write the HTML for it.

For example, when styling a contact box, I can write simply:

.contact
    &-box
        &-heading
            font-size 1.5rem

            &:hover
                color midnightBlue

        &-info
            font-size 1.1rem

Which already defines the HTML structure. The same properties written in pure CSS would be flatter and less understandable.

.contact-box-heading {
    font-size: 1.5rem;
}

.contact-box-heading:hover {
    color: midnightBlue;
}

.contact-box-info {
    font-size: 1.1rem;
}

Of course, other CSS preprocessors had this kind of superpower. The winning difference was that Stylus got rid of all the annoying nonsense that stifled creativity and flow such as curly braces, dollar signs, colons, and semicolons. Even variables didn't need to be denoted by special characters - they were just simple words.

But you could still use them. Another advantage of Stylus was that you could put pure CSS into Stylus code and it would still work.

Soon I also started using stylus packages that allowed me to create layouts on a professional level: among others, mainly the jeet package for grid and columns (this was before grid and flex were supported in most browsers) and rupture to make breakpoints handling much easier.

The best thing is that I wasn't forced to use some pre-baked solutions and I built everything from scratch.

I have worked with Stylus on more than 30 projects, some of which have been very large, with more than 200 individual .styl files. Stylus has proven to be robust and scalable, easy to use with or without a module bundler, time-saving and highly rewarding.

Farewell

By 2020, problems began to emerge. The first was that compilation errors became more and more common. This was mainly caused by misplaced whitespaces or bad indentation. The problem was that the IDE's formatting and linting plugins were out of date and didn't recognize some syntactical features. Also, the hinting was often broken or did not include some of the newer CSS properties.

Sometimes Stylus was impossible to use because the helper plugins were driving me crazy. It is also important to note that Stylus has never seen a major release: at the time of writing, the latest version is 0.59.0. Also, the project was poorly maintained and Stylus wasn't even that popular anymore: according to The State of CSS of 2021, only about 15% of all developers used Stylus and satisfaction reached only 35%. In comparison, Sass was the preprocessor of choice for 70% of developers and enjoyed 84% satisfaction.

I ran out of patience when I set up a new project in Angular and found out that Stylus is no longer offered for writing styles. It was still possible to set it up for use in the application at the time, but the Angular team had clearly stated that it would not be supported in the future (only 0.3% of all users were using it).

I choose you, Sass!

I had to make a difficult decision to move from Stylus to another preprocessor. I chose Sass for a number of reasons.

Firstly, as I mentioned, it is the most popular of them all. I'd always been put off by its ugly sibling, SCSS, and I had no idea what the difference was.

But SASS is great. It is very similar to Stylus, except for the use of colons and syntax strictness. It has some of the same drawbacks as Stylus, such as delayed support for new CSS features. However, it wasn't hard at all to migrate most of the custom mixins to Sass.

The IDE plugins are well-maintained and the community is huge. I'm confident I won't be forced to switch to anything else for years to come.

What do I miss

There are still a couple of features that I really liked in Stylus that I would like to see implemented in SASS.

Hashes

The most painful are the hash objects. In Stylus it was a piece of cake:

/** Stylus **/

foo = {
  bar: hi,
  baz: hey
}

foo.baz
// => hey

foo[baz]
// => hey

foo['baz']
// => hey

Not so in Sass. It has maps that are limited compared to Stylus hashes.

$foo: ("bar": "hi", "baz": "hey")

map.get($foo, "baz")

// => hey

Worst of all, Sass requires maps to be written on a single line. I got used to creating hashes for colors with both light and dark mode variants, and that was no longer possible with Sass. Ultimately, I ended up putting the multi-line map in a .scss file and importing it into a .sass file.

Imports and files structure

I like being able to import my styles into a single file since I use Webpack as a module bundler for most of my projects. When I was using Stylus, I was used to a certain directory structure that I couldn't reproduce with Sass:

There were some main directories (base, layout, partials etc.) containing subdirectories named after the function or purpose.

├── styles
│   ├── base
│   │   ├── color
│   │   ├── reset
│   │   └── typography
│   │
│   ├── layout
│   │   ├── body
│   │   ├── footer
│   │   └── navbar
│   │
│   ├── partials
│   │   ├── container
│   │   ├── flash
│   │   └── form
│   │       ├── checkbox
│   │       └── input
.   .
.   .
.   .

Each subdirectory contained another subdirectory or an index.styl file. All I had to do was to import all the files in the main directories using a wildcard. This represents a pattern called screaming architecture, where the names of the folders represent the function of the files in them.

/** Stylus **/

@import "./base/**/index.styl"
@import "./layout/**/index.styl"
@import "./partials/**/index.styl"

No explicit import was required. Unfortunately, this is only partially possible in Sass, so now I have to explicitly import each file I create. I prefer to keep my styles separate and organized. People who do not separate their Sass files into specific directories are beyond my comprehension although I know that many are quite happy about it.

I had to adapt another way of storing stylesheets that is ultimately very similar to the original, with an extra step of manually importing each newly added file.

├── styles
│   ├── base
│   │   ├── _color.sass
│   │   ├── _reset.sass
│   │   ├── _typography.sass
│   │   └── all.sass
│   │
│   ├── layout
│   │   ├── _body.sass
│   │   ├── _footer.sass
│   │   ├── _navbar.sass
│   │   └── all.sass
│   │
│   ├── partials
│   │   ├── _container.sass
│   │   ├── _flash.sass
│   │   └── form
│   │   │   ├── _checkbox.sass
│   │   │   └── _input.sass
│   │   │
.   .   └── all.sass
.   .
@import 'base/all'
@import 'layout/all'
@import 'partials/all'

Operators and built-in functions

Compared to Stylus, Sass has fewer built-in functions and mathematical operations. Also, interpolation is more cumbersome in Sass, and all the at-rules are less straightforward. But that may just be a matter of habit.

Noteworthy are the loops: Sass has @each and @for. I kind of hate it. The stylus for is much more versatile.

/** Sass **/

$colors: "red", "green", "blue";

@each $color in $colors
    .#{$color}
        color: $color


/** Stylus **/

color = ( red green blue )

for color in colors
    .{color}
        color: color

As for the range:

/** Sass **/

@for $i from 1 through 3 
  .branch:nth-child(3n + #{$i})
    margin-left: #{$i * 10}px

/** Stylus **/ 

for i in (1..3)
    .branch:nth-child(3n + {i})
        margin-left: {i * 10}px

Other than that, I think the sass is great and I have gotten completely used to it by now.