Skip to content

Instantly share code, notes, and snippets.

@OverZealous
Last active June 21, 2021 04:12
Show Gist options
  • Save OverZealous/8551946 to your computer and use it in GitHub Desktop.
Save OverZealous/8551946 to your computer and use it in GitHub Desktop.
Preliminary Gulpfile for replicating ngBoilerplate — Still a work in progress!
/**
* This file/module contains all configuration for the build process.
*/
/**
* Load requires and directory resources
*/
var join = require('path').join,
bowerrc = JSON.parse(require('fs').readFileSync('./.bowerrc', {encoding: 'utf8'})),
bowerJSON = bowerrc.json.replace(/^\.?\/?/, './'),
bower = require(bowerJSON),
pkg = require('./package.json'),
/**
* The `buildDir` folder is where our projects are compiled during
* development and the `compileDir` folder is where our app resides once it's
* completely built.
*/
buildDir = 'build',
compileDir = 'compile',
vendorDir = bowerrc.directory,
templatesDir = 'templates',
indexFile = 'index.html',
jsDir = 'js',
cssDir = 'css',
assetsDir = 'assets';
module.exports = {
buildDir: buildDir,
compileDir: compileDir,
// Relative paths to core files and folders for input and output
indexFile: indexFile,
jsDir: jsDir,
cssDir: cssDir,
assetsDir: assetsDir,
vendorDir: vendorDir,
templatesDir: templatesDir,
// allows settings reuse from package.json and bower.json
bowerJSON: bowerJSON,
bower: bower,
pkg: pkg,
/**
* This code is wrapped around the application code. It is used to "protect"
* the application code from the global scope.
*/
moduleWrapper: {
header: '\n(function ( window, angular, undefined ) {\n\n',
footer: '\n})( window, window.angular );'
},
/**
* Settings for the server task
* When run, this task will start a connect server on
* your build directory, great for livereload
*/
server: {
port: 8081, // 0 = random port
host: null, // null/falsy means listen to all, but will auto open localhost
// Enable disable default auto open
// false: run with --open to open
// true: run with --no-open to not open, recommended if port is 0
openByDefault: false,
// set to false to prevent request logging
// set to any non-`true` value to configure the logger
log: false,
// Live Reload server port
lrPort: 35729
},
/**
* Options passed into the various tasks.
* These are usually passed directly into the individual gulp plugins
*/
taskOptions: {
csso: false, // set to true to prevent structural modifications
jshint: {
eqeqeq: true,
camelcase: true,
freeze: true,
immed: true,
latedef: true,
newcap: true,
undef: true,
unused: true,
browser: true,
globals: {
angular: false,
console: false
}
},
less: {},
recess: {
strictPropertyOrder: false,
noOverqualifying: false,
noUniversalSelectors: false
},
uglify: {}
},
/**
* This is a collection of file patterns that refer to our app code (the
* stuff in `src/`). These file paths are used in the configuration of
* build tasks.
*
* js - All project javascript, less tests
* jsunit - All the JS needed to run tests (in this setup, it uses the build results)
* tpl - contains our various templates
* html - just our main HTML file
* less - our main stylesheet
* assets - the rest of the files that are copied directly to the build
*/
appFiles: {
js: [ 'src/**/!(app)*.js', 'src/**/*.js', '!src/**/*.spec.js', '!src/assets/**/*.js' ],
jsunit: [ join(buildDir, '/**/*.js'), 'src/**/*.spec.js', '!'+join(buildDir,'/assets/**/*.js'), '!'+join(buildDir, vendorDir, '**/*.js') ],
tpl: [ 'src/app/**/*.tpl.html', 'src/common/**/*.tpl.html' ],
html: join('src', indexFile),
less: 'src/less/main.less',
assets: join('src', assetsDir, '**/*.*')
},
/**
* Similar to above, except this is the pattern of files to watch
* for live build and reloading.
*/
watchFiles: {
js: [ 'src/**/*.js', '!src/**/*.spec.js', '!src/assets/**/*.js' ],
//jsunit: [ 'src/**/*.spec.js' ], // watch is handled by the karma plugin!
tpl: [ 'src/app/**/*.tpl.html', 'src/common/**/*.tpl.html' ],
html: [ join(buildDir, '**/*'), '!'+join(buildDir,indexFile), join('src',indexFile) ],
less: [ 'src/**/*.less' ],
assets: join('src',assetsDir,'**/*.*')
},
/**
* This is a collection of files used during testing only.
*/
testFiles: {
config: 'karma/karma.conf.js',
js: [
'vendor/angular-mocks/angular-mocks.js'
]
},
/**
* This contains files that are provided via bower.
* Vendor files under `js` are copied and minified, but not concatenated into the application
* js file.
* Vendor files under `jsConcat` are included in the application js file.
* Vendor files under `assets` are simply copied into the assets directory.
*/
vendorFiles: {
js: [
'vendor/angular/angular.js',
'vendor/firebase/firebase.js',
'vendor/angularfire/angularfire.js'
],
jsConcat: [
'vendor/angular-bootstrap/ui-bootstrap-tpls.min.js',
'vendor/angular-ui-router/release/angular-ui-router.js',
'vendor/angular-ui-utils/modules/route/route.js'
],
assets: [
]
},
/**
* This contains details about files stored on a CDN, using gulp-cdnizer.
* file: glob or filename to match for replacement
* package: used to look up the version info of a bower package
* test: if provided, this will be used to fallback to the local file if the CDN fails to load
* cdn: template for the CDN filename
*/
cdn: [
{
file: 'js/vendor/angular/angular.js',
package: 'angular',
test: 'window.angular',
cdn: '//ajax.googleapis.com/ajax/libs/angularjs/${ major }.${ minor }.${ patch }/angular.min.js'
}
]
};
//<editor-fold desc="Node Requires, gulp, etc">
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')();
//</editor-fold>
/*
* 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 <version>,'));
gutil.log(gutil.colors.gray(' or a specific type using --type <type>)'));
gutil.log('');
return gulp.src(['./package.json', cfg.bowerJSON])
.pipe(bump({type:type, version:version}))
.pipe(gulp.dest('./'));
});
{
"author": "Phil DeJarnett",
"name": "example",
"version": "0.0.1",
"homepage": "http://www.overzealous.com",
"licenses": {
"type": "",
"url": ""
},
"bugs": "",
"repository": {
"type": "git",
"url": ""
},
"dependencies": {},
"devDependencies": {
"anysort": "~0.1.1",
"event-stream": "*",
"gulp": "3.*",
"gulp-autoprefixer": "*",
"gulp-cdnizer": "*",
"gulp-clean": "*",
"gulp-concat": "*",
"gulp-csso": "*",
"gulp-debug": "*",
"gulp-embedlr": "~0.5.2",
"gulp-footer": "*",
"gulp-gzip": "0.0.4",
"gulp-header": "*",
"gulp-if": "*",
"gulp-inject": "*",
"gulp-jshint": "*",
"gulp-karma": "*",
"gulp-less": "*",
"gulp-livereload": "*",
"gulp-minify-html": "*",
"gulp-ng-html2js": "*",
"gulp-ngmin": "*",
"gulp-plumber": "*",
"gulp-recess": "*",
"gulp-rename": "*",
"gulp-rev": "*",
"gulp-tap": "*",
"gulp-task-listing": "*",
"gulp-uglify": "*",
"gulp-util": "*",
"gulp-watch": "*",
"jshint-stylish": "*",
"lazypipe": "*",
"lodash": "~2.4.1",
"run-sequence": "*",
"tiny-lr": "0.0.5",
"connect": "~2.12.0",
"open": "0.0.4",
"gulp-bump": "~0.1.4"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment