Using tomorrow’s CSS with Aurelia (typescript) and PostCSS

Aurelia is a fantastic framework to write tomorrow's JavaScript code for modern browsers. Using Aurelia together with SASS works excellent, but what if we could just write modern JavaScript and modern CSS?

In this blog post, I will use a generated project created with the Aurelia-CLI and extend it to support future CSS syntax and CSS modules/ BEM styled class names. I use TypeScript as transpiler, but this can just as good be done with Babel and ES2015.

What is PostCSS

PostCSS is a small and fast JavaScript library developed by the author of autoprefixer for processing and/ or transforming style sheets. It's not by definition a replacement for pre-processors like LESS and SASS, because it can even be used together with LESS and SASS for pre-processing files. You can use it as pre-processor or post-processor (transform css live in the browser).

How fast is it? Very quickly, as can be seen in the benchmark stats below:

PostCSS:   40 ms  
libsass:   77 ms  (1.9 times slower)  
Rework:    87 ms  (2.2 times slower)  
Less:      159 ms (4.0 times slower)  
Stylus:    224 ms (5.7 times slower)  
Stylecow:  232 ms (5.9 times slower)  
Ruby Sass: 872 ms (22.0 times slower)

source: https://github.com/postcss/benchmark

In this article we will use PostCSS with some plugins to archive the same (and more) as you could archive with SASS, so we don't need to depend upon SASS.

This blog post requires you have node.js setup on your machine.

Setting up our project

If you don't have the Aurelia-CLI installed, install it by performing in your console:

npm i aurelia-cli -g  

You should now be able to use the au command. Let's use the Aurelia-ClI to generate our project structure.

au new [name-of-your-project]  

replace [name-of-your-project] by the name of your project

Answer the questions as following:

  • Would you like to use the default setup or customize your choices?
    • 3 (custom)
  • What transpiler would you like to use?
    • 2 (typescript)
  • What CSS processor would you like to use?
    • 5 (PostCSS)
  • Would you like to configure unit testing?
    • 1 (yes, we want unit testing!)
  • What is your default code editor?
    • 1 (visual studio code)
  • Would you like to create this project?
    • 1 (yes)
  • Would you like to install the project dependencies?
    • 1 (yes)

Checking if the project boilerplate works

We now have a default Aurelia application, move to the folder it's created in cd [name-of-your-project] and check to see if it works by executing the command:

au run  

This will compile and run your project and serve it on http://localhost:9000/. Open it to see if it loads correctly.

You can also use au run --watch to enable browser sync and auto refresh on changes (but sometimes you still need to restart it, for example when modifying the build tasks).

Your project folder will now contain a folder named aurelia-project this folder contains all the scripts that belong to the Aurelia-CLI and will the files we will modify next.

CSSNext

CSSNext is a collection of plugins combined as one plugin for PostCSS that allows you to write feature CSS and transpile it back to today's CSS.

For example, it will support W3C's CSS Custom Properties for Cascading Variables Module Level 1. No need to read the spec, it allows you to do the following:

:root {
  --mainColor: red;
}

a {  
  color: var(--mainColor);
}

Which does the same as the SCSS code below

$mainColor: red;

a {  
  color: $mainColor;
}

You might not like this, but this is how W3C defined variables to work :-)

Adding the CSSNext plug-in to PostCSS

Install CSSNext by performing the following command:

npm i postcss-cssnext --save-dev  

Open up the file aurelia_project\tasks\process-css.ts to add CSSNext as PostCSS plugin.

Add the following below the other import statements

import * as cssnext from 'postcss-cssnext';  

and remove

import * as autoprefixer from 'autoprefixer';  

then modify

let processors = [  
  autoprefixer({browsers: ['last 1 version']})
];

to match

let processors = [  
  cssnext({ browsers: ['last 1 version'] })
];

The autoprefixer plugin is included in CSSNext, so it can be removed/ won't have to run twice. This plug-in will add all the vendor specific things, so you will never need to worry about those anymore.

What's different now?

You should now be able to add a CSS file called app.css to your src folder and insert the following content:

:root {
  --mainColor: red;
}

.header {  
  color: var(--mainColor);
}

And change app.html to:

<template>  
  <require from="app.css"></require>
  <h1 class="header">${message}</h1>
</template>  

Start aurelia again by performing:

au run  

It will show a red h1, with "Hello, world!" now. If you open up the file scripts\app-bundle.js (your application code bundle) and scroll down you will see it transpiled the css:

define('text!app.css', ['module'],  
function(module) {  
  module.exports = ".header {  \r\n  color: red;\r\n}"; });

Everything works!

CSS Modules

CSS Modules allow you to create scoped CSS and more, just like you do while using BEM styled class names. Glen Maddern wrote a great blog post about it, read it.

Using CSS modules is also possible by using the plugin called aurelia-binding-loader, see the description there on how this works. In this article i will use pre-processing, because I didn't like the performance, it took < 1 sec to transform the CSS, but still showed the page without it's styles for less then a second.

Enable transforming to BEM like class names

To enable CSS modules, we will install a PostCSS plugin called postcss-modules by performing the following command:

npm i postcss-modules --save-dev  

Open up the file aurelia_project\tasks\process-css.ts again and add:

import * as postcssmodules from 'postcss-modules';  

and change the processors array to look like:

let processors = [  
  cssnext({ browsers: ['last 1 version'] }),
  postcssmodules({
      generateScopedName: '[name]__[local]',
  })
];

That's it, we've setup our project to use CSS module names. I've used a custom generateScopedName and removed the hash from it, looks nicer and don't expect to have equal names.

If you want the hash in there, just remove this line.
If you want your class names to be different, modify it or check the docs :-)

If we run au build now and open up the generated bundle file scripts\app-bundle.js we can now see it modified the class name to .app__header.

We can now modify our HTML class to match app_header or we can make it even easier:.

As you can see during the build, a file (src\app.css.json) was generated in your source folder. This file contains the changed class names as JSON object, which we could use to update our HTML files.

There is a plugin to do this named posthtml-css-modules, but I didn't want to move from the class attribute to the css-module attribute and had some issues with getting this to work properly.

Custom logic to replace class names with BEM variant

So we are going to write some custom logic, to replace the names in our HTML files using the generated JSON.

First, we need to change the build to run the processCSS and processMarkup tasks sequential. We can do this by opening up the file aurelia_project\tasks\build.ts and amend the tasks to [processCSS, processMarkup].

Secondly we need to install two more npm packages:

npm i gulp-each cheerio --save-dev  

The gulp-each package allows us to walk over each file and cheerio is a easy to use HTML parser.

Open up the file aurelia_project\tasks\process-markup.ts and add the following import statements:

import * as each from 'gulp-each';  
import * as cheerio from 'cheerio';  

Next add the following between the 2 pipe statements (above .pipe(build.bundle()); if there are more)

.pipe(each((content, file, callback) => {

    let filename = file.history[0], 
        cssJsonPath = filename.replace(".html", ".css.json"),
        cssModules = require(cssJsonPath),
        $ = cheerio.load(content);

    $("*[class]").each((i, elem) => {
      let classList = $(elem).attr("class"),
          classListArray = classList.split(' '),
          changed = false;

      for(let idx = 0; idx < classListArray.length; idx++) {
        let classItem = classListArray[idx];
        if (cssModules[classItem] !== undefined) {
          classListArray[idx] = cssModules[classItem];
          changed = true;
        }
      }

      if (changed) {
        $(elem).attr("class", classListArray.join(' '));
      }
    });
    callback(null, $.html());
}))

This will get the HTML file path src/app.html and with that figure out the CSS JSON spec and load that src/app.css.json. Next it will get all the elements that contain class and replace the class names with there CSS module class name.

So if you look at your app bundle, this time at the define('text!app.html' line, you will see it now looks like this:

"<template>\n  <require from=\"app.css\"></require>\n  <h1 class=\"app__header\">${message}</h1>\n</template>\n"

Wonderful!

Adding a custom element with CSS only applied to itself

To see this in action, we will create a custom <card> element.

Modify Aurelia element template

For this, we would first want to extend our Aurelia-cli generator template to include CSS by default, because in most cases we would probably end up with custom CSS.

Open up aurelia_project\generators\element.ts and modify the following:

this.project.elements.add(  
  ProjectItem.text(`${fileName}.ts`, this.generateJSSource(className)),
  ProjectItem.text(`${fileName}.html`, this.generateHTMLSource(className))
);

change it to the code below to generate an empty css file and call the generateHTMLSource method with the parms fileName and className.

this.project.elements.add(  
  ProjectItem.text(`${fileName}.ts`, this.generateJSSource(className)),
  ProjectItem.text(`${fileName}.html`, this.generateHTMLSource(fileName, className)),
  ProjectItem.text(`${fileName}.css`, `\n`)
);

And change the function generateHTMLSource to:

  generateHTMLSource(fileName, className) {
return `<template>  
  <require from="./${fileName}.css"></require>
  <h1>\${value}</h1>
</template>`  
  }

Save the file.

Creating the card custom element

Execute the following command in your console:

au generate element card  

This will generate 3 files (html/ts/CSS/) in the folder src/resources/elements.

At the moment of writing this blog post the Aurelia-CLI is still under development it might be the next step is not required anymore today/ for you.

Open up the file src\resources\index.ts and change the commented out config.globalResources line to:

 config.globalResources(["./elements/card"]);

Setup is done, now it is tome to add our custom <card> element to the src/app.html page.

<card value="hi from card"></card>  

If you execute au run once more and check the url http://localhost:9000/ it will display the text 'hi from card' in black.

We will now add custom css. Open up the file src/resources/elements/card.html and change the content to:

<template>  
  <require from="./card.css"></require>
  <h1 class="header">${value}</h1>
</template>  

Next open up card.css in the same folder and add:

.header {
  color: purple;
}

Execute the app once more

au run  

Check the source code of the html page on http://localhost:9000/ :

<body aurelia-app="main">

  <h1 class="app__header">Hello World!</h1>
  <card value="hi from card" class="au-target" au-target-id="3">

  <h1 class="card__header">Erik</h1>
</card>  
</body>  

It now contains one class card__header and one app__header still your source html/CSS still have just header as class.

What can we now do in our CSS?

There are much more plugins that can be added to PostCSS, below I will give some examples of things that can be done with the current setup you have.

Nesting

Just like in LESS/ SASS you can nest your css code.

a {  
  & span {
    color: white;
  }
}

Composes

Composes makes reusing your logic smarter. If we have the following CSS:

.underline {
    text-decoration: underline;
}

.header {
  color: purple;
  composes: underline;
}
.subheader {
    color: cornflowerblue;
    composes: underline;
}

and the following markup:

<template>  
  <require from="./card.css"></require>
  <h1 class="header">${value}</h1>
  <h2 class="subheader">${value}</h2>
</template>  

We will end up with this:

<card value="hi from card" class="au-target" au-target-id="4">  
  <h1 class="card__header card__underline">hi from card</h1>
  <h2 class="card__subheader card__underline">hi from card</h2>
</card>  

As you can see it is smart enough to not extend the css, but just added the card__underline class to the class attribute. This can also be done with class definitions of external files.

Custom selectors & media queries

You are now able to use the following to define reusable media queries

@custom-media --small-viewport (max-width: 30em);
/* check out media queries ranges for a better syntax !*/

@media (--small-viewport) {
  /* styles for small viewport */
}

And create custom selectors

@custom-selector :--enter :hover, :focus;

button:--enter {  
  color:red;
}

And more...

Check out the css next website for a full list of features.

Plug-ins?

Plug-ins for PostCSS can make your work a lot easier and fun, some of them are: