lastminute.com logo

Technology

Themify your (S)CSS Modules

aurelio_gamero
aurelio gamero

Personalise your application with shiny themes combining CSS Modules and Bootstrap SASS


Building your site styles

The mythical Hello world…, because no project can’t start without these two magic words

Hello world
Hello world without CSS

But anyone should recognize that any Web page with a bit of CSS, looks really better…

<style>
  * {
    font-family: "Open Sans", sans-serif;
  }
  header {
    padding: 16px;
    background-color: #0063be;
    color: #fff;
    font-size: medium;
  }
  h1 {
    text-align: center;
    color: #3c3c3c;
    font-style: italic;
  }
</style>

Hello world with CSS
Hello world with CSS

But what happens when you let your imagination flies… and you becomes crazy styling your Web page/site/app:

CSS
A piece of CSS

You’ll end up more than a thousand lines of CSS, with deeply-nested, hard-to-read selectors with a bunch of !important here and there. You will fear to touch anything as a little change in one part can become a huge mess in a totally different section of the site…

CSS_mess

Meeting preprocessors

Fortunately, preprocessors arrived to make designers and developers life easier. And We could finally split and organize our CSS in more manageable pieces, and also use variables!

If you are not familiarized with preprocessors, take a look at this:
Introduction to CSS Preprocessors: SASS, LESS and Stylus

And again… becomes a nightmare. With pre-processors, the modularization of your stylesheets would end up within a heap of files very difficult to organize. Take a look at the next picture so you can feel lost:

SASS files
SASS files

File patterns to the rescue!

The file patterns are a very useful tool to organize your stylesheets in a common way until you have a more personal structure. Here are the most famous:

Simple Structure:

modules/ _color.scss _typography.scss partials/ _base.scss _navigation.scss
vendor/ _ico-moon.scss main.scss

The 7–1 Pattern:

base/ _reset.scss _typography.scss _color.scss components/ _buttons.scss
_navigation.scss _gallery.scss layout/ _header.scss _grid.scss _sidebar.scss
pages/ _home.scss _about.scss _contact.scss themes/ _theme.scss _admin.scss
helpers/ _variables.scss _functions.scss _mixins.scss vendors/ _bootstrap.scss
_jquery-ui.scss main.scss



SMACSS/BEM Architecture:

_base.scss is for vendor code and styles on base elements (html, body, ul, p,
etc.) _layout.scss is for macro layout styles _modules.scss is for micro layout
styles _other.scss is for whatever doesn’t fit in first three _shame.scss is for
code you feel ashamed of and plan to improve

Others:

pets/ _dog.scss _cat.scss aliens/ _predator.scss _alf.scss food/ _cookies.scss
_pasta.scss main.scss

But all this file patterns are just conventions and different developers may not respect them. Actually sometimes is hard to follow them: ever lost time deciding if a selector was a block or an element?

Introducing CSS Modules

Now we have more or less the basics for a medium large Web project (or, at least in my experience, these are the common elements).

But we are living in the time of continuous improvements and we have now a very useful technique which allows us managing the styles for each component much better than any previous pattern: the CSS Modules.

What are CSS Modules?

CSS files in which all class names and animation names are scoped locally by default

CSS Modules, during our build step, search through styles files imported, then look through the JavaScript we’ve written and make the classes accessible. Our build step would then process both these things into new, separate HTML and CSS files, with a new string of characters replacing both the HTML class and the CSS selector class: https://css-tricks.com/css-modules-part-1-need

What he is saying
What he is saying

In other words, we can isolate the styles of each component and keep the CSS and JS code of components together, without take care if we are importing properly the SASS file in our main file or overwriting in another part of the code. And If you remove components, styles will go with theme: no more zombie CSS left in your site!

Let’s tweak our ‘Hello World’ to use CSS Modules

Next steps explain how to transform the first simple example in a themed React project with CSS Modules:

webpack.config.js

module: {
   rules: [
     { test: /\.(js|jsx)$/,
       exclude: /node_modules/,
       loader: 'babel-loader'
     },
     { test: /\.scss$/,
       use: [
         MiniCssExtractPlugin.loader, { // Component
           loader: "css-loader", options: {
             modules: true, // Configuration
             sourceMap: true
           }
         },{
           loader: "sass-loader", options: {
             modules: true, // Configuration
             sourceMap: true
           }
         }]
     }]
},



index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Themes with 'styled-components' (PoC)</title>
    <!-- Extract CSS
    <style>
        * {
            font-family: 'Open Sans', sans-serif;
        }
        header {
            padding: 16px;
            background-color:#0063be;
            color: #fff;
            font-size: medium;
        }
        h1 {
            text-align: center;
            color: #3c3c3c;
            font-style: italic;
        }
    </style>
    -->
  </head>
  <body>
    <!-- Extract HTML
    <header>TSC (Poc)</header>
    <h1>Hello world...</h1>
    -->
    <div id="app"></div>
  </body>
</html>



src/mainLayout.scss

* {
  font-family: "Open Sans", sans-serif;
}

header {
  padding: 16px;
  background-color: #0063be;
  color: #fff;
  font-size: medium;
}

h1 {
  text-align: center;
  color: #3c3c3c;
  font-style: italic;
}

src/app.js

import React, { Fragment } from "react";
import ReactDOM from "react-dom";
import styles from "./mainLayout.scss";

class Hello extends React.Component {
  render() {
    return (
      <Fragment>
        <header>TSC (Poc)</header>
        <h1>Hello world...</h1>
      </Fragment>
    );
  }
}

ReactDOM.render(<Hello className={styles} />, document.getElementById("app"));

Building some components

building-blocks
building blocks

Now we are going to refactor and extract some code to start to create components and start to see the real helpful of this technique:

src/app.js

  import React, { Fragment } from 'react';
  import ReactDOM from 'react-dom';
+ import { Header } from '.header/header';
+ import { Title } from '.title/title';
  import styles from './mainLayout.scss';

  class Hello extends React.Component {
    render() {
      return (
        <Fragment>
-         <header>TSC (Poc)</header>
-         <h1>Hello world...</h1>
+         <Header/>
+         <Title/>
        </Fragment>
      );
    }
  }

  ReactDOM.render(
    <Hello className={styles}/>,
    document.getElementById('app')
  );

src/mainLayout.scss

  * {
    font-family: 'Open Sans', sans-serif;
  }

  header {
-   padding: 16px;
-   background-color: #0063be;
-   color: #fff;
    font-size: medium;
  }

- h1 {
-   text-align: center;
-   color: #3c3c3c;
-   font-style: italic;
- }

src/header/header.jsx

import React from "react";
import styles from "./header.scss";

export const Header = () => {
  return <header className={styles.header}>TSC (Poc)</header>;
};

src/header/header.css

.header {
  padding: 16px;
  background-color: #0063be;
  color: #fff;
}

src/title/title.jsx

import React from "react";
import styles from "./title.scss";

export const Title = () => {
  return <h1 className={styles.title}>Hello world...</h1>;
};

src/title/title.css

.title {
  text-align: center;
  color: #3c3c3c;
  font-style: italic;
}

Building themes

Warhol-Marilyn
Warhol Marilyn

But now imagine that you want to have different themes for your site depending on brand, products or just the user choice: you need your Web page to use different colours palette.

Now you need to add in Webpack the sass-loader plugin and import the variables file which you’ll use for all the CSS Modules components:

data: '@import "variables";'

webpack.config.js

loader: "sass-loader", options: {
  modules: true,
  sourceMap: true,
  data: '@import "variables";',
  includePaths: [
    path.join(__dirname, 'src')
  ]
}

src/_variables.scss

$white: #ffffff;
$gray-base: #080808;
$gray-darker: #3c3c3c;
$gray-dark: #808080;
$gray: #cfcfcf;
$gray-light: #e3e3e3;
$gray-lighter: #e9ebee;
$brand-primary: #ec008c; // Theme color
$padding-base: 16px;

src/header/header.scss

.header {
  padding: $padding-base;
  background-color: $brand-primary;
  color: $white;
}

src/title/title.scss

.title {
  text-align: center;
  color: $gray-darker;
  font-style: italic;
}

A gift:
Looking for info for this article, I found this awesome project that helps to build faster all the style sheets you define: parallel-webpack

webpack.config.js

const createVariants = require('parallel-webpack').createVariants;

const variants = {
  themes: ['pink', 'blue', 'green']
};

function createConfig(options) {
  return {
    plugins: [
    ...
    new MiniCssExtractPlugin({
      filename: `${options.themes}-theme.css`})

    module: {
      loader: "sass-loader", options: {
      ...
      data: `@import "${options.themes}";`
    }
  }
}

module.exports = createVariants(variants, createConfig);

In this example, we are building three themes and we have to use the names of the main variables files we have in the project to build each version of the CSS file.

But if you want to forget about modify the configuration for every new style sheets you’ll add, just add a bit of code to automate it:

webpack.config.js

function getThemes() {
  const files = fs.readdirSync("./src/themes/");

  return files.map((fileName) => fileName.replace(/(^_|.scss$)/g, ""));
}

const variants = {
  themes: getThemes(),
};

Boost your style(s)!

Now it’s time to push your Web to the next level. Adding one of the most common CSS frameworks, you’ll build easily pre-styled components that, magically, will adapt his primary, secondary, etc. colours to the selected template.
But not everything it’s pink…, at the moment of writing this article, the glyphicons bootstrap import causes the failure of the build, so you should at least comment this line. src/vendors/custom-bootstrap.scss

//Core variables and mixins
@import "~bootstrap-sass/assets/stylesheets/bootstrap/variables";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/mixins";

//Reset and dependencies
@import "~bootstrap-sass/assets/stylesheets/bootstrap/normalize";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/print";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/glyphicons";

//Core CSS
@import "~bootstrap-sass/assets/stylesheets/bootstrap/scaffolding";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/type";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/code";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/grid";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/tables";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/forms";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/buttons";

//Components
@import "~bootstrap-sass/assets/stylesheets/bootstrap/component-animations";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/dropdowns";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/button-groups";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/input-groups";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/navs";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/navbar";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/breadcrumbs";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/pagination";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/pager";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/labels";
...



src/mainLayout.scss

html {
  :global {
    @import "./vendors/custom-bootstrap.scss";
  }
}

* {
  font-family: "Open Sans", sans-serif;
}

header {
  font-size: medium;
}



index.html

- <button onclick="changeCss('pink')">Pink</button>
- <button onclick="changeCss('blue')">Blue</button>
- <button onclick="changeCss('green')">Green</button>

+ <div class="btn-group" role="group">
+     <button type="button" class="btn btn-default" onclick="changeCss('pink')">Pink</button>
+     <button type="button" class="btn btn-default" onclick="changeCss('blue')">Blue</button>
+     <button type="button" class="btn btn-default" onclick="changeCss('green')">Green</button>
+ </div>



title.jsx

- return (
-   <h1 className={styles.title}>Hello world...</h1>
- );
+ return (
+   <div className="panel panel-primary">
+     <div className="panel-heading">
+       <h3 className={`panel-title ${styles.title}`}>Hello world...</h3>
+     </div>
+     <div className="panel-body">Lorem ipsum dolor sit amet...</div>
+   </div>
+ );

Conclusion

Your perfect cocktail, shaken, not stirred…

shaken, not stirred
shaken, not stirred...

Pros Cons
- Isolate components (JS & CSS) - Needs a specific Webpack conf to build
- Don’t need specific Sass imports - Needs same configuration in different projects to have really shareable components
- Obfuscated CSS class names - Needs a JS template builder
- Works with any JS template builder

Thanks

To all the people who shared their knowledge and experiments to help and inspire the whole community.

My helpful references have been:
Keep a Changelog
Semantic Versioning
Muffin Man
webpack.js.org
Styled Components
CSS modules React App
Add SASS in React App
Webpack sass loader global variables file
Sass loader for webpack
Trivago parallel-webpack


https://github.com/lastminutedotcom/themed-css-modules


Read next

A Monorepo Experiment: reuniting a JVM-based codebase

A Monorepo Experiment: reuniting a JVM-based codebase

luigi_noto
luigi noto

Continuing the Monorepo exploration series, we’ll see in action a real-life example of a monorepo for JVM-based languages, implemented with Maven, that runs in continuous integration. The experiment of reuniting a codebase of ~700K lines of code from many projects and shared libraries, into a single repository. [...]

Exploring the Monorepo

Exploring the Monorepo

luigi_noto
luigi noto

Monorepo has become quite popular in Javascript world, and yet not fully explored for JVM-based development. The lack of tools and practical examples slow down its adoption in everyday software projects. In this series of articles, we’ll try to uncover the full potential of a monorepo with Java and Maven. [...]