A year ago I discussed advantages and disadvantages of frontend code minification. My conclusion was to apply it until it is not extremely hard to do. This time, I am implementing it in a project that already uses Gulp. I have HTML, JS, CSS and PNG files on the website. Additionally, there are some files that are not directly displayed on any page but a user can download them so they should be included in the final package as well.
What is code minification
A regular website or a web application has one or multiple HTML files which require JavaScript, CSS files and images. Usually, the first two contain a lot of characters which are not necessary but they enlarge these files. Subsequently, a browser has to download more data from the server in multiple files. Such behavior can be avoided by:
- removing unnecessary characters like duplicated whitespaces, redundant line terminators,
- shortening variable, function names,
- multiple JS files can be merged together, the same may be done separately for CSS files.
If you would like to know more about code minification, read code minification - the archaism?
Install htmlmin, useref and uglify Gulp plugins
I am not going to write all scripts by myself but to use existing plugins for Gulp. In case you do not have Gulp in your project, yet, you can add it. This and much more is described in setting up AngularJS with bower and gulp. Obviously, you need only Gulp not AngularJS or bower.
I need to install these plugins.
npm install gulp-htmlmin gulp-uglify gulp-if del gulp-useref gulp-cssnano --save-dev
The above command has to be executed from the command line in the root of the module/project.
These four plugins are downloaded from NPM repository and added as dev dependencies to package.json file:
{
"name": "test project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
...
},
"devDependencies": {
"del": "2.2.2",
"gulp": "3.9.1",
"gulp-cssnano": "2.1.2",
"gulp-htmlmin": "4.0.0",
"gulp-if": "2.0.2"
"gulp-uglify": "3.0.0",
"gulp-useref": "3.1.3"
},
...
}
Create Gulp tasks for minification
Before I start to create specific tasks, I define a few constants in gulpfile.js:
const gulp = require('gulp');
const del = require('del');
const uglify = require('gulp-uglify');
const useref = require('gulp-useref');
const gulpif = require('gulp-if');
const cssnano = require('gulp-cssnano');
const htmlmin = require('gulp-htmlmin');
const webappDir = 'src/main/webapp/';
The minification process will get HTML, JS and CSS files from the project and will create a minified version of the website. The minified version is what I am going to publish on the internet so it has to be autonomous and contain all necessary content. It makes absolute sense to create a separate directory for them in the project. It is usually called dist and this name I am going to use as well. This is the basic concept of minification:
The first task I need is one for cleaning the dist directory. It is defined by the following code in gulpfile.js.
gulp.task('clean', function (cb) {
return del([
webappDir + 'dist/**/*'
], cb);
});
It deletes the whole content of the dist directory so it could be filled in again from scratch.
The next task (useref) processes Java Script files.
gulp.task('useref', ['clean'], function () {
return gulp.src(webappDir + '*.html')
.pipe(useref())
.pipe(gulpif('*.js', uglify()))
.pipe(gulpif('*.css', cssnano()))
.pipe(gulp.dest(webappDir + 'dist'));
});
It depends on the clean task defined above which means that useref must start not earlier than clean finishes. The processing begins from scanning HTML files in the root directory. They are passed through useref, uglify (JS files) and cssnano (CSS files) plugins. Uglification often goes along with minification and its goal is to make the code difficult to read and understand. It can be based on renaming variables and functions.
The useref task takes care of Java Script and CSS code but there is some place for improvement in HTML as well. Redundant whitespaces can be removed by htmlmin plugin. The following task shows how to use it.
gulp.task('htmlmin', ['clean', 'useref'], function () {
return gulp.src(webappDir + 'dist/' + '*.html')
.pipe(htmlmin({collapseWhitespace: true}))
.pipe(gulp.dest(webappDir + 'dist'))
});
This task depends on both: clean and useref tasks and works on already preprocessed HTML files in the dist directory. The result is stored also to the same directory so it is actually an in-place conversion.
Mark HTML code for replacement
If I ran these tasks at this moment, the generated website would not work correctly. Java Script files would not be minified. HTML files would still point to the original JS files but they would not exist in the dist directory so they would be unreachable.
To make it work, I have to edit HTML files and mark JS and CSS dependencies that should be processed by the plugins. In most cases, the effort is limited to the head section. The lines I had to add are marked in green.
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Example</title>
<link rel="shortcut icon" href="img/favicon.ico" type="image/x-icon">
<!-- build:css styles/style.css -->
<link href="bower_components/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.min.css">
<link rel='stylesheet' href='bower_components/jquery-eu-cookie-law-popup/css/jquery-eu-cookie-law-popup.css' />
<link rel="stylesheet" href="styles/main.css">
<!-- endbuild -->
<!-- build:js scripts/scripts.js -->
<script src='bower_components/jquery/dist/jquery.slim.js'></script>
<script src='bower_components/jquery-eu-cookie-law-popup/js/jquery-eu-cookie-law-popup.js'></script>
<script src='scripts/index.js'></script>
<!-- endbuild -->
</head>
These lines (comments) mark sections of the script that should be replaced with the minified version. In this example, four link tags will be replaced with one that points to a single minified CSS file. Three script tags will be replaced with one that points to a single minified JS file.
I can execute the htmlmin task from the command line:
D:\projects\sample\main-page>gulp htmlmin
[13:57:16] Using gulpfile D:\projects\sample\main-page\gulpfile.js
[13:57:16] Starting 'clean'...
[13:57:16] Finished 'clean' after 33 ms
[13:57:16] Starting 'useref'...
[13:57:18] Finished 'useref' after 1.92 s
[13:57:18] Starting 'htmlmin'...
[13:57:18] Finished 'htmlmin' after 43 ms
Then I can deploy the dist directory content to Tomcat and it will work - HTML, JS and CSS code will be there except content of two directories: files, img. They are not included in dist because images and other downloadable files are not covered by any of the above tasks.
Tasks to copy other resources
To copy other files I have, I need four more Gulp tasks.
gulp.task('images', ['clean'], function () {
return gulp.src([webappDir + '/img/*'])
.pipe(gulp.dest(webappDir + 'dist/img/'));
});
gulp.task('files', ['clean'], function () {
return gulp.src([webappDir + '/files/*'])
.pipe(gulp.dest(webappDir + 'dist/files/'));
});
gulp.task('fonts', ['clean'], function () {
return gulp.src([webappDir + '/bower_components/font-awesome/fonts/*'])
.pipe(gulp.dest(webappDir + 'dist/fonts/'));
});
gulp.task('build', ['clean', 'files', 'images', 'fonts', 'htmlmin'], function () {
});
They are very simple. The first three copy content of specific directories from sources to dist. The fourth - build just gathers all of them together and makes sure they all are executed. Now, it is enough to run the build task.
D:\projects\sample\main-page>gulp build
[15:12:21] Using gulpfile D:\projects\sample\main-page\gulpfile.js
[15:12:21] Starting 'clean'...
[15:12:21] Finished 'clean' after 47 ms
[15:12:21] Starting 'files'...
[15:12:21] Starting 'images'...
[15:12:21] Starting 'fonts'...
[15:12:21] Starting 'useref'...
[15:12:23] Finished 'files' after 1.88 s
[15:12:23] Finished 'useref' after 1.87 s
[15:12:23] Starting 'htmlmin'...
[15:12:23] Finished 'htmlmin' after 44 ms
[15:12:23] Finished 'images' after 1.98 s
[15:12:23] Finished 'fonts' after 2.02 s
[15:12:23] Starting 'build'...
[15:12:23] Finished 'build' after 123 μs
The dist directory is nicely populated. This is what should be deployed to the server.
When minifying code, it is worth to consider cache-busting which makes sure that frontend files are read again after building new package.