Building your Angular app with Gulp.js

<p>As my work has transitioned from traditional web apps to thick-client Javascript apps (primarily using Angular), <a href="http://gruntjs.com">Grunt</a> has become essential in my workflow. Grunt is a nice tool and it gets the job done. But there was always something I didn&rsquo;t like about it that I couldn&rsquo;t quite articulate until I discovered <a href="http://gulpjs.com">Gulp.js</a>. Whereas in Grunt, you create a json configuration file, Gulp is just a script. It&rsquo;s code. And it really fits my programmer brain better. In this post, I&rsquo;ll convert a basic Gruntfile that compiles and minifies Coffeescript into Gulp.</p> <p></p> <h3 id="the-original-gruntfile">The Original Gruntfile</h3> <p>The original Gulpfile I&rsquo;m converting can be found <a href="https://github.com/adamalbrecht/ngQuickDate/blob/48fb6f6db38de16d5c92bbbbe5e8e0eee2ad69b0/Gruntfile.js">here</a>. It is for an angular datepicker library that I wrote called ngQuickDate. <em>(Side note: I&rsquo;m in the middle of re-writing this library from the ground up because I no longer like the way it works)</em>.</p> <p>I won&rsquo;t put the entire file inline, but here are the relevant parts that compile coffeescript files and then minifies them.</p> <div class="highlight"><pre class="highlight javascript"><code><span class="nx">coffee</span><span class="p">:</span> <span class="p">{</span> <span class="nl">compile</span><span class="p">:</span> <span class="p">{</span> <span class="na">files</span><span class="p">:</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">spec/build/specs.js</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">spec/*.coffee</span><span class="dl">"</span><span class="p">],</span> <span class="dl">"</span><span class="s2">dist/ng-quick-date.js</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">src/*.coffee</span><span class="dl">"</span><span class="p">]</span> <span class="p">}</span> <span class="p">}</span> <span class="p">},</span> <span class="nx">uglify</span><span class="p">:</span> <span class="p">{</span> <span class="nl">my_target</span><span class="p">:</span> <span class="p">{</span> <span class="na">files</span><span class="p">:</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">dist/ng-quick-date.min.js</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">dist/ng-quick-date.js</span><span class="dl">"</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">// ...</span> <span class="nx">grunt</span><span class="p">.</span><span class="nx">loadNpmTasks</span><span class="p">(</span><span class="dl">'</span><span class="s1">grunt-contrib-coffee</span><span class="dl">'</span><span class="p">);</span> <span class="nx">grunt</span><span class="p">.</span><span class="nx">loadNpmTasks</span><span class="p">(</span><span class="dl">'</span><span class="s1">grunt-contrib-uglify</span><span class="dl">'</span><span class="p">);</span> <span class="nx">grunt</span><span class="p">.</span><span class="nx">registerTask</span><span class="p">(</span><span class="dl">'</span><span class="s1">default</span><span class="dl">'</span><span class="p">,</span> <span class="p">[</span><span class="dl">'</span><span class="s1">coffee</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">uglify</span><span class="dl">'</span><span class="p">]);</span> </code></pre></div> <p>It&rsquo;s pretty straightforward, but I have 2 problems with it. First, my 2 tasks have no knowledge of one another. Coffeescript files are read, compiled, and written to disk. Then another task reads these new files in, minifies them, then writes to disk again. My second problem is that, with a configuration-based approach like this, it is very hard for a beginner to figure out how to modify tasks and add additional logic to them.</p> <p>So let&rsquo;s first reproduce the same gruntfile in Gulp.</p> <h3 id="simple-gulpfile">Simple Gulpfile</h3> <div class="highlight"><pre class="highlight javascript"><code><span class="kd">var</span> <span class="nx">gulp</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">gulp</span><span class="dl">"</span><span class="p">);</span> <span class="kd">var</span> <span class="nx">concat</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">gulp-concat</span><span class="dl">"</span><span class="p">);</span> <span class="kd">var</span> <span class="nx">coffee</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">gulp-coffee</span><span class="dl">"</span><span class="p">);</span> <span class="kd">var</span> <span class="nx">uglify</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">gulp-uglify</span><span class="dl">"</span><span class="p">);</span> <span class="kd">var</span> <span class="nx">rename</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">gulp-rename</span><span class="dl">"</span><span class="p">);</span> <span class="nx">gulp</span><span class="p">.</span><span class="nx">task</span><span class="p">(</span><span class="dl">"</span><span class="s2">js</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">gulp</span><span class="p">.</span><span class="nx">src</span><span class="p">([</span><span class="dl">"</span><span class="s2">src/*.coffee</span><span class="dl">"</span><span class="p">])</span> <span class="c1">// Read the files</span> <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span> <span class="nx">coffee</span><span class="p">({</span><span class="na">bare</span><span class="p">:</span><span class="kc">true</span><span class="p">})</span> <span class="c1">// Compile coffeescript</span> <span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> <span class="nx">gutil</span><span class="p">.</span><span class="nx">log</span><span class="p">)</span> <span class="p">)</span> <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">concat</span><span class="p">(</span><span class="dl">"</span><span class="s2">ng-quick-date.js</span><span class="dl">"</span><span class="p">))</span> <span class="c1">// Combine into 1 file</span> <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">gulp</span><span class="p">.</span><span class="nx">dest</span><span class="p">(</span><span class="dl">"</span><span class="s2">dist</span><span class="dl">"</span><span class="p">))</span> <span class="c1">// Write non-minified to disk</span> <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">uglify</span><span class="p">())</span> <span class="c1">// Minify</span> <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">rename</span><span class="p">({</span><span class="na">extname</span><span class="p">:</span> <span class="dl">"</span><span class="s2">.min.js</span><span class="dl">"</span><span class="p">}))</span> <span class="c1">// Rename to ng-quick-date.min.js</span> <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">gulp</span><span class="p">.</span><span class="nx">dest</span><span class="p">(</span><span class="dl">"</span><span class="s2">dist</span><span class="dl">"</span><span class="p">))</span> <span class="c1">// Write minified to disk</span> <span class="p">});</span> <span class="nx">gulp</span><span class="p">.</span><span class="nx">task</span><span class="p">(</span><span class="dl">"</span><span class="s2">default</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">gulp</span><span class="p">.</span><span class="nx">start</span><span class="p">(</span><span class="dl">"</span><span class="s2">js</span><span class="dl">"</span><span class="p">);</span> <span class="p">});</span> </code></pre></div> <p>While it&rsquo;s not any shorter, I find it significantly easier to read and modify without referring to documenation. As you can see, Gulp accomplishes tasks by using streams and piping, which is a great fit for reading source files, performing a number of actions on them, then writing back out to disk. Each action is chained on to the last one and this makes it unecessary to write out to temp files, thus making it quite a bit faster, as well.</p> <h3 id="angular-extras-in-gulp">Angular Extras in Gulp</h3> <p>If you&rsquo;ve used Angular for any significant period of time, you&rsquo;ll know that it&rsquo;s a bit quirky. First, minification often breaks your app due to the way its dependency injection system works. But luckily there&rsquo;s a fix for this: <a href="https://github.com/btford/ngmin">ng-min</a>. This library will analyze your source code and add the necessary arrays around function calls to make it minification friendly. For example, it will convert this:</p> <div class="highlight"><pre class="highlight javascript"><code><span class="nx">angular</span><span class="p">.</span><span class="nx">module</span><span class="p">(</span><span class="dl">'</span><span class="s1">whatever</span><span class="dl">'</span><span class="p">).</span><span class="nx">controller</span><span class="p">(</span><span class="dl">'</span><span class="s1">MyCtrl</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">$scope</span><span class="p">,</span> <span class="nx">$http</span><span class="p">)</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span> </code></pre></div> <p>into&hellip;.</p> <div class="highlight"><pre class="highlight javascript"><code><span class="nx">angular</span><span class="p">.</span><span class="nx">module</span><span class="p">(</span><span class="dl">'</span><span class="s1">whatever</span><span class="dl">'</span><span class="p">).</span><span class="nx">controller</span><span class="p">(</span><span class="dl">'</span><span class="s1">MyCtrl</span><span class="dl">'</span><span class="p">,</span> <span class="p">[</span><span class="dl">'</span><span class="s1">$scope</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">$http</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">$scope</span><span class="p">,</span> <span class="nx">$http</span><span class="p">)</span> <span class="p">{</span> <span class="p">...</span> <span class="p">}]);</span> </code></pre></div> <p>Integrating this into Grunt is easy enough, but you have to do so as an entirely separate task that runs independently of your compilation and minification. In gulp, we can add it into the middle of our chain of compilation/minification actions by adding a couple lines to our Gulpfile:</p> <div class="highlight"><pre class="highlight javascript"><code><span class="kd">var</span> <span class="nx">ngmin</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">gulp-ngmin</span><span class="dl">"</span><span class="p">);</span> <span class="c1">// NEW</span> <span class="c1">// ...</span> <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span> <span class="nx">coffee</span><span class="p">({</span><span class="na">bare</span><span class="p">:</span><span class="kc">true</span><span class="p">})</span> <span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> <span class="nx">gutil</span><span class="p">.</span><span class="nx">log</span><span class="p">)</span> <span class="p">)</span> <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">ngmin</span><span class="p">())</span> <span class="c1">// NEW: Make angular code friendly to minification</span> <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">concat</span><span class="p">(</span><span class="dl">"</span><span class="s2">ng-quick-date.js</span><span class="dl">"</span><span class="p">))</span> <span class="c1">// ...</span> </code></pre></div> <p>Couldn&rsquo;t be easier. Next, let&rsquo;s say we&rsquo;re also compiling html templates from Jade but we&rsquo;d like them to pre-load them into Angular&rsquo;s template cache so we don&rsquo;t have to make http requests every time. No problem!</p> <div class="highlight"><pre class="highlight javascript"><code><span class="kd">var</span> <span class="nx">ngmin</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">gulp-angular-templatecache</span><span class="dl">"</span><span class="p">);</span> <span class="c1">// ...</span> <span class="nx">gulp</span><span class="p">.</span><span class="nx">src</span><span class="p">([</span><span class="dl">"</span><span class="s2">src/templates/*.jade</span><span class="dl">"</span><span class="p">])</span> <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">jade</span><span class="p">())</span> <span class="c1">// Compile from jade to html</span> <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">templateCache</span><span class="p">())</span> <span class="c1">// Convert to JS strings and place in Angular template cache</span> <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">gulp</span><span class="p">.</span><span class="nx">dest</span><span class="p">(</span><span class="dl">"</span><span class="s2">dist</span><span class="dl">"</span><span class="p">))</span> <span class="c1">// Write the JS file to disk</span> </code></pre></div> <p>But again, it&rsquo;s easy to add steps in between. Next, let&rsquo;s take care of the fact that Angular apps often produce html that is not valid html5. We can automatically add <code>data-</code> prefixes to our various angular directive attributes by using the htmlify module:</p> <div class="highlight"><pre class="highlight javascript"><code><span class="kd">var</span> <span class="nx">htmlify</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">gulp-angular-htmlify</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// ...</span> <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">templateCache</span><span class="p">())</span> <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">htmlify</span><span class="p">())</span> <span class="c1">// Add data- prefixes to non-html5-valid attributes</span> <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">gulp</span><span class="p">.</span><span class="nx">dest</span><span class="p">(</span><span class="dl">"</span><span class="s2">dist</span><span class="dl">"</span><span class="p">))</span> </code></pre></div> <p>As you can see, it&rsquo;s fairly trivial to make changes to what started as an extremely basic gulp script.</p> <p>I wrote a couple angular starter templates that use gulp for compilation. One is meant for full web apps while the other is meant for creating angular libraries.</p> <ul> <li><a href="https://github.com/adamalbrecht/angular-starter-kit">angular-starter-kit</a> - Build thick-client web apps. Be aware, this is pretty opinionated and won&rsquo;t be for everyone.</li> <li><a href="https://github.com/adamalbrecht/angular-lib-template">angular-lib-template</a> - Build angular libraries</li> </ul> <p><hr></p> <h3 id="more-resources">More Resources</h3> <h3 id="documentation">Documentation:</h3> <ul> <li><a href="https://github.com/gulpjs/gulp/blob/master/docs/README.md">Gulp.js Documentation</a></li> </ul> <h3 id="common-gulp-utilities">Common Gulp Utilities:</h3> <ul> <li><a href="https://github.com/wearefractal/gulp-concat">gulp-concat</a> - Concatenate files</li> <li><a href="https://github.com/gulpjs/gulp-util">gulp-util</a> - Various gulp utility functions</li> <li><a href="https://github.com/peter-vilja/gulp-clean">gulp-clean</a> - Clean out a folder before writing to disk</li> <li><a href="https://github.com/nfroidure/StreamQueue">StreamQueue</a> - Combine multiple streams into one while preserving the order</li> <li><a href="https://github.com/robrich/gulp-if">gulp-if</a> - Apply actions to files that meet certain conditions</li> <li><a href="https://github.com/hparra/gulp-rename">gulp-rename</a> - Rename files</li> <li><a href="https://github.com/mikaelbr/gulp-notify">gulp-notify</a> - Show native OS notifications on compilation, errors, etc</li> <li><a href="https://github.com/avevlad/gulp-connect">gulp-connect</a> - Mini web server for running your app</li> </ul> <h3 id="common-gulp-pre-processors">Common Gulp Pre-Processors</h3> <ul> <li><a href="https://github.com/wearefractal/gulp-coffee">gulp-coffee</a> - Compile coffeescript</li> <li><a href="https://github.com/sindresorhus/gulp-ruby-sass">gulp-ruby-sass</a> - Compile (the ruby version of) sass and scss</li> <li><a href="https://github.com/plus3network/gulp-less">gulp-less</a> - Compile less.css</li> <li><a href="https://github.com/phated/gulp-jade">gulp-jade</a> - Compile jade templates</li> <li><a href="https://github.com/sindresorhus/gulp-markdown">gulp-markdown</a> - Compile markdown</li> </ul> <h3 id="minification-plugins">Minification Plugins</h3> <ul> <li><a href="https://github.com/terinjokes/gulp-uglify">gulp-uglify</a> - Minify / uglify code</li> <li><a href="https://github.com/jonathanepollack/gulp-minify-html">gulp-minify-html</a> - Minify html</li> <li><a href="https://www.npmjs.org/package/gulp-minify-css">gulp-minify-css</a> - Minify CSS</li> <li><a href="https://github.com/sindresorhus/gulp-imagemin">gulp-imagemin</a> - Compress images without losing quality</li> </ul> <h3 id="angular-helper-plugins">Angular Helper Plugins</h3> <ul> <li><a href="https://github.com/btford/ngmin">gulp-ngmin</a> - Make angular code friendly to minification</li> <li><a href="https://github.com/pgilad/gulp-angular-htmlify">gulp-angular-htmlify</a> - Make angular code valid html5</li> </ul>