Skip to content

Instantly share code, notes, and snippets.

@OverZealous
Last active June 21, 2021 04:12
Show Gist options
  • Select an option

  • Save OverZealous/8551946 to your computer and use it in GitHub Desktop.

Select an option

Save OverZealous/8551946 to your computer and use it in GitHub Desktop.

Revisions

  1. OverZealous revised this gist Feb 4, 2014. 3 changed files with 202 additions and 103 deletions.
    87 changes: 63 additions & 24 deletions build.config.js
    Original file line number Diff line number Diff line change
    @@ -2,9 +2,13 @@
    * 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'})),
    bower = require(bowerrc.json.replace(/^\.?\/?/, './')),
    bowerJSON = bowerrc.json.replace(/^\.?\/?/, './'),
    bower = require(bowerJSON),
    pkg = require('./package.json'),

    /**
    @@ -34,9 +38,19 @@ module.exports = {
    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
    @@ -53,9 +67,16 @@ module.exports = {

    // set to false to prevent request logging
    // set to any non-`true` value to configure the logger
    log: true
    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: {
    @@ -85,14 +106,17 @@ module.exports = {
    /**
    * 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` is all project javascript, less tests. `ctpl` contains
    * our reusable components' (`src/common`) template HTML files, while
    * `atpl` contains the same, but for our app's code. `html` is just our
    * main HTML file, `less` is our main stylesheet, and `unit` contains our
    * app's unit tests.
    * 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/**/*.js', '!src/**/*.spec.js', '!src/assets/**/*.js' ],
    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' ],
    @@ -128,25 +152,40 @@ module.exports = {
    },

    /**
    * This is the same as `app_files`, except it contains patterns that
    * reference vendor code (`vendor/`) that we need to place into the build
    * process somewhere. While the `app_files` property ensures all
    * standardized files are collected for compilation, it is the user's job
    * to ensure non-standardized (i.e. vendor-related) files are handled
    * appropriately in `vendorFiles.js`.
    *
    * The `vendorFiles.js` property holds files to be automatically
    * concatenated and minified with our project source files.
    *
    * The `vendorFiles.assets` property holds any assets to be copied along
    * with our app's assets. This structure is flattened, so it is not
    * recommended that you use wildcards.
    * 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/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'
    }
    ]
    };
    212 changes: 135 additions & 77 deletions gulpfile.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,8 @@
    //<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'),
    @@ -24,7 +26,6 @@ var gulp = require('gulp'),
    recess = require('gulp-recess'),
    rename = require('gulp-rename'),
    rev = require('gulp-rev'),
    runSequence = require('gulp-run-sequence'),
    tap = require('gulp-tap'),
    uglify = require('gulp-uglify'),
    watch = require('gulp-watch'),
    @@ -38,14 +39,23 @@ var gulp = require('gulp'),
    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;
    var concatName = cfg.pkg.name,
    embedLR = false,
    join = path.join;



    @@ -55,78 +65,47 @@ var concatName = cfg.pkg.name;

    gulp.task('default', ['watch']);

    gulp.task('server', ['watch'], function() {
    var devConnect, devServer, devAddress, devHost, url;
    devConnect = connect();
    if(cfg.server.log) {
    var type = cfg.server.log===true ? 'dev' : cfg.server.log;
    devConnect.use(connect.logger(type));
    }
    devConnect.use(connect.static(cfg.buildDir));

    devServer = http.createServer(devConnect).listen(cfg.server.port, cfg.server.host||undefined);
    devServer.on('error', function(error) {
    gutil.log('');
    gutil.log(gutil.colors.underline(gutil.colors.red('ERROR'))+' Unable to start server!');
    gutil.log(gutil.colors.red(error.toString()));
    gutil.log('');
    });
    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);

    gutil.log('');
    gutil.log('Started dev server at '+gutil.colors.magenta(url));
    if((cfg.server.openByDefault && !gulp.env.noOpen) ||
    (!cfg.server.openByDefault && gulp.env.open)) {
    var msg = 'Opening dev server URL in browser';
    if(cfg.server.openByDefault) {
    msg += ' (Run with --no-open to prevent automatically opening URL)';
    }
    gutil.log(msg);
    open(url);
    } else if(!cfg.server.openByDefault) {
    gutil.log('(Run with --open to automatically open URL on startup)');
    }
    gutil.log('');
    });
    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'], function() {
    watch({glob: cfg.watchFiles.js, emitOnGlob: false})
    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})
    watch({glob: cfg.watchFiles.tpl, emitOnGlob: false, name: 'Templates'})
    .pipe(plumber())
    .pipe(tplBuildTasks())
    .pipe(livereload(server));

    watch({glob: cfg.watchFiles.html, emitOnGlob: false}, function() {
    gulp.run('build-html');
    watch({glob: cfg.watchFiles.html, emitOnGlob: false, name: 'HTML'}, function() {
    return buildHTML();
    });

    watch({glob: cfg.watchFiles.less, emitOnGlob: false}, function() {
    watch({glob: cfg.watchFiles.less, emitOnGlob: false, name: 'Styles'}, function() {
    // run this way to ensure that a failed pipe doesn't break the watcher.
    buildStyles();
    return buildStyles();
    });

    watch({glob: cfg.watchFiles.assets, emitOnGlob: false})
    watch({glob: cfg.watchFiles.assets, emitOnGlob: false, name: 'Assets'})
    .pipe(gulp.dest(join(cfg.buildDir, cfg.assetsDir)));

    gulp.run('test-watch');
    });

    gulp.task('compile', function() {
    gulp.task('compile', function(cb) {
    runSequence(
    'compile-clean',
    ['compile-assets', 'compile-scripts', 'compile-styles'],
    'compile-html'
    'compile-html',
    cb
    );
    });

    @@ -145,8 +124,49 @@ function readFile(filename) {
    return fs.existsSync(filename) ? fs.readFileSync(filename, {encoding: 'utf8'}) : '';
    }

    var join = path.join;
    var embedLR = false;
    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
    @@ -157,10 +177,7 @@ var embedLR = false;
    // HTML
    //---------------------------------------------

    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.
    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())
    @@ -169,7 +186,7 @@ gulp.task('build-html', function() {
    sort: fileSorter, // see below
    ignorePath: join('/',cfg.buildDir,'/')
    }))
    .pipe(_if(embedLR, livereloadEmbed()))
    .pipe(_if(embedLR, livereloadEmbed({port: cfg.server.lrPort})))
    .pipe(gulp.dest(cfg.buildDir))
    .pipe(tap(function(file) {
    var newHtmlFile = file.contents.toString();
    @@ -178,6 +195,13 @@ gulp.task('build-html', function() {
    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() {
    @@ -187,8 +211,10 @@ gulp.task('compile-html', function() {
    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())
    @@ -199,14 +225,15 @@ gulp.task('compile-html', function() {
    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('**/', f); }),
    '**/common/**/*.js',
    '**/app/**/*.js',
    '**/**/*.js',
    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
    '**/*.css'
    join(cfg.cssDir, '**/*.css')
    ]));
    return function(a,b){ return as(a.filepath, b.filepath) };
    return function(a,b){ return as(a.filepath, b.filepath) || a.filepath < b.filepath };
    })();


    @@ -231,8 +258,9 @@ var jsFiles = function() { return gulp.src(cfg.appFiles.js); },

    //noinspection FunctionWithInconsistentReturnsJS
    gulp.task('build-scripts-vendor', function() {
    if(cfg.vendorFiles.js.length) {
    return gulp.src(cfg.vendorFiles.js, {base: cfg.vendorDir})
    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)))
    }
    });
    @@ -246,30 +274,44 @@ gulp.task('build-scripts', ['build-scripts-vendor', 'build-scripts-app', 'build-


    gulp.task('compile-scripts', function() {
    var appFiles = jsFiles()
    var appFiles, templates, files, concatFiles, vendorFiles;
    appFiles = jsFiles()
    .pipe(jsBaseTasks())
    .pipe(concat('appFiles.js')) // not used
    .pipe(ngmin())
    .pipe(header(readFile('module.prefix')))
    .pipe(footer(readFile('module.suffix')));
    .pipe(header(cfg.moduleWrapper.header))
    .pipe(footer(cfg.moduleWrapper.footer));

    var templates = tplFiles()
    templates = tplFiles()
    .pipe(minifyHtml({empty: true, spare: true, quotes: true}))
    .pipe(ngHtml2js({moduleName: 'templates'}))
    .pipe(concat('templates.min.js')); // not used

    var files = [appFiles, templates];
    if(cfg.vendorFiles.js.length) {
    files.unshift(gulp.src(cfg.vendorFiles.js));
    files = [appFiles, templates];
    if(cfg.vendorFiles.jsConcat.length) {
    files.unshift(gulp.src(cfg.vendorFiles.jsConcat));
    }

    return es.concat.apply(es, files)
    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)))
    .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;
    }
    });


    @@ -317,7 +359,7 @@ gulp.task('compile-styles', function() {
    //---------------------------------------------

    var testFiles = function() {
    return gulp.src(_.flatten([cfg.vendorFiles.js, cfg.testFiles.js, cfg.appFiles.jsunit]));
    return gulp.src(_.flatten([cfg.vendorFiles.js, cfg.vendorFiles.jsConcat, cfg.testFiles.js, cfg.appFiles.jsunit]));
    };

    gulp.task('test', ['build-scripts'], function() {
    @@ -326,7 +368,7 @@ gulp.task('test', ['build-scripts'], function() {
    configFile: cfg.testFiles.config
    }))
    });
    gulp.task('test-watch', ['build-scripts'], function() {
    gulp.task('test-watch', ['build-scripts', 'test'], function() {
    // NOT returned on purpose!
    testFiles()
    .pipe(karma({
    @@ -375,7 +417,7 @@ gulp.task('compile-assets', ['compile-assets-vendor'], function() {

    gulp.task('lr-server', function() {
    embedLR = true;
    server.listen(35729, function(err) {
    server.listen(cfg.server.lrPort, function(err) {
    if(err) {
    console.log(err);
    }
    @@ -389,4 +431,20 @@ gulp.task('build-clean', function() {

    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('./'));
    });
    6 changes: 4 additions & 2 deletions package.json
    Original file line number Diff line number Diff line change
    @@ -18,6 +18,7 @@
    "event-stream": "*",
    "gulp": "3.*",
    "gulp-autoprefixer": "*",
    "gulp-cdnizer": "*",
    "gulp-clean": "*",
    "gulp-concat": "*",
    "gulp-csso": "*",
    @@ -39,7 +40,6 @@
    "gulp-recess": "*",
    "gulp-rename": "*",
    "gulp-rev": "*",
    "gulp-run-sequence": "*",
    "gulp-tap": "*",
    "gulp-task-listing": "*",
    "gulp-uglify": "*",
    @@ -48,8 +48,10 @@
    "jshint-stylish": "*",
    "lazypipe": "*",
    "lodash": "~2.4.1",
    "run-sequence": "*",
    "tiny-lr": "0.0.5",
    "connect": "~2.12.0",
    "open": "0.0.4"
    "open": "0.0.4",
    "gulp-bump": "~0.1.4"
    }
    }
  2. OverZealous created this gist Jan 22, 2014.
    152 changes: 152 additions & 0 deletions build.config.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,152 @@
    /**
    * This file/module contains all configuration for the build process.
    */

    var join = require('path').join,
    bowerrc = JSON.parse(require('fs').readFileSync('./.bowerrc', {encoding: 'utf8'})),
    bower = require(bowerrc.json.replace(/^\.?\/?/, './')),
    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
    bower: bower,
    pkg: pkg,

    /**
    * 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: true
    },

    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` is all project javascript, less tests. `ctpl` contains
    * our reusable components' (`src/common`) template HTML files, while
    * `atpl` contains the same, but for our app's code. `html` is just our
    * main HTML file, `less` is our main stylesheet, and `unit` contains our
    * app's unit tests.
    */
    appFiles: {
    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 is the same as `app_files`, except it contains patterns that
    * reference vendor code (`vendor/`) that we need to place into the build
    * process somewhere. While the `app_files` property ensures all
    * standardized files are collected for compilation, it is the user's job
    * to ensure non-standardized (i.e. vendor-related) files are handled
    * appropriately in `vendorFiles.js`.
    *
    * The `vendorFiles.js` property holds files to be automatically
    * concatenated and minified with our project source files.
    *
    * The `vendorFiles.assets` property holds any assets to be copied along
    * with our app's assets. This structure is flattened, so it is not
    * recommended that you use wildcards.
    */
    vendorFiles: {
    js: [
    'vendor/angular/angular.js'
    ],
    assets: [
    ]
    }
    };
    392 changes: 392 additions & 0 deletions gulpfile.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,392 @@
    //<editor-fold desc="Node Requires, gulp, etc">
    var gulp = require('gulp'),
    autoprefixer = require('gulp-autoprefixer'),
    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'),
    runSequence = require('gulp-run-sequence'),
    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'),
    server = require('tiny-lr')();
    //</editor-fold>

    // Load user config and package data
    var cfg = require('./build.config.js');

    // common variables
    var concatName = cfg.pkg.name;



    //=============================================
    // MAIN TASKS
    //=============================================

    gulp.task('default', ['watch']);

    gulp.task('server', ['watch'], function() {
    var devConnect, devServer, devAddress, devHost, url;
    devConnect = connect();
    if(cfg.server.log) {
    var type = cfg.server.log===true ? 'dev' : cfg.server.log;
    devConnect.use(connect.logger(type));
    }
    devConnect.use(connect.static(cfg.buildDir));

    devServer = http.createServer(devConnect).listen(cfg.server.port, cfg.server.host||undefined);
    devServer.on('error', function(error) {
    gutil.log('');
    gutil.log(gutil.colors.underline(gutil.colors.red('ERROR'))+' Unable to start server!');
    gutil.log(gutil.colors.red(error.toString()));
    gutil.log('');
    });
    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);

    gutil.log('');
    gutil.log('Started dev server at '+gutil.colors.magenta(url));
    if((cfg.server.openByDefault && !gulp.env.noOpen) ||
    (!cfg.server.openByDefault && gulp.env.open)) {
    var msg = 'Opening dev server URL in browser';
    if(cfg.server.openByDefault) {
    msg += ' (Run with --no-open to prevent automatically opening URL)';
    }
    gutil.log(msg);
    open(url);
    } else if(!cfg.server.openByDefault) {
    gutil.log('(Run with --open to automatically open URL on startup)');
    }
    gutil.log('');
    });
    });

    gulp.task('build', function(cb) {
    runSequence(['build-assets', 'build-scripts', 'build-styles'], ['build-html', 'test'], cb);
    });
    gulp.task('watch', ['lr-server', 'build'], function() {
    watch({glob: cfg.watchFiles.js, emitOnGlob: false})
    .pipe(plumber())
    .pipe(jsBuildTasks())
    .pipe(livereload(server));

    watch({glob: cfg.watchFiles.tpl, emitOnGlob: false})
    .pipe(plumber())
    .pipe(tplBuildTasks())
    .pipe(livereload(server));

    watch({glob: cfg.watchFiles.html, emitOnGlob: false}, function() {
    gulp.run('build-html');
    });

    watch({glob: cfg.watchFiles.less, emitOnGlob: false}, function() {
    // run this way to ensure that a failed pipe doesn't break the watcher.
    buildStyles();
    });

    watch({glob: cfg.watchFiles.assets, emitOnGlob: false})
    .pipe(gulp.dest(join(cfg.buildDir, cfg.assetsDir)));

    gulp.run('test-watch');
    });

    gulp.task('compile', function() {
    runSequence(
    'compile-clean',
    ['compile-assets', 'compile-scripts', 'compile-styles'],
    'compile-html'
    );
    });

    gulp.task('clean', ['compile-clean', 'build-clean']);

    gulp.task('help', help);




    //=============================================
    // UTILITIES
    //=============================================

    function readFile(filename) {
    return fs.existsSync(filename) ? fs.readFileSync(filename, {encoding: 'utf8'}) : '';
    }

    var join = path.join;
    var embedLR = false;

    //=============================================
    // SUB TASKS
    //=============================================


    //---------------------------------------------
    // HTML
    //---------------------------------------------

    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.
    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()))
    .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('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,
    ignorePath: join('/', cfg.compileDir, '/')
    }))
    .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('**/', f); }),
    '**/common/**/*.js',
    '**/app/**/*.js',
    '**/**/*.js',
    // CSS order should be maintained via Less includes
    '**/*.css'
    ]));
    return function(a,b){ return as(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() {
    if(cfg.vendorFiles.js.length) {
    return gulp.src(cfg.vendorFiles.js, {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 = jsFiles()
    .pipe(jsBaseTasks())
    .pipe(concat('appFiles.js')) // not used
    .pipe(ngmin())
    .pipe(header(readFile('module.prefix')))
    .pipe(footer(readFile('module.suffix')));

    var templates = tplFiles()
    .pipe(minifyHtml({empty: true, spare: true, quotes: true}))
    .pipe(ngHtml2js({moduleName: 'templates'}))
    .pipe(concat('templates.min.js')); // not used

    var files = [appFiles, templates];
    if(cfg.vendorFiles.js.length) {
    files.unshift(gulp.src(cfg.vendorFiles.js));
    }

    return 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)))
    });



    //---------------------------------------------
    // 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.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'], 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(35729, 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());
    });
    55 changes: 55 additions & 0 deletions package.json
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,55 @@
    {
    "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-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-run-sequence": "*",
    "gulp-tap": "*",
    "gulp-task-listing": "*",
    "gulp-uglify": "*",
    "gulp-util": "*",
    "gulp-watch": "*",
    "jshint-stylish": "*",
    "lazypipe": "*",
    "lodash": "~2.4.1",
    "tiny-lr": "0.0.5",
    "connect": "~2.12.0",
    "open": "0.0.4"
    }
    }