Building CSS from authoring style sheets

A build system of some sort is required to compile the authoring style sheets into plain CSS.

Tip

There are many tools available to perform this task e.g Grunt, Gulp, and Brocolli to name just a few. However, just as there is no universally right CSS processor, or CSS methodology, so there is no universally right build tool.

Besides merely compiling authoring style sheets into CSS, good tooling can provide further benefits.

  • Linting: To enable code conformity and prevent non-working code reaching deployment
  • Aggressive minification: Rebasing z-indexes, converting length values to smaller length values e.g. (while 1pt is equivalent to 16px it is one less character), merging alike selectors
  • Autoprefixer: To enable fast and accurate vendor prefixing and prevent vendor prefixes being present in the authoring style sheets

Tip

For considerations of what is deemed essential, syntax-wise, in style sheet authoring, refer to Chapter 8The Ten Commandments of Sane Style Sheets.

Save to compile, the journey of an ECSS style sheet

In terms of tooling, at the time of writing, I currently write ECSS with the help of Gulp and PostCSS plus its many and varied plugins. It's a process that has worked well so I'll document it briefly here.

Note

For the very curious, a little more on my journey from Sass to PostCSS can be found here (https://benfrain.com/breaking-up-with-sass-postcss/).

The style sheet authors write into a partial CSS file (with a *.css file extension), using a syntax that is very similar to Sass.

On saving an authoring style sheet, the Gulp watch task notices the file change and first runs the linting task. Then, providing all is well, it compiles the partial authoring style sheets to a CSS file, then auto-prefixes that CSS file and finally BrowserSync injects the changed CSS directly into the webpage I'm working on. Typically, a source map file is also created as some authors find working with source maps in the developer tools easier for debugging. All this happens before I can Alt + Tab into my browser window or even move my gaze from text editor to browser window.

Here's an example gulpfile.js that demonstrate how PostCSS might be setup in a Gulp based build tool:

//PostCSS related
var postcss = require("gulp-postcss");
var postcssImport = require("postcss-import");
var autoprefixer = require("autoprefixer");
var simpleVars = require("postcss-simple-vars");
var mixins = require("postcss-mixins");
var cssnano = require("cssnano");
var reporter = require("postcss-reporter");
var stylelint = require("stylelint");
var stylelinterConfig = require("./stylelintConfig.js");
var colorFunction = require("postcss-color-function");
var nested = require("postcss-nested");
var sourcemaps = require("gulp-sourcemaps");

// Create the styles
gulp.task("styles", ["lint-styles"], function () {

    var processors = [
        postcssImport({glob: true}),
        mixins,
        simpleVars,
        colorFunction(),
        nested,
        autoprefixer({ browsers: ["last 2 version", "safari 5", "opera 12.1", "ios 6", "android 2.3"] }),
        cssnano
    ];

    return gulp.src("preCSS/styles.css")

    // start Sourcemaps
    .pipe(sourcemaps.init())

    // We always want PostCSS to run
    .pipe(postcss(processors).on( class="st">"error", gutil.log))

    // Write a source map into the CSS at this point
    .pipe(sourcemaps.write())

    // Set the destination for the CSS file
    .pipe(gulp.dest("./build"))

    // If in DEV environment, notify user that styles have been compiled
    .pipe(notify("Yo Mofo, check dem styles!!!"))

    // If in DEV environment, reload the browser
    .pipe(reload({stream: true}));
});

With Gulp the build choices are fairly limitless, this is merely an illustration. However, note how the the first thing the styles task does, is run the lint-styles task.

As mentioned in previous chapters, the linting of style sheets is a very important step on a project where multiple style sheets authors are involved. Let's look a little more at that next.

Stylelint

Stylelint is a node based linting tool for the static analysis of style sheets. In layman's terms it will analyse your style sheets for the things you specifically care about and warn you of any problems.

Tip

If you use Sass you should check out scss-lint (https://github.com/brigade/scss-lint) which provides similar functionality for Sass files.

The linting job fails the build if any authoring errors are found. Typically it's most beneficial to have linting running in two places. In the text editor (e.g. Sublime) and in the build tool (e.g. Gulp). This way, if an author has the requisite text editor then the editor based linting (https://github.com/kungfusheep/SublimeLinter-contrib-stylelint) indicates problems before an author even clicks save.

Even if a user doesn't have in-editor linting available, the linting job runs via Gulp on save. The build step prevents compiled code making its way to production (as continuous integration software would also fail the build).

This is a massive time saver and has proved invaluable when it comes to peer-reviewing code and performing quality assurance tests.

Here is an example .stylelintrc configuration for Stylelint (this is for v5 of Stylelint so future/previous versions may vary slightly):

{
    "rules": {
        "color-hex-case": "lower",
        "color-hex-length": "long",
        "color-named": "never",
        "color-no-invalid-hex": true,
        "font-family-name-quotes": "always-where-
        required",
        "font-weight-notation": "numeric",
        "function-comma-newline-before": "never-multi-
        line",
        "function-comma-newline-after": "never-multi-
        line",
        "function-comma-space-after": "always",
        "function-comma-space-before": "never",
        "function-linear-gradient-no-nonstandard-
        direction": true,
        "function-max-empty-lines": 0,
        "function-name-case": "lower",
        "function-parentheses-space-inside": "never",
        "function-url-data-uris": "never",
        "function-url-quotes": "always",
        "function-whitespace-after": "always",
        "number-leading-zero": "never",
        "number-no-trailing-zeros": true,
        "string-no-newline": true,
        "string-quotes": "double",
        "length-zero-no-unit": true,
        "unit-case": "lower",
        "unit-no-unknown": true,
        "value-keyword-case": "lower",
        "value-no-vendor-prefix": true,
        "value-list-comma-space-after": "always",
        "value-list-comma-space-before": "never",
        "shorthand-property-no-redundant-values": true,
        "property-case": "lower",
        "property-no-unknown": true,
        "property-no-vendor-prefix": true,
        "declaration-bang-space-before": "always",
        "declaration-bang-space-after": "never",
        "declaration-colon-space-after": "always",
        "declaration-colon-space-before": "never",
        "declaration-empty-line-before": "never",
        "declaration-block-no-duplicate-properties": true,
        "declaration-block-no-ignored-properties": true,
        "declaration-block-no-shorthand-property-
        overrides": true,
        "declaration-block-semicolon-newline-after":
        "always",
        "declaration-block-semicolon-newline-before":
        "never-multi-line",
        "declaration-block-single-line-max-declarations":
        1,
        "declaration-block-trailing-semicolon": "always",
        "block-closing-brace-empty-line-before": "never",
        "block-no-empty": true,
        "block-no-single-line": true,
        "block-opening-brace-newline-after": "always",
        "block-opening-brace-space-before": "always",
        "selector-attribute-brackets-space-inside":
        "never",
        "selector-attribute-operator-space-after":
        "never",
        "selector-attribute-operator-space-before":
        "never",
        "selector-attribute-quotes": "always",
        "selector-class-pattern": ["^[a-z]([a-z0-9]){1,3}-[A-Z][a-zA-Z0-9]+(_[A-Z][a-zA-Z0-9]+)?(-([a-z0-9-]+)?[a-z0-9])?$", { "resolveNestedSelectors": true }],
        "selector-combinator-space-after": "always",
        "selector-combinator-space-before": "always",
        "selector-max-compound-selectors": 3,
        "selector-max-specificity": "0,3,0",
        "selector-no-id": true,
        "selector-no-qualifying-type": true,
        "selector-no-type": true,
        "selector-no-universal": true,
        "selector-no-vendor-prefix": true,
        "selector-pseudo-class-case": "lower",
        "selector-pseudo-class-no-unknown": true,
        "selector-pseudo-class-parentheses-space-inside":
        "never",
        "selector-pseudo-element-case": "lower",
        "selector-pseudo-element-colon-notation":
        "single",
        "selector-pseudo-element-no-unknown": true,
        "selector-max-empty-lines": 0,
        "selector-list-comma-newline-after": "always",
        "selector-list-comma-newline-before": "never-
        multi-line",
        "selector-list-comma-space-before": "never",
        "rule-nested-empty-line-before": "never",
        "media-feature-colon-space-after": "always",
        "media-feature-colon-space-before": "never",
        "media-feature-name-case": "lower",
        "media-feature-name-no-vendor-prefix": true,
        "media-feature-no-missing-punctuation": true,
        "media-feature-parentheses-space-inside": "never",
        "media-feature-range-operator-space-after":
        "always",
        "media-feature-range-operator-space-before": "always",
        "at-rule-no-unknown": [true, {"ignoreAtRules": ["mixin"]}],
        "at-rule-no-vendor-prefix": true,
        "at-rule-semicolon-newline-after": "always",
        "at-rule-name-space-after": "always",
        "stylelint-disable-reason": "always-before",
        "comment-no-empty": true,
        "indentation": 4,
        "max-empty-lines": 1,
        "no-duplicate-selectors": true,
        "no-empty-source": true,
        "no-eol-whitespace": true,
        "no-extra-semicolons": true,
        "no-indistinguishable-colors": [true, {
            "threshold": 1,
            "whitelist": [ [ "#333333", "#303030" ] ]
        }],
        "no-invalid-double-slash-comments": true
    }
}

This is just an example, you can set whichever rules you care about from the ever expanding list (http://stylelint.io/user-guide/rules/). If using these sort of tools for the first time, you might also find it useful to download/clone ecss-postcss-shell (https://github.com/benfrain/ecss-postcss-shell). It's a basic Gulp setup to run the authored style sheets through PostCSS and lints the styles with Stylelint.

Note

I've even contributed a little code to the Stylelint project, helping to add a rule called selector-max-specificity for controlling the maximum level of selector specificity any selector can have. If you are involved with controlling a CSS codebase, it's a great project to get involved in.

If that wasn't enough, Stylelint is extensible. It's easy to add additional functionality. For current builds ECSS projects in my workplace we have additional Stylelint rules to:

  • Ensure only overrides and media queries can be nested (prevents nests that don't use a parent (&) selector)
  • Ensure that key selectors match ECSS naming conventions (Stylelint now has a selector-class-pattern rule to help with this)
  • Prevent key selectors from being compound (e.g. .ip-Selector.ip-Selector2 {})
  • Ensure key selectors are singular (e.g. .ip-Thing not .a-Parent .ip-Thing {})

These offer bespoke quality assurance that would be time consuming and error prone to perform by hand.

In case I'm not making it clear I want you to know that I love Stylelint and think linting is an indispensable piece of tooling for large CSS projects with multiple authors. I simply cannot recommend it highly enough.

Note

There's a little more about Stylelint in this blog post (https://benfrain.com/floss-your-style-sheets-with-stylelint/) or via the official Stylelint (http://stylelint.io/) website.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.163.13