📜 ⬆️ ⬇️

Grunt quick setup for comfortable development

Grunt quick setup for comfortable development

During the development of our service bitcalm.com , we needed to organize an automatic assembly of the project. Our goal was to improve the performance of the frontend-part of our application, as well as to optimize the development and deployment processes on the server.

The main tasks to be solved were:
  1. Combining and minifying scripts
  2. Combining and minifying styles
  3. Compress png-images
  4. Creating sprites from all images (with the possibility of convenient use and with support for two types of sprites for devices with different PPI)
  5. Building different versions of html-documents for development and for production

The first three points look quite trivial, so I will try to focus on working with sprites and processing html.
')


Briefly tell what plugins are used for the first three points. Grunt-contrib-concat and grunt-contrib-uglify are used to optimize the scripts. Working with styles is done with grunt-contrib-cssmin . Image compression is made possible by grunt-contrib-imagemin .

One trick to mention when working with grunt-contrib-concat. The fact is that if you simply specify all the files in the directory ( src: ["/scripts/*.js"] ) in the src parameter, then the files will not necessarily be joined in the order you need. This is often the case if a file of a library follows the file in which this library is used. The easiest and most reliable way out in this case is to specify the script processing order manually.

Here is an example of how we do it:
dist: { src: [ '/scripts/lib/*.js', '/scripts/app/modules/*.js', '/scripts/app/*js' ], dest: '/dist/scripts.min.js' } 


Automatic creation of sprites


To create sprites, we use the grunt-spritesmith plugin . He takes all the images from a specific folder, sticks them together in one sheet and creates a css-file for the resulting sprite. Due to this, any of the source images can be accessed by a specific CSS selector (for convenience, ` .icon- <source_file_name> ` is used).

A small example:

Source files are taken from the raw folder.

 ───img
 │ └──raw
 │ ├──img-1.png
 │ ├──img-2.png
 │ └──img-3.png

After the task is executed, two files appear: directly the spritesheet spritesheet.png itself the spritestyles.css style file .

 ───img
 │ ├──raw
 │ │ ├──img-1.png
 │ │ ├──img-2.png
 │ │ └──img-3.png
 │ │
 │ └───spritesheet.png
 │
 ├───styles
 │ └──spritestyles.css


The spritestyles.css file contains styles for sprite usage.
 .icon-img-1{ background-position: 0px 0px; width: 40px; height: 40px; } .icon-img-2{ background-position: -129px 0px; width: 95px; height: 80px; } .icon-img-3{ background-position: -42px 0px; width: 85px; height: 85px; } 

In addition, the general style for using this sprite is described at the beginning of this file:
 .sprite { display: inline-block; background-image: url(../img/spritesheet.png); background-repeat: no-repeat; } 

As a result, we can use any of our images in the layout with the help of a pair of classes:
 <span class=”sprite icon-img-1”></span> 


Creating sprites for devices with large PPI


In order for our images to look good on devices with high pixel density, you need to create an enlarged version of the sprite. In fact, it would be easy to use only a large version of the sprite for all screens. However, this approach is not entirely correct, because a large sprite is several times "heavier" than usual.

So, how are sprites of different sizes created: images for ordinary devices should be in the img / raw folder, and img / raw @ 2x folders should be enlarged copies of the same images. For screens with a pixel ratio ≥ 2, a separate spritesheet@2x.png and a stylesheet spritestyles@2x.css are created . Thanks to the use of media queries, the browser chooses which sprite it needs to use.

Here is what spritestyles@2x.css looks like :
 @media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (-moz-min-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) { .sprite { display: inline-block; background-image: url(../img/spritesheet@2x.png); background-repeat: no-repeat; } .icon-img-1 { ... } .icon-img-2 { ... } .icon-img-3 { ... } } 


In order for the grunt-spritesmith plugin to perform all of the above, it was necessary to create your own Mustache template or write a JS function that creates CSS. Since there was no time to deal with the first decision, I decided to quickly write a couple of functions and postpone the creation of templates for later.

Code taska
 sprite: { normal: { src: 'img/raw/*.png', destImg: 'img/spritesheet.png', destCSS: 'styles/spritestyles.css', padding: 2, cssTemplate: function (params) { var result = '.sprite {display: inline-block; background-image: url(../img/spritesheet.png); background-repeat: no-repeat;}'; for (var i = 0, ii = params.items.length; i < ii; i += 1) { result += '.icon-' + params.items[i].name + '{' + 'background-position: ' + params.items[i].px.offset_x + ' ' + params.items[i].px.offset_y + ';' + 'width: ' + params.items[i].px.width + ';' + 'height: ' + params.items[i].px.height + ';' + '}\n' } return result; } }, large: { src: 'img/raw@2x/*.png', destImg: 'img/spritesheet@2x.png', destCSS: 'styles/spritestyles@2x.css', padding: 4, cssTemplate: function (params) { var result = '.sprite {display: inline-block; background-image: url(../img/spritesheet@2x.png); background-repeat: no-repeat;}'; result += '@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (-moz-min-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {\n'; for (var i = 0, ii = params.items.length; i < ii; i += 1) { result += '.icon-' + params.items[i].name + '{' + 'background-position: ' + params.items[i].offset_x/2 + 'px ' + params.items[i].offset_y/2 + 'px;' + 'background-size: ' + params.items[i].total_width/2 + 'px ' + params.items[i].total_height/2 + 'px;' + 'width: ' + params.items[i].width/2 + 'px;' + 'height: ' + params.items[i].height/2 + 'px;' + '}\n' } result += '}'; return result; } } } 


As can be seen from the code of this task, the creation of a large sprite differs only by adding a media query, specifying the background-size property, and reducing all the sizes in half.

Having set up such a system, you can absolutely not be distracted by working with images during the layout. It is especially convenient to use this method in combination with the plugin for Photoshop - Retinize It .

Building different versions of html documents


Surely, many people are familiar with the situation when many styles and scripts are connected to the development in the html file, and for production it is necessary to replace them with minified versions. Manually changing the file or reinventing the wheel is a dubious pleasure, so it was decided to call for help from Grunt.

Short searches led to grunt-processhtml . Consider the basic principles of working with this plugin.

The processed html-file is in the root. It is in this file that you need to perform any operations, because it generates an html document in the dist folder.

 templates
 ───dist
 └───app.html
 ───app.html


The app.html file in the dist folder should be loaded into the browser, that is, all server routings should point to it, and not to the app.html file in the root. Setting up this task in grunt is very simple:

 var templates = { cwd: 'templates/', src: ['*.html'], dest: 'templates/dist/', ext: '.html' }; ... processhtml: { dev: { files: [templates] }, dist: { files: [templates] } } 

We only indicate in which folder the source files are located and in which folder the processed files are stored. Also, in the task itself there are dev and dist containers, which allow using different files and settings for different versions of the build. With the help of these containers, conditional constructions inside files are processed: some or other pieces of code are removed, depending on which assembly mode was selected.

 <!DOCTYPE html> <html> <head> <title></title> <!-- build:remove:dev --> <link rel="stylesheet" href="dist/styles.min.css" /> <!-- /build --> <!-- build:remove:dist --> <link rel="stylesheet" href="styles/styles-1.css"/> <link rel="stylesheet" href="styles/styles-2.css"/> <link rel="stylesheet" href="styles/styles-3.css"/> <!-- /build --> </head> <body> <div></div> <!-- build:remove:dev --> <script src="dist/scripts.min.js"></script> <!-- /build --> <!-- build:remove:dist --> <script src="scripts/scripts-1.js"></script> <script src="scripts/scripts-2.js"></script> <script src="scripts/scripts-3.js"></script> <!-- /build --> </body> </html> 

The contents of the build: remove: dev blocks are removed at design time, and the contents of build: remove: dist at build time for production. For ease of development, you can use these aliases:
 grunt.registerTask('dev', ['processhtml:dev']); grunt.registerTask('default', ['processhtml:dist', ...]); 

Now to create a production version, it is enough to execute the grunt command in the console, and for development you should run grunt dev .

The main disadvantage of this method is that after any change in the html file, you must manually run grunt in order to update the final file. The solution to this problem is very simple: use grunt-contrib-watch . This plugin tracks changes to files, and then runs the necessary task (in our case, it's dev ).
 watch: { html: { files: ['templates/*.html'], tasks: ['dev'] } } 


Conclusion


I tried to show how simply and quickly you can start using Grunt on an already running project. This system has accelerated the development and deployment of updates on the server, and also significantly optimized the frontend-part of our service.

This third article from the series is about how we did the cloud backup server bitcalm.com service .

First article: Developing your own Django billing system
Second article: How we made an application framework on AngularJS and Django

Source: https://habr.com/ru/post/244725/


All Articles