A build system of some sort is required to compile the authoring style sheets into plain CSS.
Besides merely compiling authoring style sheets into CSS, good tooling can provide further benefits.
For considerations of what is deemed essential, syntax-wise, in style sheet authoring, refer to Chapter 8, The Ten Commandments of Sane Style Sheets.
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.
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 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.
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.
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:
&
) selector)selector-class-pattern
rule to help with this).ip-Selector.ip-Selector2 {}
).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.
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.
18.221.163.13