Organizing Gulp Files

I noticed something that I didn’t like when I was developing the web components. It wasn’t the code itself. It was the build process. I like Gulp and JSPM and my build system, but the Gulp file was getting a little unwieldy. It no longer fit on one page, which meant scrolling just to find out what linked to what. It also had too many intermingled dependencies. It is time to refactor it so that I can add things to the build process without cluttering the main file.

The way I am going to organize it is that each distinct task is going to get its own file. Within that file there will be a build task and a clean task. I’m going to ensure that the system cleans up after itself. Thus if I have components to build, there will be a build:components and a clean:components task within a components.js file. All the tasks will be stored as Javascript files in the Gulp directory.

Each of these files could be a Gulpfile.js by themselves – I’d just be running gulp build:components instead.

I also did a little bit of organization. My client side files are all stored in src now, under specific directories for style, images, elements and javascript. This is purely organizational. I’ve adjusted the source to look in the right place for these files as well.

On to the individual tasks. I’ve shown off the style tasks a few times. Now I’ve placed them in the Gulp/styles.js file:

var gulp = require("gulp"),
    autoprefixer = require("gulp-autoprefixer"),
    clean = require("gulp-clean"),
    less = require("gulp-less"),
    minify = require("gulp-minify-css"),
    sourcemaps = require("gulp-sourcemaps");

gulp.task("build:style", function () {
    return gulp.src("src/style/*.less")
        .pipe(sourcemaps.init())
        .pipe(less())
        .pipe(autoprefixer({
            browsers: ["> 2%", "last 2 versions"],
            cascade: false,
            remove: true
        }))
        .pipe(minify())
        .pipe(sourcemaps.write("."))
        .pipe(gulp.dest("wwwroot/style"));
});

gulp.task("clean:style", function () {
    return gulp.src("wwwroot/style", { read: false })
        .pipe(clean());
});

The nice thing about this is that it is probably the same for every project I do so I can just use Add Existing Item… to add it from an existing project and it will just work. On to the Gulp/javascript.js file – again, this will be fairly common for ES6 code:

var gulp = require("gulp"),
    babel = require("gulp-babel"),
    clean = require("gulp-clean"),
    eslint = require("gulp-eslint"),
    sourcemaps = require("gulp-sourcemaps"),
    uglify = require("gulp-uglify");


gulp.task("javascript:jspm", function () {
    return gulp.src("./config.js").pipe(gulp.dest("wwwroot"));
});

gulp.task("javascript:eslint", function () {
    return gulp.src("src/js/**/*.js")
        .pipe(eslint({
            envs: {
                browser: true,
                es6: true
            }
        }))
        .pipe(eslint.format())
        .pipe(eslint.failOnError());
});

gulp.task("build:javascript", [ "javascript:jspm", "javascript:eslint" ], function () {
    return gulp.src("src/js/**/*.js")
        .pipe(sourcemaps.init())
        .pipe(babel({ modules: "system" }))
        .pipe(uglify())
        .pipe(sourcemaps.write({ includeContent: false, sourceRoot: "/src/" }))
        .pipe(gulp.dest("wwwroot/js"));
});

gulp.task("clean:javascript", function () {
    return gulp.src(["wwwroot/config.js", "wwwroot/js"], { read: false })
        .pipe(clean());
});

Note that I’ve integrated the copy of the config.js from the root of my directory into the wwwroot. When I get round to loading modules, I’ll want it there, so I may as well handle it at the same time. I’ve also added in eslint, source maps and file compression into the process.

I tended in the past not to do any processing of imaages – it tends to be not worth it. However, since I may re-use this file I decided to research the process and get something that would optimize images as well. This is in Gulp/images.js:

var gulp = require("gulp"),
    clean = require("gulp-clean"),
    imagemin = require("gulp-imagemin"),
    pngquant = require("imagemin-pngquant");

var images = [
    "src/images/**/*.png",
    "src/images/**/*.jpg"
];

gulp.task("build:images", function () {
    return gulp.src(images)
        .pipe(imagemin({
            progressive: true,
            svgoPlugins: [{ removeViewBox: false }],
            use: [pngquant()]
        }))
        .pipe(gulp.dest("wwwroot/images"));
});

gulp.task("clean:images", function () {
    return gulp.src("wwwroot/images", { read: false })
        .pipe(clean());
});

The complicated one that I added is the Gulp/components.js file that generates and vulcanizes the web components:

var gulp = require("gulp"),
    autoprefixer = require("gulp-autoprefixer"),
    babel = require("gulp-babel"),
    clean = require("gulp-clean"),
    less = require("gulp-less"),
    minifycss = require("gulp-minify-css"),
    path = require("path"),
    rename = require("gulp-rename"),
    sourcemaps = require("gulp-sourcemaps"),
    vulcanize = require("gulp-vulcanize");

var component_src = "src/elements";
var component_dst = "wwwroot/elements";
var component_tmp = "temp";

var paths = {
    css: path.join(component_src, "**", "*.css"),
    html: path.join(component_src, "**", "*.html"),
    less: path.join(component_src, "**", "*.less"),
    js: path.join(component_src, "**", "*.js")
};

// Task to just copy the source file to the destination area
gulp.task("components:copySRC", function () {
    return gulp.src([paths.html, paths.css])
        .pipe(gulp.dest(component_tmp));
})

// Task to pre-process less files into CSS
gulp.task("components:less", function () {
    return gulp.src(paths.less)
        .pipe(less())
        .pipe(autoprefixer("last 2 versions", "> 5%"))
        .pipe(minifycss())
        .pipe(gulp.dest(component_tmp));
});

gulp.task("components:js", function () {
    return gulp.src(paths.js)
        .pipe(babel())
        .pipe(gulp.dest(component_tmp));
});

gulp.task("build:components", [
    "components:copySRC",
    "components:less",
    "components:js"
], function () {
    return gulp.src(path.join(component_tmp, "**", "*.html"))
        .pipe(vulcanize({
            dest: component_dst,
            inline: true,
            strip: true
        }))
        .pipe(gulp.dest(component_dst));
});

gulp.task("clean:components", function () {
    return gulp.src([component_tmp, component_dst], { read: false })
        .pipe(clean());
});

Finally, I have one to re-build the jspm package cache in Gulp/libraries.js:

var gulp = require("gulp"),
    clean = require("gulp-clean"),
    exec = require("child_process").exec,
    path = require("path"),
    vulcanize = require("gulp-vulcanize");

gulp.task("libraries:jspm", function () {
    exec("jspm install", function (err, stdout, stderr) {
        console.log(stdout);
        console.error(stderr);
    });
});

gulp.task("libraries:polymer", ["libraries:jspm"], function () {
    var SRC_DIR = "./wwwroot/jspm_packages/github/Polymer/polymer@0.8.0",
        DEST_DIR = path.join(SRC_DIR, "dist");

    return gulp.src(path.join(SRC_DIR, "polymer.html"))
        .pipe(vulcanize({
            dest: DEST_DIR,
            inline: true,
            strip: true
        }))
        .pipe(gulp.dest(DEST_DIR));
});

gulp.task("build:libraries", ["libraries:jspm", "libraries:polymer"]);

gulp.task("clean:libraries", function () {
    return gulp.src("wwwroot/jspm_packages", { read: false })
        .pipe(clean());
});

Note that I also vulcanize the polymer.html as part of the build process now. This ensures I don’t have to drop out to the command line to re-build an environment.

I use a node.js package called require-dir to bring it all together. require-dir takes a directory and iterates over every file in the directory, requiring each one in turn. My new Gulpfile.js looks like this:

/// <binding BeforeBuild="build" />

var gulp = require("gulp"),
    requireDir = require("require-dir");

requireDir("./Gulp", { recurse: true });

gulp.task("build", [
    "build:components",
    "build:style",
    "build:images",
    "build:javascript",
    "build:libraries"
]);
gulp.task("clean", [
    "clean:components",
    "clean:style",
    "clean:images",
    "clean:javascript",
    "clean:libraries"
]);

Finally, I did add some dependencies, so my package.json needed to be updated. The devDependencies section now looks like this:

"devDependencies": {
    "gulp": "^3.8.11",
    "gulp-autoprefixer": "^2.1.0",
    "gulp-babel": "^5.0.0",
    "gulp-clean": "^0.3.1",
    "gulp-eslint": "^0.8.0",
    "gulp-imagemin": "^2.2.1",
    "gulp-less": "^3.0.2",
    "gulp-minify-css": "^1.0.0",
    "gulp-sourcemaps": "^1.5.1",
    "gulp-uglify": "^1.1.0",
    "gulp-vulcanize": "^5.0.0",
    "imagemin-pngquant": "^4.0.0",
    "require-dir": "^0.3.0"
  },

With this organization, I can add new task sections into my build process without changing the existing task descriptions. This is a win for organization and accidental deletions. Also, I feel more confident that my clean-up process does actually clean-up.

This code is checked in as tag cs-0.0.5.