Files
freeCodeCamp/gulpfile.js
Berkeley Martinez ef80131a29 feat(build): Add source maps to production
We don't have the same precautions as other sites as our code base is
open source, so we might as well make it easier on us to debug
production errors
2018-02-06 13:53:56 -08:00

458 lines
11 KiB
JavaScript

// enable debug for gulp
process.env.DEBUG = process.env.DEBUG || 'fcc:*';
require('dotenv').load();
require('babel-core/register');
const Rx = require('rx'),
gulp = require('gulp'),
path = require('path'),
debug = require('debug')('fcc:gulp'),
yargs = require('yargs'),
sortKeys = require('sort-keys'),
del = require('del'),
// utils
plumber = require('gulp-plumber'),
notify = require('gulp-notify'),
gutil = require('gulp-util'),
reduce = require('gulp-reduce-file'),
concat = require('gulp-concat'),
uglify = require('gulp-uglify'),
merge = require('merge-stream'),
sourcemaps = require('gulp-sourcemaps'),
// react app
webpack = require('webpack'),
webpackStream = require('webpack-stream'),
webpackDevMiddleware = require('webpack-dev-middleware'),
webpackHotMiddleware = require('webpack-hot-middleware'),
webpackConfig = require('./webpack.config.js'),
webpackFrameConfig = require('./webpack.frame-runner.js'),
// server process
nodemon = require('gulp-nodemon'),
browserSync = require('browser-sync'),
// css
less = require('gulp-less'),
// rev
rev = require('gulp-rev'),
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;
const sync = browserSync.create('fcc-sync-server');
function resolve(filepath, thisString, withThisString) {
const newPath = require.resolve(filepath);
if (thisString && withThisString) {
return newPath.replace(thisString, withThisString);
}
return newPath;
}
// user definable
const __DEV__ = !yargs.argv.p;
const host = process.env.HOST || 'localhost';
const port = yargs.argv.port || process.env.PORT || '3001';
const syncPort = yargs.argv['sync-port'] || process.env.SYNC_PORT || '3000';
// make sure sync ui port does not interfere with proxy port
const syncUIPort = yargs.argv['sync-ui-port'] ||
process.env.SYNC_UI_PORT ||
parseInt(syncPort, 10) + 2;
const 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: [
resolve('jshint', 'src', 'dist'),
resolve('chai', 'index.js', 'chai.js'),
resolve('codemirror'),
resolve('codemirror', 'lib/codemirror.js', 'addon/comment/comment.js'),
resolve('codemirror', 'lib/codemirror.js', 'addon/edit/closebrackets.js'),
resolve('codemirror', 'lib/codemirror.js', 'addon/edit/matchbrackets.js'),
resolve('codemirror', 'lib/codemirror.js', 'addon/lint/lint.js'),
resolve('codemirror', 'lib/codemirror.js', 'addon/lint/javascript-lint.js'),
resolve('codemirror', 'lib/codemirror.js', 'mode/javascript/javascript.js'),
resolve('codemirror', 'lib/codemirror.js', 'mode/jsx/jsx.js'),
resolve('codemirror', 'lib/codemirror.js', 'mode/xml/xml.js'),
resolve('codemirror', 'lib/codemirror.js', 'mode/css/css.js'),
resolve('codemirror', 'lib/codemirror.js', 'mode/htmlmixed/htmlmixed.js'),
resolve('emmet-codemirror'),
'public/js/lib/loop-protect/loop-protect.js'
],
vendorMain: [
resolve('jquery', '.js', '.min.js'),
resolve('bootstrap', 'npm.js', 'bootstrap.min.js'),
resolve('d3', '.js', '.min.js'),
resolve('cal-heatmap'),
resolve('moment', '.js', '.min.js'),
resolve(
'moment-timezone',
'index.js',
'builds/moment-timezone-with-data.min.js'
),
resolve('mousetrap', '.js', '.min.js'),
resolve('lightbox2', '.js', '.min.js'),
resolve('rx', 'index.js', 'dist/rx.all.min.js')
],
less: './client/less/main.less',
lessFiles: [
'./client/**/*.less',
'./common/**/*.less'
],
manifest: 'server/manifests/',
node: {
src: './client',
dest: 'common/app'
},
syncWatch: [
'public/**/*.*'
],
challenges: [
'seed/challenges/*/*.json'
]
};
const errorNotifier = notify.onError({
title: 'Compile Error',
message: '<%= error %>'
});
function errorHandler(...args) {
// Send error to notification center with gulp-notify
errorNotifier.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', function(cb) {
let called = false;
const monitor = nodemon({
script: paths.server,
ext: '.jsx .js .json',
ignore: paths.serverIgnore,
exec: path.normalize('node_modules/.bin/babel-node'),
env: {
NODE_ENV: process.env.NODE_ENV || 'development',
DEBUG: process.env.DEBUG || 'fcc:*',
PORT: port
}
})
.on('start', function() {
if (!called) {
called = true;
cb();
}
})
.on('restart', function(files) {
if (files) {
debug('Nodemon will restart due to changes in: ', files);
}
});
process.once('SIGINT', () => {
monitor.once('exit', () => {
/* eslint-disable no-process-exit */
process.exit(0);
/* eslint-enable no-process-exit */
});
});
});
const syncDepenedents = [
'serve',
'js',
'less'
];
gulp.task('dev-server', syncDepenedents, function() {
webpackConfig.entry.bundle = [
'webpack/hot/dev-server',
'webpack-hot-middleware/client'
].concat(webpackConfig.entry.bundle);
const bundler = webpack(webpackConfig);
sync.init(null, {
ui: {
port: syncUIPort
},
proxy: {
target: `http://${host}:${port}`,
reqHeaders: ({ url: { hostname } }) => ({
host: `${hostname}:${syncPort}`
})
},
logLevel: 'info',
files: paths.syncWatch,
port: syncPort,
open: false,
middleware: [
webpackDevMiddleware(bundler, {
publicPath: webpackConfig.output.publicPath,
stats: 'errors-only'
}),
webpackHotMiddleware(bundler)
]
});
});
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'); }
const dest = webpackConfig.output.path;
return gulp.src(webpackConfig.entry.bundle)
.pipe(plumber({ errorHandler }))
.pipe(webpackStream(webpackConfig))
.pipe(gulp.dest(dest));
});
gulp.task('pack-frame-runner', function() {
if (!__DEV__) { console.log('\n\nbundling frame production\n\n'); }
const dest = webpackFrameConfig.output.path;
return gulp.src(webpackFrameConfig.entry)
.pipe(plumber({ errorHandler }))
.pipe(webpackStream(webpackFrameConfig))
.pipe(gulp.dest(dest));
});
const webpackManifestFiles = [ 'react-manifest.json', 'chunk-manifest.json' ];
gulp.task('move-webpack-manifest', ['pack-client'], function() {
const files = webpackManifestFiles.map(function(filename) {
return path.join(webpackConfig.output.path, filename);
});
return gulp.src(files).pipe(gulp.dest(paths.manifest));
});
const cleanDeps = ['pack-client', 'move-webpack-manifest'];
gulp.task('clean-webpack-manifest', cleanDeps, function() {
return del(webpackManifestFiles.map(function(filename) {
return path.join(webpackConfig.output.path, filename);
}))
.then(function(pathsDeleted) {
gutil.log('[clean-webpack-manifest]', 'paths deleted' + pathsDeleted);
})
.catch(function(err) {
throw new gutil.PluginError('clean-webpack-manifest', err);
});
});
gulp.task('less', function() {
const manifestName = 'css-manifest.json';
const dest = paths.css;
return gulp.src(paths.less)
.pipe(plumber({ errorHandler }))
.pipe(__DEV__ ? sourcemaps.init() : gutil.noop())
// compile
.pipe(less({
paths: [
path.join(__dirname, 'client', 'less'),
path.join(__dirname, 'common')
]
}))
.pipe(__DEV__ ?
sourcemaps.write({ sourceRoot: '/less' }) :
gutil.noop()
)
.pipe(gulp.dest(dest))
// add revision
.pipe(__DEV__ ? gutil.noop() : rev())
// copy files to public
.pipe(__DEV__ ? gutil.noop() : gulp.dest(dest))
// create and merge manifest
.pipe(__DEV__ ? gutil.noop() : rev.manifest(manifestName))
.pipe(__DEV__ ? gutil.noop() : delRev(
dest,
manifestName
))
.pipe(__DEV__ ? gutil.noop() : 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() {
const manifestName = 'js-manifest.json';
const dest = paths.publicJs;
const 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()
)
);
return jsFiles
.pipe(gulp.dest(dest))
// create registry file
.pipe(__DEV__ ? gutil.noop() : rev())
// copy revisioned assets to dest
.pipe(__DEV__ ? gutil.noop() : gulp.dest(dest))
// create manifest file
.pipe(__DEV__ ? gutil.noop() : rev.manifest(manifestName))
.pipe(__DEV__ ? gutil.noop() : delRev(
dest,
manifestName
))
// copy manifest file to dest
.pipe(__DEV__ ? gutil.noop() : gulp.dest(paths.manifest));
});
const collector = (file, memo) =>
Object.assign(memo, JSON.parse(file.contents));
function done(manifest) {
return sortKeys(manifest);
}
const buildDependents = [
'less',
'js',
'pack-client',
'move-webpack-manifest'
];
gulp.task('build-manifest', buildDependents, function() {
return gulp.src(paths.manifest + '*.json')
.pipe(reduce('rev-manifest.json', collector, done, {}))
.pipe(gulp.dest('server/'));
});
gulp.task('build', [
'less',
'js',
'pack-client',
'pack-frame-runner',
'move-webpack-manifest',
'clean-webpack-manifest',
'build-manifest'
]);
const watchDependents = [
'less',
'js',
'serve',
'pack-frame-runner',
'dev-server'
];
gulp.task('watch', watchDependents, function() {
gulp.watch(paths.lessFiles, ['less']);
gulp.watch(paths.vendorChallenges, ['js']);
gulp.watch(webpackFrameConfig.entry, ['pack-frame-runner']);
});
gulp.task('default', [
'less',
'serve',
'watch',
'dev-server',
'pack-frame-runner'
]);
gulp.task('test', function() {
return gulp.src('test/**/*.js')
.pipe(tape({
reporter: tapSpec()
}));
});