//
var gulp = require('gulp'),
autoprefixer = require('gulp-autoprefixer'),
bump = require('gulp-bump'),
cdnizer = require('gulp-cdnizer'),
clean = require('gulp-clean'),
concat = require('gulp-concat'),
csso = require('gulp-csso'),
debug = require('gulp-debug'),
footer = require('gulp-footer'),
gutil = require('gulp-util'),
gzip = require('gulp-gzip'),
header = require('gulp-header'),
help = require('gulp-task-listing'),
_if = require('gulp-if'),
inject = require('gulp-inject'),
jshint = require('gulp-jshint'),
karma = require('gulp-karma'),
less = require('gulp-less'),
livereload = require('gulp-livereload'),
livereloadEmbed = require('gulp-embedlr'),
minifyHtml = require('gulp-minify-html'),
ngHtml2js = require('gulp-ng-html2js'),
ngmin = require('gulp-ngmin'),
plumber = require('gulp-plumber'),
recess = require('gulp-recess'),
rename = require('gulp-rename'),
rev = require('gulp-rev'),
tap = require('gulp-tap'),
uglify = require('gulp-uglify'),
watch = require('gulp-watch'),
_ = require('lodash'),
anysort = require('anysort'),
connect = require('connect'),
es = require('event-stream'),
fs = require('fs'),
http = require('http'),
lazypipe = require('lazypipe'),
open = require('open'),
path = require('path'),
runSequence = require('run-sequence'),
server = require('tiny-lr')();
//
/*
* TODO:
* - Banner for compressed JS/CSS
* - Changelog?
*/
// Load user config and package data
var cfg = require('./build.config.js');
// common variables
var concatName = cfg.pkg.name,
embedLR = false,
join = path.join;
//=============================================
// MAIN TASKS
//=============================================
gulp.task('default', ['watch']);
gulp.task('server-dist', ['compile'], function(cb) {
startServer(cfg.compileDir, cb);
});
gulp.task('server', ['watch'], function(cb) {
startServer(cfg.buildDir, cb);
});
gulp.task('build', function(cb) {
runSequence(['build-assets', 'build-scripts', 'build-styles'], ['build-html', 'test'], cb);
});
gulp.task('watch', ['lr-server', 'build', 'test-watch'], function() {
watch({glob: cfg.watchFiles.js, emitOnGlob: false, name: 'JS'})
.pipe(plumber())
.pipe(jsBuildTasks())
.pipe(livereload(server));
watch({glob: cfg.watchFiles.tpl, emitOnGlob: false, name: 'Templates'})
.pipe(plumber())
.pipe(tplBuildTasks())
.pipe(livereload(server));
watch({glob: cfg.watchFiles.html, emitOnGlob: false, name: 'HTML'}, function() {
return buildHTML();
});
watch({glob: cfg.watchFiles.less, emitOnGlob: false, name: 'Styles'}, function() {
// run this way to ensure that a failed pipe doesn't break the watcher.
return buildStyles();
});
watch({glob: cfg.watchFiles.assets, emitOnGlob: false, name: 'Assets'})
.pipe(gulp.dest(join(cfg.buildDir, cfg.assetsDir)));
});
gulp.task('compile', function(cb) {
runSequence(
'compile-clean',
['compile-assets', 'compile-scripts', 'compile-styles'],
'compile-html',
cb
);
});
gulp.task('clean', ['compile-clean', 'build-clean']);
gulp.task('help', help);
//=============================================
// UTILITIES
//=============================================
function readFile(filename) {
return fs.existsSync(filename) ? fs.readFileSync(filename, {encoding: 'utf8'}) : '';
}
function insertRevGlob(f) {
// allows for rev'd filenames (which end with /-[0-9a-f]{8}.ext/
return f.replace(/(\..*)$/, '?(-????????)$1');
}
function startServer(root, cb) {
var devApp, devServer, devAddress, devHost, url, log=gutil.log, colors=gutil.colors;
devApp = connect();
if(cfg.server.log) {
devApp.use(connect.logger(cfg.server.log===true ? 'dev' : cfg.server.log));
}
devApp.use(connect.static(root));
devServer = http.createServer(devApp).listen(cfg.server.port, cfg.server.host||undefined);
devServer.on('error', function(error) {
log(colors.underline(colors.red('ERROR'))+' Unable to start server!');
cb(error);
});
devServer.on('listening', function() {
devAddress = devServer.address();
devHost = devAddress.address === '0.0.0.0' ? 'localhost' : devAddress.address;
url = 'http://' + devHost + ':' + devAddress.port + join('/', cfg.indexFile);
log('');
log('Started dev server at '+colors.magenta(url));
var openByDefault = cfg.server.openByDefault;
if(gutil.env.open || (openByDefault && gutil.env.open !== false)) {
log('Opening dev server URL in browser');
if(openByDefault) {
log(colors.gray('(Run with --no-open to prevent automatically opening URL)'));
}
// Open the URL in the browser at this point.
open(url);
} else if(!openByDefault) {
log(colors.gray('(Run with --open to automatically open URL on startup)'));
}
log('');
cb();
});
}
//=============================================
// SUB TASKS
//=============================================
//---------------------------------------------
// HTML
//---------------------------------------------
var buildHTML = function() {
var htmlFile = readFile(join(cfg.buildDir, cfg.indexFile));
return gulp.src([join(cfg.buildDir, '/**/*.*'), '!' + join(cfg.buildDir, cfg.indexFile)], {read: false})
.pipe(plumber())
.pipe(inject(cfg.appFiles.html, {
addRootSlash: false,
sort: fileSorter, // see below
ignorePath: join('/',cfg.buildDir,'/')
}))
.pipe(_if(embedLR, livereloadEmbed({port: cfg.server.lrPort})))
.pipe(gulp.dest(cfg.buildDir))
.pipe(tap(function(file) {
var newHtmlFile = file.contents.toString();
if(newHtmlFile !== htmlFile) {
htmlFile = newHtmlFile;
gulp.src(file.path).pipe(livereload(server));
}
}));
};
gulp.task('build-html', function() {
// NOTE: this task does NOT depend on buildScripts and buildStyles,
// therefore, it may incorrectly create the HTML file if called
// directly.
return buildHTML();
});
gulp.task('compile-html', function() {
// NOTE: this task does NOT depend on compileScripts and compileStyles,
// therefore, it may incorrectly create the HTML file if called
// directly.
return gulp.src([join(cfg.compileDir, '/**/*.*'), '!' + join(cfg.compileDir, cfg.indexFile)], {read: false})
.pipe(inject(cfg.appFiles.html, {
addRootSlash: false,
sort: fileSorter, // see below
ignorePath: join('/', cfg.compileDir, '/')
}))
.pipe(cdnizer(cfg.cdn))
.pipe(minifyHtml({empty:true,spare:true,quotes:true}))
.pipe(gulp.dest(cfg.compileDir))
.pipe(gzip())
.pipe(gulp.dest(cfg.compileDir))
});
// used by build-html to ensure correct file order during builds
var fileSorter = (function(){
var as = anysort(_.flatten([
// JS files are sorted by original vendor order, common, app, then everything else
cfg.vendorFiles.js.map(function(f){ return join(cfg.jsDir, insertRevGlob(f)); }),
cfg.vendorFiles.jsConcat.map(function(f){ return join(cfg.jsDir, insertRevGlob(f)); }),
join(cfg.jsDir, 'common/**/*.js'),
join(cfg.jsDir, 'app/**/!(app)*.js'), // exclude app.js so it comes last
join(cfg.jsDir, '**/*.js'),
// CSS order should be maintained via Less includes
join(cfg.cssDir, '**/*.css')
]));
return function(a,b){ return as(a.filepath, b.filepath) || a.filepath < b.filepath };
})();
//---------------------------------------------
// JavaScript
//---------------------------------------------
var jsFiles = function() { return gulp.src(cfg.appFiles.js); },
jsBaseTasks = lazypipe()
.pipe(plumber) // jshint won't render parse errors without plumber
.pipe(function() {
return jshint(_.clone(cfg.taskOptions.jshint));
})
.pipe(jshint.reporter, 'jshint-stylish'),
jsBuildTasks = jsBaseTasks
.pipe(gulp.dest, join(cfg.buildDir, cfg.jsDir)),
tplFiles = function() { return gulp.src(cfg.appFiles.tpl); },
tplBuildTasks = lazypipe()
.pipe(ngHtml2js, {moduleName: 'templates'})
.pipe(gulp.dest, join(cfg.buildDir, cfg.jsDir, cfg.templatesDir));
//noinspection FunctionWithInconsistentReturnsJS
gulp.task('build-scripts-vendor', function() {
var vendorFiles = [].concat(cfg.vendorFiles.js||[], cfg.vendorFiles.jsConcat||[]);
if(vendorFiles.length) {
return gulp.src(vendorFiles, {base: cfg.vendorDir})
.pipe(gulp.dest(join(cfg.buildDir, cfg.jsDir, cfg.vendorDir)))
}
});
gulp.task('build-scripts-app', function() {
return jsFiles().pipe(jsBuildTasks());
});
gulp.task('build-scripts-templates', function() {
return tplFiles().pipe(tplBuildTasks());
});
gulp.task('build-scripts', ['build-scripts-vendor', 'build-scripts-app', 'build-scripts-templates']);
gulp.task('compile-scripts', function() {
var appFiles, templates, files, concatFiles, vendorFiles;
appFiles = jsFiles()
.pipe(jsBaseTasks())
.pipe(concat('appFiles.js')) // not used
.pipe(ngmin())
.pipe(header(cfg.moduleWrapper.header))
.pipe(footer(cfg.moduleWrapper.footer));
templates = tplFiles()
.pipe(minifyHtml({empty: true, spare: true, quotes: true}))
.pipe(ngHtml2js({moduleName: 'templates'}))
.pipe(concat('templates.min.js')); // not used
files = [appFiles, templates];
if(cfg.vendorFiles.jsConcat.length) {
files.unshift(gulp.src(cfg.vendorFiles.jsConcat));
}
concatFiles = es.concat.apply(es, files)
.pipe(concat(concatName + '.js'))
.pipe(uglify(cfg.taskOptions.uglify))
.pipe(rev())
.pipe(gulp.dest(join(cfg.compileDir, cfg.jsDir)))
.pipe(gzip())
.pipe(gulp.dest(join(cfg.compileDir, cfg.jsDir)));
if(cfg.vendorFiles.js.length) {
vendorFiles = gulp.src(cfg.vendorFiles.js, {base: cfg.vendorDir})
.pipe(uglify(cfg.taskOptions.uglify))
.pipe(rev())
.pipe(gulp.dest(join(cfg.compileDir, cfg.jsDir, cfg.vendorDir)))
.pipe(gzip())
.pipe(gulp.dest(join(cfg.compileDir, cfg.jsDir, cfg.vendorDir)));
return es.concat(vendorFiles, concatFiles);
} else {
return concatFiles;
}
});
//---------------------------------------------
// Less / CSS Styles
//---------------------------------------------
var styleFiles = function() { return gulp.src(cfg.appFiles.less); },
styleBaseTasks = lazypipe()
.pipe(recess, cfg.taskOptions.recess)
.pipe(less, cfg.taskOptions.less)
.pipe(autoprefixer),
buildStyles = function() {
return styleFiles()
.pipe(
styleBaseTasks()
// need to manually catch errors on recess/less :-(
.on('error', function() {
gutil.log(gutil.colors.red('Error')+' processing Less files.');
})
)
.pipe(gulp.dest(join(cfg.buildDir, cfg.cssDir)))
.pipe(livereload(server))
};
gulp.task('build-styles', function() {
return buildStyles();
});
gulp.task('compile-styles', function() {
return styleFiles()
.pipe(styleBaseTasks())
.pipe(rename(concatName + '.css'))
.pipe(csso(cfg.taskOptions.csso))
.pipe(rev())
.pipe(gulp.dest(join(cfg.compileDir, cfg.cssDir)))
.pipe(gzip())
.pipe(gulp.dest(join(cfg.compileDir, cfg.cssDir)))
});
//---------------------------------------------
// Unit Testing
//---------------------------------------------
var testFiles = function() {
return gulp.src(_.flatten([cfg.vendorFiles.js, cfg.vendorFiles.jsConcat, cfg.testFiles.js, cfg.appFiles.jsunit]));
};
gulp.task('test', ['build-scripts'], function() {
return testFiles()
.pipe(karma({
configFile: cfg.testFiles.config
}))
});
gulp.task('test-watch', ['build-scripts', 'test'], function() {
// NOT returned on purpose!
testFiles()
.pipe(karma({
configFile: cfg.testFiles.config,
action: 'watch'
}))
});
//---------------------------------------------
// Assets
//---------------------------------------------
// If you want to automate image compression, or font creation,
// this is the place to do it!
//noinspection FunctionWithInconsistentReturnsJS
gulp.task('build-assets-vendor', function() {
if(cfg.vendorFiles.assets.length) {
return gulp.src(cfg.vendorFiles.assets, {base: cfg.vendorDir})
.pipe(gulp.dest(join(cfg.buildDir, cfg.assetsDir, cfg.vendorDir)))
}
});
gulp.task('build-assets', ['build-assets-vendor'], function() {
return gulp.src(cfg.appFiles.assets)
.pipe(gulp.dest(join(cfg.buildDir, cfg.assetsDir)))
});
//noinspection FunctionWithInconsistentReturnsJS
gulp.task('compile-assets-vendor', function() {
if(cfg.vendorFiles.assets.length) {
return gulp.src(cfg.vendorFiles.assets, {base: cfg.vendorDir})
.pipe(gulp.dest(join(cfg.compileDir, cfg.assetsDir, cfg.vendorDir)))
}
});
gulp.task('compile-assets', ['compile-assets-vendor'], function() {
return gulp.src(cfg.appFiles.assets)
.pipe(gulp.dest(join(cfg.compileDir, cfg.assetsDir)))
});
//---------------------------------------------
// Miscellaneous Tasks
//---------------------------------------------
gulp.task('lr-server', function() {
embedLR = true;
server.listen(cfg.server.lrPort, function(err) {
if(err) {
console.log(err);
}
gutil.log('Started LiveReload server');
});
});
gulp.task('build-clean', function() {
return gulp.src(cfg.buildDir, {read: false}).pipe(clean());
});
gulp.task('compile-clean', function() {
return gulp.src(cfg.compileDir, {read: false}).pipe(clean());
});
gulp.task('bump', function() {
var version = gutil.env['set-version'],
type = gutil.env.type,
msg = version ? ('Setting version to '+gutil.colors.yellow(version)) : ('Bumping '+gutil.colors.yellow(type||'patch') + ' version');
gutil.log('');
gutil.log(msg);
gutil.log(gutil.colors.gray('(You can set a specific version using --set-version ,'));
gutil.log(gutil.colors.gray(' or a specific type using --type )'));
gutil.log('');
return gulp.src(['./package.json', cfg.bowerJSON])
.pipe(bump({type:type, version:version}))
.pipe(gulp.dest('./'));
});