From a6f9e95ed88f9fd2b45d71a8b58055ef72d7d3df Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Tue, 27 Sep 2016 10:57:56 -0700 Subject: [PATCH] Feat: Add react-hot-loader Combine webpack and browser-sync on one port --- client/index.js | 12 ++-- gulpfile.js | 103 ++++++++++++----------------- package.json | 4 +- server/middlewares/csp.js | 4 +- server/middlewares/jade-helpers.js | 10 --- server/views/layout-react.jade | 2 +- webpack.config.js | 6 +- 7 files changed, 58 insertions(+), 83 deletions(-) diff --git a/client/index.js b/client/index.js index c2c98abb6f4..c401c4cc497 100644 --- a/client/index.js +++ b/client/index.js @@ -28,8 +28,8 @@ import { const isDev = Rx.config.longStackSupport = debug.enabled('fcc:*'); const log = debug('fcc:client'); -const hotReloadTimeout = 5000; -const csrfToken = window.__fcc__.csrf.token; +const hotReloadTimeout = 2000; +const { csrf: { csrfToken } = {} } = window.__fcc__; const DOMContainer = document.getElementById('fcc'); const initialState = isColdStored() ? getColdStorage() : @@ -37,7 +37,8 @@ const initialState = isColdStored() ? initialState.app.csrfToken = csrfToken; initialState.toasts = flashToToast(window.__fcc__.flash); -delete window.__fcc__; +// make empty object so hot reload works +window.__fcc__ = {}; const serviceOptions = { xhrPath: '/services', context: { _csrf: csrfToken } }; @@ -69,7 +70,10 @@ createApp({ }) .doOnNext(({ store }) => { if (module.hot && typeof module.hot.accept === 'function') { - module.hot.accept('../common/app', function() { + module.hot.accept(err => { + if (err) { console.error(err); } + log('saving state and refreshing.'); + log('ignore react ssr warning.'); saveToColdStorage(store.getState()); setTimeout(() => window.location.reload(), hotReloadTimeout); }); diff --git a/gulpfile.js b/gulpfile.js index 126e931acfb..b39dd02a8ab 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -25,12 +25,13 @@ var Rx = require('rx'), // react app webpack = require('webpack'), webpackStream = require('webpack-stream'), - WebpackDevServer = require('webpack-dev-server'), + webpackDevMiddleware = require('webpack-dev-middleware'), + webpackHotMiddleware = require('webpack-hot-middleware'), webpackConfig = require('./webpack.config.js'), // server process nodemon = require('gulp-nodemon'), - sync = require('browser-sync'), + browserSync = require('browser-sync'), // css less = require('gulp-less'), @@ -48,10 +49,18 @@ var Rx = require('rx'), tapSpec = require('tap-spec'); Rx.config.longStackSupport = true; +var sync = browserSync.create('fcc-sync-server'); +var reload = sync.reload.bind(sync); +// user definable var __DEV__ = !yargs.argv.p; -var reloadDelay = 1000; -var reload = sync.reload; +var port = yargs.argv.port || process.env.PORT || '3001'; +var syncPort = yargs.argv['sync-port'] || process.env.SYNC_PORT || '3000'; +// make sure sync ui port does not interfere with proxy port +var syncUIPort = yargs.argv['sync-ui-port'] || + process.env.SYNC_UI_PORT || + parseInt(syncPort, 10) + 2; + var paths = { server: './server/server.js', serverIgnore: [ @@ -171,25 +180,20 @@ gulp.task('serve', function(cb) { exec: path.join(__dirname, 'node_modules/.bin/babel-node'), env: { NODE_ENV: process.env.NODE_ENV || 'development', - DEBUG: process.env.DEBUG || 'fcc:*' + DEBUG: process.env.DEBUG || 'fcc:*', + PORT: port } }) .on('start', function() { if (!called) { called = true; - setTimeout(function() { - cb(); - }, reloadDelay); + cb(); } }) .on('restart', function(files) { if (files) { - debug('Files that changes: ', files); + debug('Nodemon will restart due to changes in: ', files); } - setTimeout(function() { - debug('Restarting browsers'); - reload(); - }, reloadDelay); }); }); @@ -199,14 +203,34 @@ var syncDepenedents = [ 'less' ]; -gulp.task('sync', syncDepenedents, function() { +gulp.task('dev-server', syncDepenedents, function() { + webpackConfig.entry.bundle = [ + 'webpack/hot/dev-server', + 'webpack-hot-middleware/client' + ].concat(webpackConfig.entry.bundle); + + var bundler = webpack(webpackConfig); sync.init(null, { - proxy: 'http://localhost:3000', + ui: { + port: syncUIPort + }, + proxy: { + target: `http://localhost:${port}`, + reqHeaders: ({ url: { hostname } }) => ({ + host: `${hostname}:${syncPort}` + }) + }, logLeval: 'debug', files: paths.syncWatch, - port: 3001, + port: syncPort, open: false, - reloadDelay: reloadDelay + middleware: [ + webpackDevMiddleware(bundler, { + publicPath: webpackConfig.output.publicPath, + stats: 'errors-only' + }), + webpackHotMiddleware(bundler) + ] }); }); @@ -273,46 +297,6 @@ gulp.task('clean-webpack-manifest', cleanDeps, function() { }); }); -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.bundle = [ - 'webpack-dev-server/client?http://localhost:2999/', - 'webpack/hot/dev-server' - ].concat(webpackConfig.entry.bundle); - - 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('less', function() { var manifestName = 'css-manifest.json'; var dest = paths.css; @@ -435,7 +419,7 @@ var watchDependents = [ 'less', 'js', 'serve', - 'sync' + 'dev-server' ]; gulp.task('reload', function() { @@ -453,9 +437,8 @@ gulp.task('watch', watchDependents, function() { gulp.task('default', [ 'less', 'serve', - 'webpack-dev-server', 'watch', - 'sync' + 'dev-server' ]); gulp.task('test', function() { diff --git a/package.json b/package.json index ec849e9def1..e33cc7ba37f 100644 --- a/package.json +++ b/package.json @@ -156,13 +156,15 @@ "loopback-component-explorer": "^2.1.1", "merge-stream": "^1.0.0", "proxyquire": "^1.7.10", + "react-hot-loader": "^1.3.0", "rev-del": "^1.0.5", "sinon": "^1.17.3", "sort-keys": "^1.1.1", "tap-spec": "^4.1.1", "tape": "^4.2.2", "webpack": "^1.9.12", - "webpack-dev-server": "^1.14.0", + "webpack-dev-middleware": "^1.8.3", + "webpack-hot-middleware": "^2.12.2", "webpack-manifest-plugin": "^1.0.0", "webpack-stream": "^3.1.0", "yargs": "^5.0.0" diff --git a/server/middlewares/csp.js b/server/middlewares/csp.js index 93b3cc99218..5ef54e1f93c 100644 --- a/server/middlewares/csp.js +++ b/server/middlewares/csp.js @@ -6,9 +6,7 @@ let trusted = [ if (process.env.NODE_ENV !== 'production') { trusted = trusted.concat([ - 'ws://localhost:3001', - 'http://localhost:2999', - 'ws://localhost:2999' + 'ws://localhost:3000' ]); } diff --git a/server/middlewares/jade-helpers.js b/server/middlewares/jade-helpers.js index f7672ef9c6a..837f797d56d 100644 --- a/server/middlewares/jade-helpers.js +++ b/server/middlewares/jade-helpers.js @@ -1,7 +1,4 @@ import manifest from '../rev-manifest'; -/* eslint-disable import/default */ -import config from '../../webpack.config'; -/* eslint-enable import/default */ let chunkManifest; try { @@ -30,16 +27,9 @@ function removeOldTerms(str = '') { return str.replace(challengesRegex, ''); } -function getBundleLocation() { - return __DEV__ ? - config.output.publicPath + '/bundle.js' : - rev('/js', 'bundle.js'); -} - export default function jadeHelpers() { return function jadeHelpersMiddleware(req, res, next) { res.locals.removeOldTerms = removeOldTerms; - res.locals.getBundleLocation = getBundleLocation; res.locals.rev = rev; // static data res.locals.user = req.user; diff --git a/server/views/layout-react.jade b/server/views/layout-react.jade index d4a23e8a318..fb646d69725 100644 --- a/server/views/layout-react.jade +++ b/server/views/layout-react.jade @@ -12,4 +12,4 @@ html(lang='en') script. window.webpackManifest = !{JSON.stringify(chunkManifest || {})}; script(src=rev('/js', 'vendor-challenges.js')) - script(src=getBundleLocation()) + script(src=rev('/js', 'bundle.js')) diff --git a/webpack.config.js b/webpack.config.js index 94355ad18e1..7f178a5d4d9 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -23,7 +23,7 @@ module.exports = { 'bundle-[name].js' : 'bundle-[name]-[chunkhash].js', path: path.join(__dirname, '/public/js'), - publicPath: __DEV__ ? 'http://localhost:2999/js' : '/js' + publicPath: '/js' }, module: { loaders: [ @@ -33,9 +33,7 @@ module.exports = { path.join(__dirname, 'client/'), path.join(__dirname, 'common/') ], - loaders: [ - 'babel-loader' - ] + loaders: __DEV__ ? ['react-hot', 'babel'] : [ 'babel' ] }, { test: /\.json$/,