Wednesday, December 31, 2014

Gulp is the Wild West

TL;DR This post described how I implemented asset processing with Gulp in a .NET project.

enter image description here

I am working on a .NET web application using Nancy in the backend and AngularJS in the frontend.
I decided to use a frontend-build based on Gulp. Frontend builds based on Grunt or Gulp have a lot of momentum right now, and this seems a better option for me than some .NET based tools like Cassette or SquishIt

By the way: if you have a node based frontend build you can run it on each Azure deployment like described here.

My requirements were the following:

  • For development the project should be runnable from VisualStudio without having to trigger a frontend build. That means that all the frontend assets have to be present at development time and referenced from my .cshtml files.
  • For release the frontend assets should be processed (concatenated, minified, revisioned …) by a frontend build. This build also needed to manipulate the .cshtml files, since they must reference the processed assets.

I started with gulp-usemin:

gulp.task("usemin", function() {
    return gulp.src("./Views/*.cshtml")
        .pipe(usemin({
            js: [ngAnnotate(), uglify(), rev()]
        }))
        .pipe(gulp.dest("Dist/"));

});

This was nice and easy and worked well … until I had some more requirements …

The problem:
With gulp-usemin you have not access to the stream in a pipeline. So you are limited to list some tasks that should be performed. If you want to do something more fancy, you are out of luck. In my case I wanted to add the output of angular template cache to my main js file after it has been concatenated and minified…
In my case I wanted to append another JavaScript file that contains my angular templates (generated with gulp-angular-templatecache)

So I started looking for another solution… thats where I discovered that I am in the Wild Wild West: gulp-usemin2, gulp-spa, gulp-asset-transform, gulp-inject, gulp-rev-replace, gulp-useref, gulp-rev-all, gulp-rev-collector … all seem somehow to do something similar, documentation is minimalistic and you can waste a big amount of time to find the right plugin combination that works for you.

I ended up with the following combination of plugins:

var gulp = require("gulp");
var ngAnnotate = require("gulp-ng-annotate");
var templateCache = require("gulp-angular-templatecache");
var uglify = require("gulp-uglify");
var addsrc = require("gulp-add-src");
var concat = require("gulp-concat");
var rev = require("gulp-rev");
var minifyCSS = require("gulp-minify-css");
var useref = require("gulp-useref");
var filter = require("gulp-filter");
var revReplace = require("gulp-rev-replace");
var order = require("gulp-order");

gulp.task("angular-templates", function(){
    return gulp.src(TEMPLATES_SOURCES)
        .pipe(templateCache(TEMPLATES_JS, {module: "moonwalk", root: TEMPLATES_ROOT}))
        .pipe(gulp.dest("Temp/"));
});

gulp.task("release-assets", ["angular-templates"], function() {
    var assets = useref.assets();
    var jsFilter = filter("**/*.js");
    var moonwalkFilter = filter("**/" + MOONWALK_JS);
    var cssFilter = filter("**/*.css");

    return gulp.src("./Content/**/*.cshtml")
        .pipe(assets)               // Concatenate with gulp-useref
        .pipe(jsFilter)
        .pipe(ngAnnotate())         // Process javascript sources to add dependency injection annotations
        .pipe(uglify())             // Minify javascript sources
        .pipe(jsFilter.restore())
        .pipe(cssFilter)
        .pipe(minifyCSS())          // Minify CSS sources
        .pipe(cssFilter.restore())
        .pipe(moonwalkFilter)       // Filter the moonwalk.js source file, which is generated above by useref
        .pipe(addsrc("Temp/" + TEMPLATES_JS))// Add the templates.js to the stream, which is generated by a seperate task
        .pipe(order(["**/" + MOONWALK_JS, "*.js"]))// Order stream, so that templates.js is appended to moonwalk.js (needed, since templates depend on the angular module)
        .pipe(concat(MOONWALK_JS))// Concat the existing moonwalk.js and the templates.js into moonwalk.js
        .pipe(moonwalkFilter.restore())
        .pipe(rev())                // Rename the concatenated files
        .pipe(assets.restore())
        .pipe(useref())             // Replace the original references in the cshtml with the concatenated and processed resources by usemin
        .pipe(revReplace({replaceInExtensions:[".cshtml"]}))         // Replace the usemin generated resources with the reved resources
        .pipe(gulp.dest("Dist/"));
});

And by the way: This thread about running tasks synchronously is representative for Gulp (final verdict: “This will be fixed in gulp 4”) … welcome in the wild wild west!

2 comments:

  1. how do you dynamically set the folder to serve the views in your .net project

    ReplyDelete
  2. @George: We were usnig Nancy (http://nancyfx.org/)

    In Nancy it is quite easy to dyamically configure where views should be searched:

    https://github.com/NancyFx/Nancy/wiki/View-location-conventions

    ReplyDelete

Related Posts Plugin for WordPress, Blogger...