How we build CSS in Office Delve

A year ago a friend called me and said: “Hey man, wanna try working for Microsoft?”

Well why not. Not as they’ll ever consider me anyway. Fast forward 8 months, 2 phone screens and 6 interviews, I found myself sitting in the office at Oslo, with two big monitors staring at me, and a not-so-tiny gut feeling of “what am I supposed to do now.” At least I knew my name: Jakob Werner. And I was the new guy.

Delve

The product I was going to work on was growing fast. The build process was just switched to using Webpack, React, and Typescript, which made it possible to build new components at a rapid pace. This is when CSS issues started piling up, and ignoring them was no longer an option.

- “You’re that CSS guy, right?”

Seems like everyone knew I was joining. My colleagues helped me feel like part of one big family and directed me to work on the stuff I felt really comfortable with — CSS. I couldn’t wait to start.

So I figured investigating Delve’s CSS would be a good idea. It did have LESS files, but they were actually only CSS files renamed to .less with a few mixins every now and then. We had specificity issues, lots of unmatched colors were still all over the place, classes were clashing with each other and !important rules were overriding something way out of intended scope. These issues had to be addressed. I could finally fix something!

One rule to rule them all

The major concern was that this way of writing CSS collides with “The Rule”: encouraging devs to fall into The Pit of Success. Delve engineers encourage architecture that encourages the right thing to naturally happen: this was a key decision behind React and Webpack. Delve’s previous CSS, on the other hand, was just too easy to fail and break the design of our product. All must follow The Rule, and CSS should not be an exception.

Image for post

So we started thinking of ways to fix this. Starting was easy — just createvariables.less, mixins.less and animations.less files. Okay, we solved colors, synced some animations and browser special cases, but we still had all those specificity issues.

Then we tried BEM. It didn’t work for us, because we feel a dev shouldeffortlessly write CSS and feel confident about it. Code reviews were helpful, but inefficient and error-prone.

CSS modules

Glen Maddern’s article was a reason to bring cake to the office. If you haven’t read it yet, please do! Using CSS modules we could hash class names to isolate styles from each other. CSS modules spit out a variable which contains the hashed names. So, using JSX, instead of writing:

return <div class="container"> ... </div>;

We could do this:

var styles = require("component.module.less");
return <div className={styles.container}> ... </div>;
// generates:
<div class="moduleName-container__1h4ds"> ... </div>;

We just got rid of class name clashing! And this follows The Rule perfectly!

We had a prototype working in no time and proceeded with converting all our components to modules. This made our developers feel a lot more confident in writing code and they no longer needed to worry about class name clashing and specificity. CSS was finally local and isolated to a specific component.

A new challenge

As part of Office 365, Delve supports a variety of languages. Some of those are right-to-left (RTL), so we needed to find a way to convert our CSS in order to mirror the page.

We used ExtractTextPlugin to extract CSS from all of our components, generate a single file with all of our styles, and convert it to RTL as part of our build process. On the initial page load we would decide which CSS file to load based on language, wait for it to load and then show the page. This was a big unnecessary cost for us.

As we built new features, our single CSS file got bigger and bigger. So did our build time. ExtractTextPlugin was responsible for 5 minutes of build time and requesting a single CSS file on the first page load didn’t make a lot of sense as many components could be loaded on demand afterwards.

The idea was to initially load only the components we need, and load only the CSS for those components. But how will webpack know whether it should load left-to-right or right-to-left CSS?

PostCSS

We figured — what if we converted our CSS to inline the minimal amount of CSS we need to support RTL and always included both versions? PostCSS to the rescue!

I made a PostCSS plugin, which utilizes Mohammad Younes’s great rtlcss framework to find out which properties it needs to convert to fully support RTL CSS inline. Example:

.myClass {
color: red;
float: left;
}
// converts to:
.myClass {
color: red;
}
html[dir='ltr'] .myClass{
float: left;
}
html[dir='rtl'] .myClass{
float: right;
}

I tweaked it to make both animations and CSS modules work. We could finally load the minimum CSS required for and page/scenario, without worrying about LTR/RTL. That’s a Pit of Success!

Results

This effort reduced the initial page load by 50KB gzipped, shortened our build times by 40%, and most importantly, we still follow The Rule so developers don’t need to change anything about the way they build components.

This helped with creating our beautiful new Profile page:

We still hit an occasional hiccup like this one, but we’re really happy with the way CSS is built for Delve.

What’s next

In our constant effort to make Delve delightful for our customers, we always think of ways to make it easier for our developers to do so. With that in mind, we plan to investigate React animation libraries (like Cheng Lou’s React Motion), find a way to generate Typescript definition files for CSS modules and try and share constants between Typescript and our CSS.

I’d love to hear about how you solved similar problems, and if you have any questions, do ping me.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store