cwonrails
6/18/2015 - 7:13 PM

The gulpfile we use for our webapp-in-android-app project

The gulpfile we use for our webapp-in-android-app project

var gulp = require('gulp'),
    // Used to process `include` statements in source files
    includer = require('gulp-includer'),
    // Compiles SASS files using LibSass
    sass = require('gulp-sass'),
    // Renames files in the file stream
    rename = require('gulp-rename'),
    // JavaScript minifier
    uglify = require('gulp-uglify'),
    // Filter files out of the vinyl stream
    filter = require('gulp-filter'),
    // Creates zip files
    zip = require('gulp-zip'),
    // CSS minifier
    csso = require('gulp-csso'),
    // Used for working with streams
    es = require('event-stream'),
    // File system utilities
    fs = require('fs'),
    // Search for files/directories matching certain patterns - regex-lite
    glob = require('glob'),
    // Used for working with file system paths
    path = require('path'),
    // Promise library used in more complex tasks (like `zip`)
    Q = require('q'); 

// TODO: Implement something like MEF, where we scan for particular JSON files, 
//   and then assemble this config by merging all of the contents.
var pathConfig = {

    ScriptSrc                  : 'Scripts/src',
    
    // These three core files are required by damn near everything, so instead 
    // of processing them over and over again, we'll process them once in the 
    // `core` task and then include the output files (i.e. the ones not under 
    // `/Scripts/src`) instead of the `src` version to cut down on needless 
    // repetition of the include process.
    models                     : 'Scripts/models',
    Graph                      : 'Scripts/graph',
    Report                     : 'Scripts/report',

    Collectors                 : 'Scripts/src/report/collectors',
    ReportTemplates            : 'Scripts/src/report/template',
    
    // Foo
    FooBasePkg                 : 'Packages/src/Bar',
    FooAuditPkg                : 'Packages/src/Bar/Foo Audit',
    FooSvaAuditPkg             : 'Packages/src/Bar/Foo SVA Audit',
    FooSvaUnitPkg              : 'Packages/src/Bar/Foo SVA Unit',
    FooUnitPkg                 : 'Packages/src/Bar/Foo Unit',
    FooDealerTradePkg          : 'Packages/src/Bar/Foo Dealer Trade',
    FooManualAuditPkg          : 'Packages/src/Bar/Foo Manual Audit',
    FooManualUnitPkg           : 'Packages/src/Bar/Foo Manual Unit',
    FooPartialInventoryAuditPkg: 'Packages/src/Bar/Foo Partial Inventory Audit',
    FooPartialInventoryUnitPkg : 'Packages/src/Bar/Foo Partial Inventory Unit',

    // Bar
    BarBasePkg                 : 'Packages/src/Bar',
    BarDscAuditPkg             : 'Packages/src/Bar/DSC Audit',
    BarDscUnitPkg              : 'Packages/src/Bar/DSC Unit',
    BarHondaAuditPkg           : 'Packages/src/Bar/Honda Audit',
    BarHondaAutoPkg            : 'Packages/src/Bar/Honda Auto',
    BarHondaMotoEtAlPkg        : 'Packages/src/Bar/Honda Moto, Utility, Watercraft and Marine',
    BarHondaPePkg              : 'Packages/src/Bar/Honda PE',
    BarManualAuditPkg          : 'Packages/src/Bar/Manual Audit',
    BarManualUnitPkg           : 'Packages/src/Bar/Manual Unit',

    // Test Client
    TestClientBasePkg          : 'Packages/src/TestClient',
    TestClientTcAuditPkg       : 'Packages/src/TestClient/TC Audit'

};

// Where to save our package zip files
var packageZipDest = '../../Shared';

    // Files to copy
var themeSourceFiles = [
        'Content/Layouts/Layout.html',
        'Content/themes/default.min.css'
    ],
    // Factor these out to make structure changes easier
    themeDestPrefix = 'Packages/bin',
    themeDestSuffix = '/Reporting',
    // All of the packages that require Layout.html and default.min.css
    themeDests = [
        '/Bar/DSC Audit',
        '/Bar/Honda Audit',
        '/Bar/Manual Audit',
        '/Bar/Foo Audit',
        '/Bar/Foo SVA Audit',
        '/Bar/Foo Manual Audit',
        '/Bar/Foo Partial Inventory Audit'
    ];

// Used by the `gulp-rename` plugin to rename `foo.combined.js` to `foo.min.js`.
var combined2min = function (path) {
    path.basename = path.basename.replace('.combined', '.min');
};

// We use the same includer options in every task, so just define them here to
// avoid code duplication.
var includerOpts = {
    // By default includer will wrap each include in an immediately-executing
    // closure, so override the `wrap` method with a simple identify function.
    wrap: function (src) { return src; },
    // Pass the aliases defined above to includer to resolve path mappings.
    paths: pathConfig
};

// Processes the three "core" files that are used in a majority of other
// packages.  This is a dependency of both the `scripts` and `packages` tasks,
// which allows the files handled by those tasks to include the output of this
// task when requiring any of these three files, rather than recursively
// building these files over and over again, which is what would happen if
// the `@Report`, `@models`, and `@Graph` mappings above pointing to the
// versions in the `Scripts/src` directory instead.
gulp.task('core', function () {
    // If we want tasks dependent on this task to wait until this task has 
    // completely finished before they themselves execute, we have to return
    // the pipeline.
    return gulp.src([
                './Scripts/src/**/models.combined.js',
                './Scripts/src/**/graph.combined.js',
                './Scripts/src/**/report.combined.js'
            ], 
            // Because nothing but includer actually needs to know the contents 
            // of the files (and it will handle its own reading), tell gulp not 
            // to bother reading the file content - just pass the paths along.
            { read: false }
        )
        .pipe(includer(includerOpts))
        .pipe(gulp.dest('./Scripts'));
});

// Processes includes in all JavaScript files under `Scripts/src` and writes
// the output to the base `Scripts` directory while preserving folder structure.
// Make sure the `core` task executes completely before this runs.
gulp.task('scripts', ['core'], function () {
    // Because the `core` task has already handled processing these three files,
    // filter them out from the file stream to avoid processing them again.
    var coreFilter = filter('**/!(models.combined|graph.combined|report.combined).js');

    return gulp.src('./Scripts/src/**/*.combined.js', { read: false })
        // Here we pass the file stream through the filter defined above.
        .pipe(coreFilter)
        // Now that we've filtered out the files we don't want to process,
        // pass the files that remain to includer.
        .pipe(includer(includerOpts))
        .pipe(gulp.dest('./Scripts'));
});

// Minifies all processed `*.combined.js` files in 'Scripts' (i.e. the files NOT
// in the `Scripts/src` directory, which are unprocessed) and writes them to 
// disk as `*.min.js`. Make sure the `scripts` task executes completely before
// this runs. Get the right fileset by first retrieving all *.combined.js files,
// then removing any files from `Scripts/src`.
gulp.task('scriptMin', ['scripts'], function () {
    return gulp.src(['Scripts/**/*.combined.js', '!Scripts/src{,/**}'])
        .pipe(uglify())
        .pipe(rename(combined2min))
        .pipe(gulp.dest('./Scripts'));
});

// Processes includes in all JavaScript files under `Packages/src` and writes 
// the output to the base `Packages/bin` directory while preserving the folder 
// structure. Make sure the `scripts` task executes completely before this runs.
gulp.task('packages', ['scripts'], function () {
    return gulp.src('./Packages/src/**/*.combined.js', { read: false })
        .pipe(includer(includerOpts))
        .pipe(gulp.dest('./Packages/bin'));
});

// Minifies all processed `*.combined.js` files in `Packages` (i.e. the files 
// NOT in the `Packages/src` directory, which are unprocessed) and writes them 
// to disk as `*.min.js`. Make sure the `packages` task executes completely
// before this runs. You can't use a directory negation in the middle of a glob
// (whoops) so instead you have to basically do a diff between "all combined 
// files" and "all files in Packages/src".
gulp.task('packageMin', ['packages'], function () {
    return gulp.src(['Packages/bin/**/*.combined.js'])
        .pipe(uglify())
        .pipe(rename(combined2min))
        .pipe(gulp.dest('./Packages/bin'));
});

// Copies all of the txt, JSON, html, etc. files that exist in the 
// `Packages/src` directory tree to the root `Packages` tree. Apparently 
// glob wildcards have some undocumented bugs, because this was previously
// just `gulp.src('Packages/src/!(*.js)')`, but that omitted .JSON files
// as well as .js files.
gulp.task('packageCopy', function () {
    return gulp.src(['Packages/src/**/*.*', '!Packages/src/**/*.js'])
        .pipe(gulp.dest('./Packages/bin'));
});

// Compile all `*.scss` files and write their output to the `/Content`
// directory.  The existing folder structure will be preserved.
gulp.task('sass', function () {
    return gulp.src('./Content/**/*.scss', { read: false })
        .pipe(sass({outputStyle: 'nested'}))
        .pipe(gulp.dest('./Content'));
});

// Minifies the CSS output by the SASS compiler, and saves the file as 
// filename.min.css. Makes sure the `SASS` task runs before this task executes.
gulp.task('cssMin', ['sass'], function () {
    return gulp.src('./Content/**/!(*.min).css', { read: false })
        .pipe(csso())
        .pipe(rename(function (path) {
            path.basename += '.min';
        }))
        .pipe(gulp.dest('./Content'));
});

// Copies the `Layout.html` and `default.min.css` files from the /Content dir
// into the package directories where they are used. Make sure the `sass` task
// executes completely before this runs.
gulp.task('themeDist', ['cssMin'], function () {
    var i, il, stream, merged;
    for (i=0, il=themeDests.length; i<il; i++) {
        stream = gulp.src(themeSourceFiles, { read: false })
            .pipe(gulp.dest(themeDestPrefix + themeDests[i] + themeDestSuffix));

        // merge the stream with stream(s) from previous loop iterations
        merged = (merged) ? es.merge(merged, stream) : stream;
    }
    // By returning this we can make dependent tasks wait for execution of this
    // task to finish before exeuting themselves.
    return merged;
});

// Creates the package zip files in the "Shared" directory.  Makes sure that the
// `packageMin`, `packageCopy`, and `themeDist` tasks execute completely before
// this runs to ensure zip content is always up-to-date.
gulp.task('zip', ['packageMin', 'packageCopy', 'themeDist'], function () {
    var promises = [];
        
    // First, get a list of all sub-directories in "Packages", omitting 'src'.
    // folderPath should be path to Packages/Foo, Packages/Bar, etc.
    glob.sync('Packages/bin/*').forEach(function (folderPath) {
        var bankName;
        // make sure it's a directory
        if (fs.statSync(folderPath).isDirectory()) {
            // Get just "Foo" or "Bar" etc.
            bankName = path.basename(folderPath);
            // Now find all sub-dirs (packages) of that bank
            glob.sync(folderPath + '/*').forEach(function (packagePath) {
                var defer = Q.defer(),
                    stream, packageName;
                // packagePath should be path to Foo/Foo Audit, Foo/SVA, etc
                // make sure we are only processing directories
                if (fs.statSync(packagePath).isDirectory()) {
                    // Okay, so now we have the bank name, the package name, and
                    // a path to the package files - time to zip stuff up and
                    // write it to disk
                    packageName = path.basename(packagePath);
                    // The glob pattern means "all files and folders inside
                    // a folder inside packagePath", and set packagePath as base
                    // so that folders will be relative to the package rather
                    // than the project root. Do NOT set read:false here, as the
                    // zip package relies on gulp to read the file contents.
                    stream = gulp.src(packagePath + '/*/**/!(*.combined.js|setup.min.js)', {
                            base: packagePath
                        })
                        .pipe(zip(packageName + '.zip'))
                        .pipe(gulp.dest(packageZipDest + '/' + bankName));
                    
                    // Make sure the deferred is resolved when done
                    stream.on('end', function () {
                        defer.resolve();
                    });
                    // add our promise to the array
                    promises.push(defer);
                }
            });
        }
    });

    // Return a promise that will resolve when all streams end
    return Q.all(promises);
});

// Watch task for active development.  Whenever a file is changed that matches 
// the glob pattern, run the specified task(s).
gulp.task('watch', function () {
    gulp.watch('./Scripts/src/**/*.js', ['scripts']);
    gulp.watch('./Packages/src/**/*.js', ['packages']);
    // The `packages` task will handle copying/creating all `*.js`, `*.min.js`,
    // and `*.combined.js` files to the base `Packages` directory, so use the
    // `!(pattern)` glob functionality to avoid re-copying those files.
    gulp.watch(['Packages/src/**/*.*', '!Packages/src/**/*.js'], ['packageCopy']);
    gulp.watch('./Content/**/*.scss', ['sass']);
});

// Watch task for active development on handhelds. Whenever a file is changed
// in Scripts, Packages, or a sass file in Content is updated, the `zip` task
// will run automatically.
gulp.task('zipWatch', function () {
    gulp.watch([
        './Scripts/src/**/*.js', 
        './Packages/src/**/*.*',
        './Content/**/*.scss'
    ], ['zip']);
});

// These tasks will run in series when calling gulp with no arguments.
gulp.task('default', ['scripts', 'packages', 'packageCopy', 'sass']);

// The minification tasks take WAY longer to run than anything else, so put them
// in a `build` task that will only be called when building the project.
gulp.task('build', ['scriptMin', 'zip']);

// Uncomment this to use node-debug on the `packageCopy` task. To do this, you'd first
// `npm install -g node-debug`, then change the task to whichever task you'd like to debug,
// then call `node-debug gulpfile.js`.
/* gulp.start('packageCopy'); */