Skip to content

Instantly share code, notes, and snippets.

@dcousineau
Created April 9, 2015 14:07
Show Gist options
  • Save dcousineau/1476362bdd3b4793a90a to your computer and use it in GitHub Desktop.
Save dcousineau/1476362bdd3b4793a90a to your computer and use it in GitHub Desktop.
Gulp Build System
"use strict";
var _ = require('underscore')
, nodeResolve = require('resolve')
, gulp = require('gulp')
, gutil = require('gulp-util')
, browserify = require('browserify')
, watchify = require('watchify')
, babelify = require('babelify')
, source = require('vinyl-source-stream')
, buffer = require('vinyl-buffer')
, uglify = require('gulp-uglify')
, size = require('gulp-size')
, rename = require('gulp-rename')
, less = require('gulp-less')
, sourcemaps = require('gulp-sourcemaps')
, gulpif = require('gulp-if')
, minifyCSS = require('gulp-minify-css')
, notify = require('gulp-notify')
, clean = require('gulp-clean')
, hbsfy = require('hbsfy')
, jshint = require('gulp-jshint')
, ignore = require('gulp-ignore')
, svgstore = require('gulp-svgstore')
, svgmin = require('gulp-svgmin')
, jscs = require('gulp-jscs')
, replace = require('gulp-replace')
, runSequence = require('run-sequence')
, stripify = require('stripify')
;
var env = process.env.NODE_ENV || 'dev'
, production = (env == 'prod')
, destPath = './application/static/build'
;
////
// General
////
gulp.task('default', function(cb) {
runSequence(
'clean',
['watch-js', 'watch-less', 'watch-svg'],
cb
);
});
//run before deployments
gulp.task('build', function(cb) {
runSequence(
'clean',
['js', 'less', 'svgstore'],
cb
);
});
////
// Style/CSS Tasks
////
gulp.task('less', function() {
return gulp.src('application/static/src/less/core.less')
.pipe(sourcemaps.init())
.pipe(less({
paths: ['node_modules', 'application/static/src/less']
}).on('error', notify.onError({
message: "Error: <%= error.message %>"
, title: "LESS Compile Error"
})))
.pipe(gulpif(!production, sourcemaps.write('.')))
.pipe(gulp.dest(destPath + '/css'))
.pipe(buffer())
.pipe(minifyCSS({
keepSpecialComments: 0 //Drop everything including licensing comments, we will list in app
}))
.pipe(rename('core.min.css'))
.pipe(gulp.dest(destPath + '/css'))
;
});
gulp.task('watch-less', ['less'], function() {
return gulp.watch('application/static/src/less/**/*.less', ['less']);
});
////
// Javascript Tasks
////
function getNPMPackageIds() {
// read package.json and get dependencies' package ids
var packageManifest = {};
try {
var packageConfig = nodeResolve.sync('./package.json');
if (require.cache[packageConfig]) {
delete require.cache[packageConfig];
}
packageManifest = require(packageConfig);
} catch (e) {
// does not have a package.json manifest
}
return _.keys(packageManifest.dependencies) || [];
}
gulp.task('vendor-js', function() {
var entryFile = __dirname + '/application/static/src/js/__vendor__.js';
var bundler = browserify({
entries: [entryFile]
, paths: [__dirname + '/node_modules', __dirname + '/application/static/src/js/vendor']
, debug: !production
});
bundler
.transform(hbsfy)
;
// do the similar thing, but for npm-managed modules.
// resolve path using 'resolve' module
getNPMPackageIds().forEach(function (id) {
//Skip projects we will inject globally
if (['bootstrap-select'].indexOf(id) !== -1) return;
//Handle unpatched projects
if (id == 'vintagejs') {
bundler.require('vintagejs/dist/vintage', { expose: 'vintagejs/dist/vintage' });
return;
}
gutil.log(gutil.colors.gray("Adding Vendor:"), gutil.colors.dim(id));
bundler.require(nodeResolve.sync(id), { expose: id });
});
bundler.require(entryFile);
var bundle = bundler
.bundle()
.pipe(source(entryFile))
.pipe(rename(destPath + '/js/vendor.js'))
.pipe(gulp.dest('./'))
;
!production || bundle
.pipe(buffer())
.pipe(uglify())
.pipe(rename({extname: '.min.js'}))
.pipe(gulp.dest('./'))
;
return bundle;
});
function buildApp(watch) {
var entryFile = __dirname + '/application/static/src/js/__init__.js';
var bundler = browserify({
debug: !production
, extensions: ['.js', '.hbs']
, paths: [__dirname + '/application/static/src/js']
, cache: {}
, packageCache: {}
, fullPaths: false
});
if (watch) {
bundler = watchify(bundler);
}
getNPMPackageIds().forEach(function (id) {
//For now skip bootstrap-select and manually include it if need be
if (['bootstrap-select'].indexOf(id) !== -1) return;
//Handle unpatched projects
if (id == 'vintagejs') {
bundler.external('vintagejs/dist/vintage', { expose: 'vintagejs/dist/vintage' });
return;
}
//bundler.external(nodeResolve.sync(id), { expose: id })
bundler.external(id, {expose: id});
});
//Initialize browserify bundler and setup transforms
bundler
.transform(hbsfy)
.transform(babelify.configure({
//loose: ['es6.classes']
}))
;
//Load stripify on production builds to kill any console
!production || bundler.transform(stripify);
//Setup entry point and error reporting
bundler
.add(entryFile)
.on('error', function(e) {
var message = e.message.replace(__dirname + '/application/static/src/js/', '');
notify.onError({
message: message
, title: "JS Error"
})(e);
this.emit('end');
})
;
var rebundle = function() {
var bundle = bundler
.bundle()
.on('error', function(e) {
var message = e.message.replace(__dirname + '/application/static/src/js/', '');
if(process.platform === 'linux'){
message = colorReplace(message, '');
}
notify.onError({
message: message
, title: "JS Error"
})(e);
this.emit('end');
})
.pipe(source(entryFile))
.pipe(rename(destPath + '/js/app.js'))
.pipe(gulp.dest('./'))
;
!production || bundle
.pipe(buffer())
.pipe(uglify())
.pipe(rename({extname: '.min.js'}))
.pipe(gulp.dest('./'))
;
return bundle;
};
if (watch) {
bundler
.on('update', function(){
runSequence(
['lint-js', 'jscs'],
function() {
//noop on error
}
);
gutil.log("Rebuilding...");
return rebundle();
})
.on('time', function(time) {
gutil.log('Rebuilt in', gutil.colors.magenta('' + time/1000 + ' s'));
})
;
}
return rebundle();
}
gulp.task('watch-vendor-js', ['vendor-js'], function() {
return gulp.watch([
'application/static/src/js/__vendor__.js',
'package.json'
], ['vendor-js']);
});
gulp.task('app-js', function() {
return buildApp(false);
});
gulp.task('watch-app-js', function(){
return buildApp(true);
});
gulp.task('watch-js', ['watch-vendor-js', 'watch-app-js']);
gulp.task('js', ['vendor-js', 'app-js']);
gulp.task('clean', function () {
return gulp.src([
destPath + '/js/*.js',
destPath + '/css/*.css',
destPath + '/css/*.map',
destPath + '/svg/*.svg',
'./application/templates/svg/*.svg',
'./application/static/compiled-includes/svg/*.svg'
], {read: false})
.pipe(clean());
});
gulp.task('lint-js', function () {
return gulp.src(['application/static/src/js/**/*.js'])
.pipe(ignore.exclude(/js\/vendor\//)) //Ignore vendor files.
.pipe(ignore.exclude(/sandbox/i))
.pipe(jshint({
browser: true,
browserify: true,
jquery: true,
nonstandard: true,
devel: true,
globals: {
"ga": true,
"FB": true,
"Instajam": true,
"gapi": true,
"tracking": true
},
esnext: true,
curly: false, //Consider enabling to disallow single line if statements
freeze: true,
latedef: "nofunc",
laxcomma: true,
undef: true,
eqnull: true,
eqeqeq: false,
"-W041": false //Disable the warning about requiring === with 0
}))
.pipe(notify(function (file) {
if (file.jshint.success) {
// Don't show something if success
return false;
}
var errors = file.jshint.results.map(function (data) {
if (data.error) {
return "(" + data.error.line + ':' + data.error.character + ') ' + data.error.reason;
}
}).join("\n");
return file.relative + " (" + file.jshint.results.length + " errors)\n" + errors;
}))
.pipe(jshint.reporter('jshint-stylish'))
.pipe(jshint.reporter('fail'))
;
});
gulp.task('jscs', function() {
return gulp.src(['application/static/src/js/**/*.js'])
.pipe(ignore.exclude(/js\/vendor\//)) //Ignore vendor files.
.pipe(ignore.exclude(/sandbox/i))
.pipe(jscs())
;
});
gulp.task('lint', ['lint-js', 'jscs']);
////
// SVG Tasks
////
gulp.task('svgstore', function () {
return gulp.src('application/static/src/svg/*.svg')
.pipe(svgmin().on('error', function(e) {
gutil.log(gutil.colors.red("SVG Minify Error:"), e.message, gutil.colors.red("(Likely empty file)"));
return false;
}))
.pipe(svgstore({
inlineSvg: true
}))
// make sure that the SVG does not show itself on render
// ToDo: This is causing problems when interacting with the SVG.
// More info here https://github.com/w0rm/gulp-svgstore#transform-combined-svg:
.pipe(replace(/<svg/, '<svg display="none" version="1.1" xmlns="http://www.w3.org/2000/svg"'))
.pipe(rename('app.svg'))
.pipe(gulp.dest('./application/static/compiled-includes/svg'))
});
gulp.task('watch-svg', ['svgstore'], function() {
return gulp.watch('application/static/src/svg/*.svg', ['svgstore']);
});
////
// Util
////
// On linux the colors sometimes cause problems with the notification.
function colorReplace(input, replace) {
var replaceColors = {
"0;31" : "{r",
"1;31" : "{R",
"0;32" : "{g",
"1;32" : "{G",
"0;33" : "{y",
"1;33" : "{Y",
"0;34" : "{b",
"1;34" : "{B",
"0;35" : "{m",
"1;35" : "{M",
"0;36" : "{c",
"1;36" : "{C",
"0;37" : "{w",
"1;37" : "{W",
"1;30" : "{*",
"0" : "{x"
};
if ( replace )
{
for( var k in replaceColors )
{
var re = new RegExp( "\\033\\[[" + k + "]*m", "g" );
input = input.replace( re, replaceColors[ k ] );
}
} else {
input = input.replace( /\033\[[0-9;]*m/g, "" );
}
return input;
};
{
"dependencies": {
"backbone": "^1.1.2",
"backbone.marionette": "^2.2.2",
"bootstrap": "^3.3.1",
"bootstrap-select": "^1.6.3",
"cookie-cutter": "^0.1.1",
"cropper": "^0.9.1",
"fabric": "^1.4.13",
"handlebars": "^1.3.0",
"jquery": "^2.1.3",
"slick-carousel": "^1.3.15",
"sortablejs": "^1.1.1",
"tinyscrollbar": "^2.4.1",
"underscore": "^1.8.2",
"video.js": "^4.11.3",
"vintagejs": "^1.1.4"
},
"devDependencies": {
"babelify": "^5.0.4",
"browserify": "^7.0.0",
"browserify-shim": "^3.8.1",
"cheerio": "^0.18.0",
"glob": "^4.3.1",
"gulp": "^3.8.10",
"gulp-clean": "^0.3.1",
"gulp-if": "^1.2.5",
"gulp-ignore": "^1.2.1",
"gulp-jscs": "^1.4.0",
"gulp-jshint": "^1.9.0",
"gulp-less": "^3.0.1",
"gulp-minify-css": "^0.5.1",
"gulp-notify": "^2.2.0",
"gulp-rename": "^1.2.0",
"gulp-replace": "~0.5.3",
"gulp-size": "^1.2.1",
"gulp-sourcemaps": "^1.2.8",
"gulp-svgmin": "^1.1.1",
"gulp-svgstore": "^5.0.0",
"gulp-uglify": "^1.0.2",
"gulp-util": "^3.0.4",
"hbsfy": "^2.2.1",
"jshint-stylish": "^1.0.0",
"resolve": "^1.1.5",
"run-sequence": "^1.0.2",
"stripify": "^3.0.0",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.0.0",
"watchify": "^2.6.2"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment