Use Gulp to ease your angularjs workflow
I’m often seeing people having trouble managing angular dependency injection, minification, templates etc. I never had such issues, because my gulp-based workflow already handles everything for me.
# What’s wrong
For example, angular DI, even though they deprecated the awful array notation, is a pain in the ass. Yeah, it’s not DRY at all:
angular.module('myModule')
.controller('SomeCtrl', SomeCtrl)
function SomeCtrl($scope, $timeout) {}
//get the déjà-vu feeling?
SomeCtrl.$inject = ['$scope', '$timeout'];
Writing some templates, that angular will interprete so that they’ll be available in the $templateCache
also sucks. Especially if you want some clean way of doing it. Using javascript, it would look a lot like this:
angular.module('myModule.templates')
.run(['$templateCache', function($templateCache) {
$templateCache.put('views/list', '<ul><li></li></ul>')
}])
But, a template is HTML right? Why bother writing HTML inside Javascript? Actually I copied one of my gulp-rendered templates because I’ve never done that. Ok, I might but I was an angular newbie then.
# Fix it!
# Dependency injection
First, use ng-annotate! (FYI: first commit on Sep 3, 2013)
This is an amazing tool that will take care of the DI for you. You can now write:
function MyCtrl($scope) {}
And the plugin will add .$inject = ['$scope']
. This is nice and allows you, on top of less keystrokes, to keep a cleaner code.
# Templates
Write your templates using HTML, or HAML or whatever. Just don’t write crazy templates within javascript! This makes me remember of late 2006’s ; when writing HTML inside an awful PHP code made sense!
# Gulping fancy
For the record, I’m following those angular style guide.
Take the following structure:
.
├── dist
│ ├── main.js
│ └── main.min.js
└── src
├── js
│ ├── app.js
│ └── controller
│ └── SomeCtrl.js
└── templates
└── views
└── home.html
We need:
- gulp-ng-annotate yeah it’s the ng-annotate thing in a gulp plugin
- gulp-angular-templatecache transforms html to angular templates
- gulp-minify-html yup, now we can show off – and actually reduce our assets size
- gulp-jsclosure do I really need to justify?
- gulp-uglify of course, minified is better
- gulp-concat we’re using a nice structure but we only want 1 file left
- gulp-rename I want one main.js and main.min.js
Optionally, you might want to use gulp-plumber to manage stream errors so that they don’t throw. Especially annoying when gulp exits while using gulp watch
.
One-liner dependencies installation:
npm install gulp gulp-ng-annotate gulp-angular-templatecache gulp-minify-html gulp-jsclosure gulp-uglify gulp-concat gulp-rename --save-dev
And now we can write the gulpfile
:
// gulpfile.js
'use strict'
const gulp = require('gulp')
const templateCache = require('gulp-angular-templatecache')
const minifyHtml = require('gulp-minify-html')
const concat = require('gulp-concat')
const uglify = require('gulp-uglify')
const rename = require('gulp-rename')
const ngannotate = require('gulp-ng-annotate')
const closure = require('gulp-jsclosure')
const p = require('path')
/**
* Paths configuration, easy to change and/or use in multiple tasks
*/
const paths = {
javascripts: [
'./src/js/app.js',
/*
* this file should not be commited to git, you write HTML!
* it should also not beeing watched by gulp if it then triggers a change
* or gulp will be left in an infinite loop (see below)
*/
'./src/js/templates.js',
'./src/**/*.js',
'./src/**/**/*.js'
],
templates: [
'./src/templates/*.html',
'./src/templates/**/*.html'
]
}
/**
* Takes html templates and transform them to angular templates (javascript)
*/
gulp.task('templates', function() {
return gulp.src(paths.templates)
.pipe(minifyHtml({
empty: true,
spare: true,
quotes: true
}))
.pipe(templateCache({
module: 'myModule.templates',
standalone: true,
/**
* Here, I'm removing .html so that `$templateCache` holds
* the template in `views/home` instead of `views/home.html`.
* I'm keeping the directory structure for the template's name
*/
transformUrl: function(url) {
return url.replace(p.extname(url), '')
}
}))
//put all those to our javascript file
.pipe(concat('templates.js'))
.pipe(gulp.dest('./src/js'))
})
/**
* Concat, closure, annotate, uglify scripts
* @beforeDo `gulp templates`
*/
gulp.task('scripts', ['templates'], function() {
return gulp.src(paths.javascripts)
//first, I'm building a clean 'main.js' file
.pipe(concat('main.js'))
.pipe(closure({angular: true}))
.pipe(ngannotate())
.pipe(gulp.dest('./dist'))
//then, uglify the `main.js` and rename it to `main.min.js`
//mangling might cause issues with angular
.pipe(uglify({mangle: false}))
.pipe(rename('main.min.js'))
.pipe(gulp.dest('./dist'))
})
/**
* The command `gulp` will resolve in `gulp scripts`
*/
gulp.task('default', ['scripts'])
/**
* Watch the paths you want and execute the scripts task
* @beforeDo default (small useful hack)
*/
gulp.task('watch', ['default'], function() {
/**
* Either don't add `templates.js` to the js paths
* and add it later to the scripts task source or remove it here.
* Indeed, if `templates.js` is beeing watched,
* it'll run the templates task and it might change the file.
* The task will then run again, etc.
* You can use https://github.com/urish/gulp-add-src to add it to the `script`
* sources.
*/
let js = paths.javascripts.slice()
js.splice(js.indexOf('./src/js/templates.js'), 1)
gulp.watch(js, ['scripts'])
})
And now we can code in HTML, javascript, and forget about .$inject
or ugly javascripthtml, while coding!
To use the $templateCache
in your app, you just have to add myModule.templates
to the module dependencies and use $templateCache.get('views/home')
.
# Feel the Gulp power
I hate HTML, maybe I wrote to much of it, maybe just because closing tags give me a “repeat yourself” feeling. Thankfully, there are many template engines that compiles to HTML those days. My preferred one is HAML, but the following will work the same with Jade, or any other.
So, I’m adding HAML to the workflow. HAML needs to be converted in HTML and then in Angular templates.
For HAML, the only plugin that always worked for me is the gulp-ruby-haml. Note that it is not really following gulp philosophy in which it just executes the haml
command.
Let’s install this (you will need haml
ruby gem):
npm install gulp-ruby-haml
Then simply add the dependency, change your templates extensions and add the haml to html transformation:
Index: gulpfile.js
===================================================================
--- gulpfile.js 2015-12-01 21:05:25.000000000 +0100
+++ gulpfile.js 2015-12-01 21:05:35.000000000 +0100
@@ -8,6 +8,7 @@
const rename = require('gulp-rename')
const ngannotate = require('gulp-ng-annotate')
const closure = require('gulp-jsclosure')
+const haml = require('gulp-ruby-haml')
const p = require('path')
const paths = {
@@ -23,13 +24,14 @@
'./src/**/**/*.js'
],
templates: [
- './src/templates/*.html',
- './src/templates/**/*.html'
+ './src/templates/*.haml',
+ './src/templates/**/*.haml'
]
}
gulp.task('templates', function() {
return gulp.src(paths.templates)
+ .pipe(haml()) //that's the gulp magic we love
.pipe(minifyHtml({
empty: true,
spare: true,
That’s it, you can now write HAML, and keep the exact same configuration.
Now enjoy coding with angularjs and your favorite template engine!