Last week I was working on AngularJS project which contains multiple JavaScript files (controllers, services, directives, etc…), good number of HTML partials, and some custom CSS files. This project started to grow and the number of JavaScript files and HTML partials started to increase quickly. I felt it is the right time to start minifing, and concatenating those files, usually I depend on Web Essentials for this kind of tasks, but this time I’ve decided to automate this process and depend on JavaScript Task Runner.
What is a Task Runner?
It is application that is used to automate the repetitive tasks like minification, concatenating, image optimization, etc… so you can forget about those tasks and focus on application development. Usually the task runner takes its instructions from a task file that you define once at the beginning of your project and forget about it. There are different task runners available, but the two common task runners nowadays are Gulp.js and Grunt.js. I find out that configuring Gulpjs is very simple compared to Grunt.js; all you need to do is to write your code in JavaScript file where you define all the tasks needed to be executed by Gulpjs, it is really follows code over configuration approach! I’m not here to compare between both, if you are using Grunt.js for a while and your happy with the results; then there is no need to switch to Gulpjs, both will do the same for you.
Where we’ll use Gulpjs?
Before installing Gulpjs we need to agree on the project we’ll use Gulpjs with, I’ll depend on the previous AngularJS tutorial (you can check the live demo), currently if we look at the application JavaScript references in index.html page as the code snippet below we’ll notice that I’m referencing (three AngularJS services, six controllers, one directive, and one filter) so in total I’m refrencing eleven small JavaScript files and the application is pretty small, you can imagine how many JavaScript files will be referenced for larger project!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<!-- Load app main script --> <script src="app/app.js"></script> <!-- Load services --> <script src="app/services/placesExplorerService.js"></script> <script src="app/services/placesPhotosService.js"></script> <script src="app/services/placesDataService.js"></script> <!-- Load controllers --> <script src="app/controllers/placesPhotosController.js"></script> <script src="app/controllers/placesExplorerController.js"></script> <script src="app/controllers/userContextController.js"></script> <script src="app/controllers/myPlacesController.js"></script> <script src="app/controllers/navigationController.js"></script> <script src="app/controllers/aboutController.js"></script> <!-- Load custom filters --> <script src="app/filters/placeNameCategoryFilter.js"></script> <!-- Load custom directives --> <script src="app/directives/fsUnique.js"></script> |
So instead of letting the browser to request eleven small files simultaneously, it is better to concatenate all these files into a single file, then minify this file and reference it.
Minifying AngularJS controllers or services depends on how you define those services/controllers on the un-minified version, the controllers and services should be written in a minified-safe way. Unfortunately when I wrote this tutorial I wasn’t thinking of minifying my app files, so let’s see how we can write AngularJS minified-safe files.
Writing minified-safe AngualrJS files
Currently if we take a look on how I created the unsafe version of the controller as the code snippet below, you will notice that I’m injecting the $scope and a custom Angular service named placesDataService into myPlacesController which is added to a module named app:
1 2 3 |
app.controller('myPlacesController', function ($scope, placesDataService) { //Injected $scope and custom placesDataService into 'myPlacesController' }); |
When we try to minify this file using any minification tool, the result will be as the code snippet below:
1 2 3 4 |
app.controller('myPlacesController', function (a, b) { //This is not correct and will throw exception at run time because (a) param is not valid //dependency and the (b) param will not represent the 'placesDataService' }); |
As you notice in the comments above this will throw run time exception when we invoke this controller, there is no way to tell angular that (a) parameter is equivalent to ($scope) and the same applies to (b) parameter, the dependency injector will not be able to identify services correctly.
To solve this issue we need to write the controllers in a different way, we’ve to annotate the function with the names of dependencies provided as strings which will not get minified, so instead of just providing the function only we provide an array which contains list of all the services injected, be aware of the order for array elements, they should match the order of function parameters. So the minified-safe version of the controller will look as the code snippet below:
1 2 3 |
app.controller('myPlacesController', ['$scope', 'placesDataService', function ($scope, placesDataService) { //This is the safe way to write controllers if you are planning to minify them, and I believe you should do! }]); |
We’ve to modify app.js file, all services, all controllers, and any AngularJS JavaScript file which uses dependency injection and rewrite it in the above way by annotating the functions with the names of dependencies as strings. You can download the complete modified project (minfied-safe version) and compare it with the original one hosted on GitHub (This contains unsafe-minified version of AngularJS files.)
After we’ve these nice minified-safe JavaScript files we need to optimize our web application for faster loading and less bandwidth consumption, so we need to use Gulpjs to help us in achieving the below requirements:
- Concatenate, minify, and remove debugger or console logs from JavaScript files.
- Concatenate, add missing vendor prefixes, then minify CSS files.
- Minify HTML partial views.
- Automate the tasks above by watching any change on those files.
Now it is time to play with Gulpjs, I’ll implement all the requirements above in seven steps so you can follow along if this is your first time to use Gulpjs.
Step One: Install Node.js
Don’t panic, you do not need to be Node.js developer to use Gulpjs, you just need to have basic JavaScript knowledge to be able to define the Gulpjs tasks, to install Node.js jump to nodejs.org and select the appropriate platform installer, this installer will install two things: Node.js and NPM (Node Package Manager) which we’ll be using to install Gulpjs and Gulpjs modules.
Once the installation is completed open command prompt and enter: node -v and the installed Node.js version will be displayed, as well you can do the same for Node Package Manger so you can enter: npm -v
Step Two: Install Gulpjs
Now we’ll use the Node Package Manager (NPM) to install Gulpjs, all we need to do is open CMD and enter:
1 |
npm install gulp -g |
Notice that we’ve added -g in order to install Gulpjs globally for any project, so there is no need to do this step again if you want to configure Gulpjs with another project.
In order to confirm that installation took place successfully you can enter: gulp -v and the installed version will be displayed.
Step Three: Setup The Project:
Our web project is located on path: “D:\GettingStartedGulp\FoursquareAngularJS\FoursquareAngularJS.Web” so we need to use CMD to navigate to this path, this is very important step to configure Gulpjs correctly, so use CMD and make sure you are on drive “D:\” and enter:
1 |
cd D:\GettingStartedGulp\FoursquareAngularJS\FoursquareAngularJS.Web |
The path for CMD should be changed to the path above, In this post I’ll keep calling this path “ProjectPath” for brevity.
Now we need to install Gulpjs locally for this project only, so use CMD and enter:
1 |
npm install gulp --save-dev |
Notice the –save-dev command which will tell the package to appear in devDependencies, you can read about this here. Note: You can follow along with those steps and read this later.
After you execute the command above a new folder named node_modules will be created automatically under ProjectPath, this folder will contain all Gulpjs modules we’ll install later on, last thing to do here is to create manually a file named gulpfile.js under the ProjectPath, this file will be used to write all the code needed to define Gulpjs tasks.
Step Four: Installing Gulpjs modules:
There are more than 600 Gulpjs modules which will help you in defining tasks, we’ll use some of those modules to achieve the four requirements mentioned above, the first requirement I want to achieve here is to minify the HTML partials, so I need to install the Gulpjs modules which will help me to accomplish this, open CMD and enter:
1 |
npm install gulp-changed gulp-minify-html --save-dev |
As you notice above I’ve installed two gulp modules which they are “gulp-changed” and “gulp-minify-html”, the gulp-changed will be responsible to minify only the changed HTML files.
Now it is time to write our first task inside the file gulpfile.js but before writing the first task it is important to mention that Gulpjs only provides 4 simple APIs you need to remember in order to define all your tasks, APIs are: task, src, dest, and watch. We’ll cover those APIs while we are defining Gulpjs tasks, so open the gulpfile.js and paste the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// include gulp var gulp = require('gulp'); // include plug-ins var changed = require('gulp-changed'); var minifyHTML = require('gulp-minify-html'); // minify new or changed HTML pages gulp.task('minify-html', function() { var opts = {empty:true, quotes:true}; var htmlPath = {htmlSrc:'./app/views/*.html', htmlDest:'./appbuild/views'}; return gulp.src(htmlPath.htmlSrc) .pipe(changed(htmlPath.htmlDest)) .pipe(minifyHTML(opts)) .pipe(gulp.dest(htmlPath.htmlDest)); }); |
What we’ve implemented above is simple, we included the modules needed (gulp-changed, and gulp-minify-html) then we defined our first gulp task and named it “minify-html”. We provided for this task the source of the HTML partials we want to minify and the destination for the minified version, note: that the destination path will be created automatically.
The nice thing is how Gulpjs works, there is chaining for actions, in other words the output of a process will be used as input for the another process. In our case the first thing we will do is to watch which file has changed, then we will pass this file to minification process, once the minification process is done it will pass the output to the last process where the modified file will be sent to the destination path to be updated or created if it is new.
To test this we need to run this task from CMD by entering:
1 |
gulp minify-html |
If all is configured correctly the response will be as the image below, you can now navigate to the destination path “\ProjectPath\appbuild\views” and check the minified version of the HTML views.
Step Five: Add More Modules:
I believe things are becoming clearer now, we want to achieve another requirement which is concatenate, add missing vendor prefixes, and minify CSS files, so first thing to do is to install the modules needed, open CMD and enter:
1 |
npm install gulp-concat gulp-autoprefixer gulp-minify-css gulp-rename --save-dev |
Now we’ve installed four new modules which will be responsible of concatenating the CSS files into one file, adding the required vendor prefix, minifying the CSS, and renaming the output file.
We need to add a new Gulpjs task named “minify-css” so open file gulpfile.js and paste the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// include plug-ins var concat = require('gulp-concat'); var autoprefix = require('gulp-autoprefixer'); var minifyCSS = require('gulp-minify-css'); var rename = require('gulp-rename'); // CSS concat, auto prefix, minify, then rename output file gulp.task('minify-css', function() { var cssPath = {cssSrc:['./content/css/*.css', '!*.min.css', '!/**/*.min.css'], cssDest:'./contentbuild/css/'}; return gulp.src(cssPath.cssSrc) .pipe(concat('styles.css')) .pipe(autoprefix('last 2 versions')) .pipe(minifyCSS()) .pipe(rename({ suffix: '.min' })) .pipe(gulp.dest(cssPath.cssDest)); }); |
What we’ve implemented above is simple, we want to concatenate all CSS files in single file named “styles.css”, add any additional vendor specific prefixes for CSS properties, minify the newly created file “styles.css”, and finally rename it to “styles.min.css”. The the only thing worth noticing here that gulp.src API accepts an array of paths, so you can specify the files to be excluded from minification. In my case I’m excluding any file ends with “.min.css” from minification.
To test this we need to run this task from CMD by entering:
1 |
gulp minify-css |
The last task we want to define here is bundling all our AngularJS files (Services, controllers, directives, and filters) into single file named “ngscripts.min.js”. It is very similar to the tasks “minify-css”, the only difference that we need to remove “debugger and console.log” from all JavaScript files.
So to install the modules needed open CMD and enter:
1 |
npm install gulp-strip-debug gulp-uglify --save-dev |
Now we’ve installed two new modules which will be used for stripping debugging JavaScript code and for minifying JavaScript files.
We need to add a new Gulpjs task named “bundle-scripts” so open file gulpfile.js and paste the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// include plug-ins var stripDebug = require('gulp-strip-debug'); var uglify = require('gulp-uglify'); // JS concat, strip debugging code and minify gulp.task('bundle-scripts', function() { var jsPath = {jsSrc:['./app/app.js','./app/**/*.js'], jsDest:'./appbuild'}; gulp.src(jsPath.jsSrc) .pipe(concat('ngscripts.js')) .pipe(stripDebug()) .pipe(uglify()) .pipe(rename({ suffix: '.min' })) .pipe(gulp.dest(jsPath.jsDest)); }); |
The above task is similar to task “minify-html”, we want to concatenate all AngularJS JavaScript files in single file named “ngscripts.js”, strip debugging code from files, minify the new created file “ngscripts.js”, and finally rename it to “ngscripts.min.js”. Notice how we can specify the order of JS files as I want the file “app.js” to be on the top of the newly minified file.
To test this we need to run this task from CMD by entering:
1 |
gulp bundle-scripts |
Till this step if we modified any HTML, JavaScript, CSS file we need to run the corresponding task manually from CMD, this won’t be convenient and you might forget to do this. In the next step we’ll see how we can overcome this issue.
Step Six: Adding Default Task:
Gulpjs allow us to create a “default” task which acts as wrapper for all other tasks, the second parameter of the task constructor accepts an array of dependent tasks, so open gulpfile.js again and let’s create the default task as the code below:
1 2 3 4 |
// default gulp task gulp.task('default', ['minify-html', 'bundle-scripts', 'minify-css'], function() { }); |
In the code above we’ve defined the default task and configured it to run the three tasks in parallel, so if we jump to CMD and executed the default task all the three tasks (‘minify-html’, ‘bundle-scripts’, ‘minify-css’) will be executed in a parallel fashion, to test this open CMD and type gulp only without any additional parameter as the code snippet below:
1 |
gulp |
Step Seven: Automate the Process:
This is the last step, we want to automate this and stop jumping to CMD after each change on any file and type gulp over and over to execute the default task. Fortunately Gulpjs has an API called watch which will help us to automate this, in simple words we need to add multiple watches on a certain paths and execute array of tasks when any file get changed on the path we are watching.
To implement this open gulpfile.js again and paste the code below:
1 2 3 4 5 6 7 8 9 |
// default gulp task gulp.task('default', ['minify-html', 'bundle-scripts', 'minify-css'], function() { // watch for HTML changes gulp.watch('./app/views/*.html', ['minify-html']); // watch for JS changes gulp.watch('./app/**/*.js', ['bundle-scripts']); // watch for CSS changes gulp.watch('./content/css/*.css', ['minify-css']); }); |
Jump to CMD and enter:
1 |
gulp |
Now we are watching the path “./app/views/*.html” for any file changes, so if we updated HTML file in folder “views” the Gulpjs task named “minify-html” will fire automatically and minify the changed HTML file again and so on. This process will remain active and will respond to any file change. Now there is no need to type anything on CMD and if you want to terminate it all you need to do is jump to CMD and press ctrl + c
Updating our AngularJS Application
Needles to say we’ve to update our AngularJS application references to point to those newly bundled and minified HTML, CSS, and JavaScript files, I’ve done this manually and I’m not sure if there is a way to change references only we want to publish our web application to production server and keep the raw un-minified versions during development. If you have any idea on how to implement this please drop me a comment.
That’s it for now, hopefully this article will be benefical for anyone new to Gulpjs. You can download the final project along with gulpfile.js and try the live demo (The live demo is referencing the un-minfied files).
Follow me on Twitter @tjoudeh
References
Special thanks for the two excellent articles:
Hi,
Thank you for an excellent tutorial!
I am following the tutorial step by step and everything is working perfectly. However, when I tried to run the task “bundle-scripts” I got the following error:
“errored after 5.8 ms stripDebug is not defined”
I noticed that you forgot to include this plugin in the code snippet.
Thanks again!
Hello Diana,
Good catch, thanks for your feedback. Code snippet has been updated.
Thanks again!
You should check out ng-min. It will make your Angular code minify-safe for you.
Thanks Tristin for ur feedback, I’ll consider using it.
Quick question: is there a way to change the source of referenced files without editing index.html manually?
I’m not sure what you mean.
Do you mean not adding the src for all your JS files in your index?
you can use https://www.npmjs.org/package/gulp-usemin to insert the minified and cleaned up versions of your files for distribution
Thanks Aaron, will consider this for sure.
Hi Taiseer and thanks again for wonderful tutorial.
I aggree with Aaaron to use gulp-usemin, and on the web.config you can check if debug or not so it’ll serve the app/index.html (non minified references) on debug and build/index.html (with minified referneces) on release.
Hello Oron, Glad you liked the tutorial, can you guide me where we’ll put this check, like where/when we’ll read the web.config and serve the minified version the configuration is set to release?
Sure, I’d love to. would you like to take it offline with your favourite messaging application?
That is great, add me on Google Chat: taiseer.joudeh@gmail.com and lets chat once you are free.
Hey Taiseer! Could you give a hint how to serve the minified version the configuration for release?
Also I need to figure out how to make web deploy package for release after that and omit file included to project. Help !!!
I didn’t do those steps by my self, thankfuly Oron chated with me and suggested the below steps, hopefully they will give you guide on how to implement this
– Create new minified shell page name it index.min.html.
– Install “usemin” from https://www.npmjs.org/package/gulp-usemin
– usemin will only replace the references with minified references.
– Set your watch to do the usemin task.
– To serve the minified index.min.html (or what ever name you choose) create Web.Release.config and use xdt:Transform for the default document element.
Let me know if you are able to implement this correctly.
Hi, so will gulp modify my html to change path to css/js to use new (concatenated and minified) versions?
Hi Jose,
I’ve just answered this on another comment, please check the answer.
Taiseer, I made following.
Adjusted for usemin my index.html.
Created gulpfile using bunch of plugins:
// include gulp
var gulp = require(‘gulp’);
// include plug-ins
var changed = require(‘gulp-changed’);
var minifyHTML = require(‘gulp-minify-html’);
var concat = require(‘gulp-concat’);
var autoprefix = require(‘gulp-autoprefixer’);
var minifyCSS = require(‘gulp-minify-css’);
var stripDebug = require(‘gulp-strip-debug’);
var uglify = require(‘gulp-uglify’);
var del = require(‘del’);
var usemin = require(‘gulp-usemin’);
var rev = require(‘gulp-rev’);
gulp.task(‘clean’, function (cb) {
return del([‘./appbuild/**’], cb);
});
gulp.task(‘usemin’, [‘clean’], function () {
return gulp.src(‘./index.html’)
.pipe(usemin({
css: [‘concat’, autoprefix(‘last 2 versions’), minifyCSS({ keepSpecialComments: 0 }), rev()],
js: [stripDebug(), uglify(), rev()]
}))
.pipe(gulp.dest(‘./appbuild/’));
});
// minify new or changed HTML pages
gulp.task(‘minify-html’, [‘clean’], function () {
var opts = { empty: true, quotes: true };
var htmlPath = { htmlSrc: ‘./app/**/*.html’, htmlDest: ‘./appbuild/app’ };
return gulp.src(htmlPath.htmlSrc)
.pipe(changed(htmlPath.htmlDest))
.pipe(minifyHTML(opts))
.pipe(gulp.dest(htmlPath.htmlDest));
});
gulp.task(‘publish’, [‘clean’, ‘minify-html’, ‘usemin’], function () {
});
Then added PublishIgnore(Pre) NuGet package and stop publishing almost all my directories:
(need to change in ls.pubignore.wpp.targets dll name to use Microsoft.Build.Tasks.v12.0.dll)
# prevents this file from being published
publish.ignore
*.wpp.targets
index.html
Content\
Plugins\
Scripts\
app\
Then I added .wpp.targets file to publish what I need:
CustomCollectFiles;
$(CopyAllFilesToSingleFolderForPackageDependsOn);
CustomCollectFiles;
$(CopyAllFilesToSingleFolderForMsdeployDependsOn);
%(RecursiveDir)%(Filename)%(Extension)
I run gulp as post build step in Release.
Created package for MSDeploy, it has only needed staff.
It is pretty clean on my opinion.
Thank you for help !
Olexandr
Don’t know what to say. Just want to cry out of joy. This gulp thing is AMAZING!!!!!!! TvT o
Glad it was useful 🙂
Hello, I like your articles so much, I always learn something. Thank you very much.
when i tried to minify js file i am getting stuck with this error. let me know how to proceed
[11:25:07] Starting ‘bundle-scripts’…
[11:25:08] The following tasks did not complete: bundle-scripts
[11:25:08] Did you forget to signal async completion?