Files
freeCodeCamp/gulpfile.js
Berkeley Martinez 4e12c45057 Add webpack cold reloading
On changes to the react bundle
webpack will store the current redux state
in localStorage, waits (to allow the server to restart)
then refreshes the page. On page load, it checks if it
has state stored and loads it into the app.
2016-07-28 23:39:17 -07:00

554 lines
13 KiB
JavaScript

// enable debug for gulp
process.env.DEBUG = process.env.DEBUG || 'fcc:*';
require('babel-core/register');
var Rx = require('rx'),
gulp = require('gulp'),
path = require('path'),
// utils
plumber = require('gulp-plumber'),
notify = require('gulp-notify'),
gutil = require('gulp-util'),
reduce = require('gulp-reduce-file'),
sortKeys = require('sort-keys'),
debug = require('debug')('fcc:gulp'),
yargs = require('yargs'),
concat = require('gulp-concat'),
uglify = require('gulp-uglify'),
merge = require('merge-stream'),
babel = require('gulp-babel'),
sourcemaps = require('gulp-sourcemaps'),
// react app
webpack = require('webpack'),
webpackStream = require('webpack-stream'),
WebpackDevServer = require('webpack-dev-server'),
webpackConfig = require('./webpack.config.js'),
webpackConfigNode = require('./webpack.config.node.js'),
// server process
nodemon = require('gulp-nodemon'),
sync = require('browser-sync'),
// css
less = require('gulp-less'),
// rev
rev = require('gulp-rev'),
revReplace = require('gulp-rev-replace'),
revDel = require('rev-del'),
// lint
jsonlint = require('gulp-jsonlint'),
eslint = require('gulp-eslint'),
// unit-tests
tape = require('gulp-tape'),
tapSpec = require('tap-spec');
Rx.config.longStackSupport = true;
var __DEV__ = !yargs.argv.p;
var reloadDelay = 1000;
var reload = sync.reload;
var paths = {
server: './server/server.js',
serverIgnore: [
'gulpfile.js',
'public/',
'node_modules/',
'client/',
'seed',
'server/manifests/*.json',
'server/rev-manifest.json'
],
publicJs: './public/js',
css: 'public/css',
loopback: {
client: './client/loopbackClient',
root: path.join(__dirname, 'client/'),
clientName: 'lbApp'
},
client: {
src: './client',
dest: 'public/js'
},
vendorChallenges: [
'public/bower_components/jshint/dist/jshint.js',
'public/bower_components/chai/chai.js',
'public/bower_components/CodeMirror/lib/codemirror.js',
'public/bower_components/CodeMirror/addon/comment/comment.js',
'public/bower_components/CodeMirror/addon/edit/closebrackets.js',
'public/bower_components/CodeMirror/addon/edit/matchbrackets.js',
'public/bower_components/CodeMirror/addon/lint/lint.js',
'public/bower_components/CodeMirror/addon/lint/javascript-lint.js',
'public/bower_components/CodeMirror/mode/javascript/javascript.js',
'public/bower_components/CodeMirror/mode/xml/xml.js',
'public/bower_components/CodeMirror/mode/css/css.js',
'public/bower_components/CodeMirror/mode/htmlmixed/htmlmixed.js',
'node_modules/emmet-codemirror/dist/emmet.js',
'public/js/lib/loop-protect/loop-protect.js'
],
vendorMain: [
'public/bower_components/jquery/dist/jquery.min.js',
'public/bower_components/bootstrap/dist/js/bootstrap.min.js',
'public/bower_components/d3/d3.min.js',
'public/bower_components/moment/min/moment.min.js',
'public/bower_components/' +
'moment-timezone/builds/moment-timezone-with-data.min.js',
'public/bower_components/mousetrap/mousetrap.min.js',
'public/bower_components/lightbox2/dist/js/lightbox.min.js',
'public/bower_components/rxjs/dist/rx.all.min.js'
],
js: [
'client/main.js',
'client/iFrameScripts.js',
'client/plugin.js'
],
commonFramework: [
'init',
'bindings',
'add-test-to-string',
'code-storage',
'code-uri',
'add-loop-protect',
'get-iframe',
'update-preview',
'create-editor',
'detect-unsafe-code-stream',
'display-test-results',
'execute-challenge-stream',
'output-display',
'phone-scroll-lock',
'report-issue',
'run-tests-stream',
'show-completion',
'step-challenge',
'end'
],
less: './client/less/main.less',
lessFiles: './client/less/**/*.less',
manifest: 'server/manifests/',
node: {
src: './client',
dest: 'common/app'
},
syncWatch: [
'public/**/*.*'
],
challenges: [
'seed/challenges/*/*.json'
]
};
var webpackOptions = {
devtool: 'inline-source-map'
};
function formatCommonFrameworkPaths() {
return this.map(function(script) {
return 'client/commonFramework/' + script + '.js';
});
}
function errorHandler() {
var args = Array.prototype.slice.call(arguments);
// Send error to notification center with gulp-notify
notify.onError({
title: 'Compile Error',
message: '<%= error %>'
}).apply(this, args);
// Keep gulp from hanging on this task
this.emit('end');
}
function delRev(dest, manifestName) {
// in production do not delete old revisions
if (!__DEV__) {
return gutil.noop();
}
return revDel({
oldManifest: path.join(paths.manifest, manifestName),
dest: dest
});
}
gulp.task('serve', ['build-manifest'], function(cb) {
var called = false;
nodemon({
script: paths.server,
ext: '.jsx .js .json',
ignore: paths.serverIgnore,
exec: path.join(__dirname, 'node_modules/.bin/babel-node'),
env: {
'NODE_ENV': process.env.NODE_ENV || 'development',
'DEBUG': process.env.DEBUG || 'fcc:*'
}
})
.on('start', function() {
if (!called) {
called = true;
setTimeout(function() {
cb();
}, reloadDelay);
}
})
.on('restart', function(files) {
if (files) {
debug('Files that changes: ', files);
}
setTimeout(function() {
debug('Restarting browsers');
reload();
}, reloadDelay);
});
});
var syncDepenedents = [
'serve',
'js',
'less',
'dependents',
'build-manifest'
];
gulp.task('sync', syncDepenedents, function() {
sync.init(null, {
proxy: 'http://localhost:3000',
logLeval: 'debug',
files: paths.syncWatch,
port: 3001,
open: false,
reloadDelay: reloadDelay
});
});
gulp.task('lint-js', function() {
return gulp.src([
'common/**/*.js',
'common/**/*.jsx',
'client/**/*.js',
'client/**/*.jsx',
'server/**/*.js',
'config/**/*.js'
])
.pipe(eslint())
.pipe(eslint.format());
});
gulp.task('lint-json', function() {
return gulp.src(paths.challenges)
.pipe(jsonlint())
.pipe(jsonlint.reporter());
});
gulp.task('test-challenges', ['lint-json']);
gulp.task('pack-client', function() {
if (!__DEV__) { console.log('\n\nbundling production\n\n'); }
var manifestName = 'react-manifest.json';
var dest = webpackConfig.output.path;
return gulp.src(webpackConfig.entry)
.pipe(plumber({ errorHandler: errorHandler }))
.pipe(webpackStream(Object.assign(
{},
webpackConfig,
webpackOptions
)))
.pipe(__DEV__ ? gutil.noop() : uglify())
.pipe(gulp.dest(dest))
.pipe(rev())
// copy files to public
.pipe(gulp.dest(dest))
// create manifest
.pipe(rev.manifest(manifestName))
// delete old rev
.pipe(delRev(
dest,
manifestName
))
.pipe(gulp.dest(paths.manifest));
});
var webpackCalled = false;
gulp.task('webpack-dev-server', function(cb) {
if (webpackCalled) {
console.log('webpack dev server already runnning');
return cb();
}
var devServerOptions = {
headers: {
'Access-Control-Allow-Credentials': 'true'
},
hot: true,
noInfo: true,
contentBase: false,
publicPath: '/js'
};
webpackConfig.entry = [
'webpack-dev-server/client?http://localhost:2999/',
'webpack/hot/dev-server'
].concat(webpackConfig.entry);
var compiler = webpack(webpackConfig);
var devServer = new WebpackDevServer(compiler, devServerOptions);
devServer.use(function(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
next();
});
return devServer.listen('2999', 'localhost', function(err) {
if (err) {
throw new gutil.PluginError('webpack-dev-server', err);
}
if (!webpackCalled) {
gutil.log('[webpack-dev-server]', 'webpack init completed');
webpackCalled = true;
cb();
}
});
});
gulp.task('pack-watch-manifest', function() {
var manifestName = 'react-manifest.json';
var dest = webpackConfig.output.path;
return gulp.src(dest + '/bundle.js')
.pipe(rev())
// copy files to public
.pipe(gulp.dest(dest))
// create manifest
.pipe(rev.manifest(manifestName))
.pipe(delRev(
dest,
manifestName
))
.pipe(gulp.dest(paths.manifest));
});
gulp.task('pack-node', function() {
return gulp.src(webpackConfigNode.entry)
.pipe(plumber({ errorHandler: errorHandler }))
.pipe(webpack(webpackConfigNode))
.pipe(gulp.dest(webpackConfigNode.output.path));
});
gulp.task('pack', ['pack-client', 'pack-node']);
gulp.task('less', function() {
var manifestName = 'css-manifest.json';
var dest = paths.css;
return gulp.src(paths.less)
.pipe(plumber({ errorHandler: errorHandler }))
.pipe(__DEV__ ? sourcemaps.init() : gutil.noop())
// compile
.pipe(less({
paths: [ path.join(__dirname, 'less', 'includes') ]
}))
.pipe(__DEV__ ?
sourcemaps.write({ sourceRoot: '/less' }) :
gutil.noop()
)
.pipe(gulp.dest(dest))
// add revision
.pipe(rev())
// copy files to public
.pipe(gulp.dest(dest))
// create and merge manifest
.pipe(rev.manifest(manifestName))
.pipe(delRev(
dest,
manifestName
))
.pipe(gulp.dest(paths.manifest));
});
function getFilesGlob(files) {
if (!__DEV__) {
return files;
}
return files.map(function(file) {
return file
.replace('.min.', '.')
// moment breaks the pattern
.replace('/min/', '/');
});
}
gulp.task('js', function() {
var manifestName = 'js-manifest.json';
var dest = paths.publicJs;
var jsFiles = merge(
gulp.src(getFilesGlob(paths.vendorMain))
.pipe(__DEV__ ? sourcemaps.init() : gutil.noop())
.pipe(concat('vendor-main.js'))
.pipe(
__DEV__ ?
sourcemaps.write({ sourceRoot: '/vendor' }) :
gutil.noop()
),
gulp.src(paths.vendorChallenges)
.pipe(__DEV__ ? sourcemaps.init() : gutil.noop())
.pipe(__DEV__ ? gutil.noop() : uglify())
.pipe(concat('vendor-challenges.js'))
.pipe(
__DEV__ ?
sourcemaps.write({ sourceRoot: '/vendor' }) :
gutil.noop()
),
gulp.src(paths.js)
.pipe(plumber({ errorHandler: errorHandler }))
.pipe(babel())
.pipe(__DEV__ ? gutil.noop() : uglify())
);
return jsFiles
.pipe(gulp.dest(dest))
// create registry file
.pipe(rev())
// copy revisioned assets to dest
.pipe(gulp.dest(dest))
// create manifest file
.pipe(rev.manifest(manifestName))
.pipe(delRev(
dest,
manifestName
))
// copy manifest file to dest
.pipe(gulp.dest(paths.manifest));
});
// commonFramework depend on iFrameScripts
// and faux.js
gulp.task('dependents', ['js'], function() {
var manifestName = 'dependents-manifest.json';
var dest = paths.publicJs;
var manifest = gulp.src(
path.join(__dirname, paths.manifest, 'js-manifest.json')
);
return gulp.src(formatCommonFrameworkPaths.call(paths.commonFramework))
.pipe(plumber({ errorHandler: errorHandler }))
.pipe(babel())
.pipe(__DEV__ ? sourcemaps.init() : gutil.noop())
.pipe(concat('commonFramework.js'))
.pipe(
__DEV__ ?
sourcemaps.write({ sourceRoot: '/commonFramework' }) :
gutil.noop()
)
.pipe(__DEV__ ? gutil.noop() : uglify())
.pipe(revReplace({ manifest: manifest }))
.pipe(gulp.dest(dest))
.pipe(rev())
.pipe(gulp.dest(dest))
.pipe(rev.manifest(manifestName))
.pipe(delRev(
dest,
manifestName
))
.pipe(gulp.dest(paths.manifest));
});
function collector(file, memo) {
return Object.assign({}, JSON.parse(file.contents), memo);
}
function done(manifest) {
return sortKeys(manifest);
}
function buildManifest() {
return gulp.src(paths.manifest + '*.json')
.pipe(reduce('rev-manifest.json', collector, done, {}))
.pipe(gulp.dest('server/'));
}
var buildDependents = ['less', 'js', 'dependents'];
if (__DEV__) {
buildDependents.push('pack-watch-manifest');
}
gulp.task('build-manifest', buildDependents, function() {
return buildManifest();
});
gulp.task('build-manifest-watch', function() {
return buildManifest();
});
gulp.task('build', [
'less',
'js',
'dependents',
'pack-client',
'build-manifest'
]);
var watchDependents = [
'less',
'js',
'dependents',
'serve',
'sync',
'build-manifest'
];
gulp.task('reload', function() {
notify({ message: 'test changed' });
reload();
});
gulp.task('watch', watchDependents, function() {
gulp.watch(paths.lessFiles, ['less']);
gulp.watch(paths.js.concat(paths.vendorChallenges), ['js']);
gulp.watch(paths.challenges, ['test-challenges', 'reload']);
gulp.watch(paths.js, ['js', 'dependents']);
gulp.watch(
formatCommonFrameworkPaths.call(paths.commonFramework),
['dependents']
);
gulp.watch(paths.manifest + '/*.json', ['build-manifest-watch']);
});
gulp.task('default', [
'less',
'serve',
'webpack-dev-server',
'build-manifest-watch',
'watch',
'sync'
]);
gulp.task('test', function() {
return gulp.src('test/**/*.js')
.pipe(tape({
reporter: tapSpec()
}));
});