Skip to content

Instantly share code, notes, and snippets.

@isochronous
Last active May 2, 2016 10:13
Show Gist options
  • Save isochronous/65fa6031e2bf5ad8b6c4 to your computer and use it in GitHub Desktop.
Save isochronous/65fa6031e2bf5ad8b6c4 to your computer and use it in GitHub Desktop.
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'); */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment