Autoloading Grunt tasks concurrently with optimized assets
Currently, I have more to deal with front-end stuff, because of creating a responsive web layout for a popular shop. We use a Grunt build process and the template engine Handlebarsjs to create a style guide and the several pages. On Linux is the build performance ok, but on Windows it's slow. In this post I will share my experience how making the build process faster with autoloading of Grunt tasks and to parallelize as much as possible. I show you how to build a fully fledged front-end build process with optimized assets and how to ensure the code quality. The source code can be downloaded on GitHub. This is also a small tutorial how you can build a new web layout without changing existing code and about front-end design patterns. This could be useful for legacy applications.
Grunt dependencies
Besides the general Grunt plugins (see package.json for full list) we focus to grunt-assemble
, grunt-uncss
, jit-grunt
, grunt-newer
, grunt-concurrent
, grunt-combine-media-queries
, grunt-processhtml
, grunt-autoprefixer
, grunt-jscs
, grunt-contrib-jshint
, grunt-htmlhint
and grunt-contrib-csslint
.
grunt-assemble: Compiles the Handlebarsjs templates to HTML files. So you can split your templates in several partails which can reflect your PHP template structure.
grunt-uncss: Removes not used CSS. This can be useful if you use a CSS framework but be aware, you have to set your CSS classes which are set from JS to the ignore list. This can be hard to maintain.
jit-grunt: If you have many plugins like in this example, the Grunt loading task is slow. The JIT(Just In Time) plugin loader for Grunt solves that problem. Only current used plugins are loaded, so yes you have autoloading here.
grunt-newer: This plugin is useful for your assets, because only Grunt tasks are executed which source files modified since the last successful run.
grunt-concurrent : You can drastically improve your build process time, if you run tasks in parallel. This plugin comes to the rescue.
grunt-combine-mq: Combines the media queries of severall CSS classes to one media query. So you can write your media queries directly to the CSS class to keep the overview.
grunt-processhtml: This is useful if you want to test how your application performs with the minified files. This plugin replaces every single file in the templates with the minified version.
grunt-autoprefixer: Don't write the CSS vendor prefixes manually. This plugin does the job better than you.
grunt-jscs: JSCS is a JavaScript Code Style checker and ensures uniform code.
grunt-contrib-jshint: JSHint is a program that flags suspicious usage in JS scripts. There is also an online version available.
grunt-htmlhint: HTMLHint is a static code analysis tool for HTML and ensures uniform code. There is also an online version available.
grunt-contrib-csslint: Finds wrong CSS code and gives recommendations. There is also an online version (Will hurt your feelings) available.
Bower dependencies
For this example I use Twitter Bootstrap SASS and some jQuery plugins. The resolutions definition is useful if you have dependency version conflicts. If some componenent want jQuery 1.10.0 and you have jQuery 1.11.3, then define it here.
I don't use range operators to pin a specific version of the component, because there is no lock file like Composer has. The goal is, that every developer gets the same versions.
"dependencies": {
"bootstrap-sass-official": "3.3.5",
"FitVids": "1.1.0",
"jquery": "1.11.3",
"scrollup": "2.4.1"
},
"resolutions": {
"jquery": "1.11.3"
}
Optimize build process
For a full list of the Grunt tasks please see gruntfile.js. It's necessary that you know which tasks depends on each other. For example: you can't minify your CSS files until the SASS task was executed before. Let's take a look at the concurrent config. The build settings compiles the templates, the CSS sprites and concatenates files in parallel. There is no collision between these tasks. The optimize settings minifies JS and removes useless CSS. The server settings rewrites the HTML files, so that the minified versions are used and minifies the CSS. The dist settings creates the CSS files and optimizes images and svg files. The qa settings checks the code style for HTML, CSS and JS also in parallel.
module.exports = function( grunt ) {
// ...
grunt.initConfig( {
// ...
// Run some tasks in parallel to speed up build process
concurrent: {
build: [
'assemble',
'sprite',
'concat:app',
],
optimize: [
'uglify:app',
'uncss'
],
server: [
'processhtml:optimize',
'cssmin:optimize'
],
dist: [
'sass:dist',
'imagemin',
'svgmin'
],
qa: [
'htmlhint',
'jscs',
'jshint',
'csslint'
]
}
} );
};
The order of these settings are independent. Important are how the Grunt tasks use them. Let's have a look at the main Grunt tasks. The build task builds the complete project from scratch. Every task uses the build task to ensure the latest version of the files. The optimize task minifies JS and CSS and rewrites HTML pages for production usage. The qa task checks code violations. These tasks use several concurrent config settings from above to run the tasks in parallel. The serve task has a special target dist. If this is used, you get the optimized version and the server uses gzip compression for the files. This is great because you can simulate live conditions.
module.exports = function( grunt ) {
// Time how long tasks take. Can help when optimizing build times
require( 'time-grunt' )( grunt );
// Load grunt tasks automatically just in time
require( 'jit-grunt' )( grunt, {
sprite: 'grunt-spritesmith',
cmq: 'grunt-combine-media-queries'
} );
// ...
grunt.registerTask( 'serve',
'start the server and preview your src, --allow-remote for remote access',
function( target ) {
if ( grunt.option( 'allow-remote' ) ) {
grunt.config.set( 'connect.options.hostname', '0.0.0.0' );
}
if ( target === 'dist' ) {
return grunt.task.run( [ 'optimize', 'connect:dist:keepalive' ] );
}
grunt.task.run( [
'build',
'connect:livereload',
'watch'
] );
} );
// build project
grunt.registerTask( 'build', [
'clean:server',
'concurrent:build',
'concurrent:dist',
'cssmin:dist',
'autoprefixer',
'copy'
] );
// minify js and css and rewrite page for production usage
grunt.registerTask( 'optimize', [
'build',
'concurrent:optimize',
'cmq',
'concurrent:server'
] );
// check code style violations with Grunt
grunt.registerTask( 'qa', 'check code style for violations', function( target ) {
grunt.task.run( [
'build',
'concurrent:qa'
] );
} );
};
Code quality assurance tools
It's recommended to configure the QA tools before you start coding. Otherwise it's a lot of work to fix the code. You should use a standard code style like jQuery. For JSHint settings see .jshintrc, for JSCS see .jscsrc, for HTMLHint see .htmlhintrc and for CSSLint see .csslintrc file in the root of the repository. It's important to use the value 2 for a setting of the CSSLint configuration. This value means that the process exists with an error and the build is broken. An explanation of the JS errors has jslinterrors.com, for HTMLHint errors see HTMLHint rules and for CSSLint errors see CSSLint rules.
Note that some settings may be stricter in your project. Here it's only an example. I recommend to use the defaults and only disable rules if it is necessary and you know what you do.
Git pre-commit hook
To ensure the quality of the code, you can use the following Git pre-commit hook. If an error occurs, you are not able to commit the files. This is very useful. It is also possible to configure your IDE to check the code style depending on your configuration and you can also use a Docker container for that.
#!/usr/bin/env bash
# Git pre-commit hook to confirm code style
# Install under: .git/hooks/pre-commit (and ensure it is executable)
PROJECTROOT=`echo $(cd ${0%/*}/../../ && pwd -P)`/
cd $PROJECTROOT
grunt --no-color qa
Templates and processhtml instruction
The layout.hbs
file looks very easy. With processhtml and the Grunt optimize task you get optimized pages to simulate live conditions. You must only define a HTML comment with a build instruction build:css:optimize.
<!doctype html>
<html class="no-js">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0"/>
<meta name="HandheldFriendly" content="True"/>
<title>Grunt Example</title>
<link rel="shortcut icon" href="/favicon.ico"/>
<!-- build:css:optimize /css/main.min.css -->
<link rel="stylesheet" href="/css/main.css"/>
<!-- /build -->
</head>
<body>
{{> header }}
<div class="container">
{{> body }}
</div>
{{> footer }}
{{> body-script }}
</body>
</html>
The body-script.hbs
file contains the several JS files and the processhtml instruction build:js:optimize to use the one file minified version.
<!-- build:js:optimize /js/main.min.js -->
<!--
IF YOU ADD ONE FILE HERE YOU MUST ADD THIS FILE TO GRUNT TASK concat:app TOO and maintain order !!!
-->
<script src="/bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/dropdown.js"></script>
<script src="/bower_components/FitVids/jquery.fitvids.js"></script>
<script src="/bower_components/scrollup/src/jquery.scrollUp.js"></script>
<script src="/js/main.js"></script>
<!-- /build -->
Pages, partials and style guide
We’re not designing pages, we’re designing systems of components says Stephen Hay. So it's time to introduce Atomic Web Design. This is really cool, because you build design modules which can be combined. If you not sure which CSS naming standard you should use, I recommend Named Cascading Style Sheets - NCSS. Ok, let's go back to the topic. Independent of this, Twitter Bootstrap has also components. Partials helps you to reuse components on other sites and the style guide helps to keep the overview of your layout. It's also great to see how some component is implemented and which kind of design modules are available.
Handlebarsjs template drawbacks
If you use this approach to redesign your layout, you have to convert all the Handlebarsjs templates to your favorite PHP template engine. Another approach is PatternLab with Atomic Design. So it depends on your legacy code base, if you can integrate the front-end build process to your project or to use an own project for this.
Docker for Grunt build and serve
I don't want and I don't have the time to install all the front-end tools on my machine, so I use the digitallyseamless/nodejs-bower-grunt Docker image. You can use the following bash script docker-grunt
to run grunt, node and npm.
#!/usr/bin/env bash
docker run -i -t=false -p 35729:35729 -p 9000:9000 --rm -v $(pwd)/:/data digitallyseamless/nodejs-bower-grunt $@
Then you can run the commands from the root of the repository. Is this easy, isn't it? Thank you Docker.
$: docker-grunt npm install
$: docker-grunt bower --allow-root install
$: docker-grunt grunt qa
$: docker-grunt grunt serve --allow-remote
$: docker-grunt grunt serve:dist --allow-remote
Note that serve:dist uses the minified versions. Another hint is to use Google Chrome Developer Toolbar to check several resolutions and to emulate mobile connections e.g. 2G. You can browse the pages under http://localhost:9000/page/
.
Conclusion
You have learned to make your front-end build process faster and which essential components are needed to achieve this goal. You ensure the high quality of your code and keep the assets as small as possible. You have a style guide of your layout, to make it easier for other developers how to use it. Last but not least, you also see that Docker helps you to develop faster, because you don't waste your time with the installation of tools.
I hope this post helps you to push your front-end build process to a higher level. If you have any suggestions or recommendations or something else, please leave a comment.