Split server and routes/config

Turn HtmlGeneratorPlugin into a module
Turn server into dev-server and simplify it
This commit is contained in:
Ray Schamp 2016-04-18 14:07:11 -04:00
parent 707af8dbe9
commit 43788eb7d8
14 changed files with 117 additions and 177 deletions

View file

@ -50,7 +50,7 @@ configure-fastly:
# ------------------------------------
start:
$(NODE) ./server/index.js
$(NODE) ./dev-server/index.js
# ------------------------------------

View file

@ -3,7 +3,7 @@ var defaults = require('lodash.defaults');
var glob = require('glob');
var path = require('path');
var routes = require('../server/routes.json');
var routes = require('../src/routes.json');
const FASTLY_SERVICE_ID = process.env.FASTLY_SERVICE_ID || '';
const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME || '';

18
dev-server/handler.js Normal file
View file

@ -0,0 +1,18 @@
/**
* Constructor
*/
function Handler (route) {
var url = '/' + route.view + '.html';
return function (req, res, next) {
req.url = url;
next();
};
}
/**
* Export a new instance
*/
module.exports = function (route) {
return new Handler(route);
};

40
dev-server/index.js Normal file
View file

@ -0,0 +1,40 @@
var express = require('express');
var proxy = require('express-http-proxy');
var webpackDevMiddleware = require('webpack-dev-middleware');
var webpack = require('webpack');
var compiler = webpack(require('../webpack.config.js'));
var handler = require('./handler');
var log = require('./log');
var routes = require('../src/routes.json');
// Create server
var app = express();
app.disable('x-powered-by');
// Server setup
app.use(log());
// Bind routes
for (var routeId in routes) {
var route = routes[routeId];
app.get(route.pattern, handler(route));
}
app.use(webpackDevMiddleware(compiler));
var proxyHost = process.env.FALLBACK || '';
if (proxyHost !== '') {
// Fall back to scratchr2 in development
// This proxy middleware must come last
app.use('/', proxy(proxyHost));
}
// Start listening
var port = process.env.PORT || 8333;
app.listen(port, function () {
process.stdout.write('Server listening on port ' + port + '\n');
if (proxyHost) {
process.stdout.write('Proxy host: ' + proxyHost + '\n');
}
});

View file

@ -0,0 +1,51 @@
var defaults = require('lodash.defaults');
var fs = require('fs');
var mustache = require('mustache');
var path = require('path');
render = function (template, route, config) {
config = config || {};
// Route definition
defaults(route, config);
// Render template
return mustache.render(template, route);
};
function MustacheRendererPlugin (options) {
if (!options.templatePath) throw new Error('MustacheRendererPlugin requires a templatePath option');
// Read template
var template = fs.readFileSync(options.templatePath, 'utf8');
this.template = template;
this.routes = options.routes || {};
this.config = options.config || {};
return this;
}
MustacheRendererPlugin.prototype.apply = function (compiler) {
var template = this.template;
var config = this.config;
var routes = this.routes;
compiler.plugin('emit', function (compilation, callback) {
var outputRoutes = {};
routes.forEach(function (route) {
var filename = route.view + '.html';
var content = render(template, route, config);
outputRoutes[route.pattern] = filename;
compilation.assets[filename] = {
source: function () {return content;},
size: function () {return content.length;}
};
});
var routeJson = JSON.stringify(outputRoutes);
compilation.assets['routes.json'] = {
source: function () {return routeJson;},
size: function () {return routeJson.length;}
};
callback();
});
};
module.exports = MustacheRendererPlugin;

View file

@ -63,7 +63,6 @@
"react-redux": "4.4.0",
"react-slick": "0.9.2",
"redux-thunk": "2.0.1",
"routes-to-nginx-conf": "0.0.4",
"sass-lint": "1.5.1",
"sass-loader": "2.0.1",
"scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master",

View file

@ -1,27 +0,0 @@
var crypto = require('crypto');
var render = require('./render.js');
/**
* Constructor
*/
function Handler (route) {
var output = render(route);
var checksum = crypto.createHash('md5').update(output).digest('hex');
return function (req, res) {
res.set({
'Content-Type': 'text/html',
'Cache-Control': 'public, max-age=31536000',
'Etag': '"' + checksum + '"'
});
res.send(output);
};
}
/**
* Export a new instance
*/
module.exports = function (route) {
return new Handler(route);
};

View file

@ -1,97 +0,0 @@
if (typeof process.env.NEW_RELIC_LICENSE_KEY === 'string') {
require('newrelic');
}
var compression = require('compression');
var express = require('express');
var path = require('path');
var proxy = require('express-http-proxy');
var url = require('url');
var handler = require('./handler');
var log = require('./log');
var routes = require('./routes.json');
var isProduction = process.env.NODE_ENV === 'production';
// Create server
var app = express();
app.disable('x-powered-by');
// Block POST & PUT requests in production
if (isProduction) {
app.use(function (req, res, next) {
if (req.method === 'GET') return next();
if (req.method === 'OPTIONS') return next();
if (req.method === 'HEAD') return next();
res.writeHead(405, {'content-type' : 'application/json'});
res.end('{"error": "Method not allowed"}');
});
}
// Server setup
app.use(log());
app.use(compression());
if (isProduction) {
app.use(express.static(path.resolve(__dirname, '../build'), {
etag: 'strong',
maxAge: '1y'
}));
}
app.use(function (req, res, next) {
req._path = url.parse(req.url).path;
next();
});
// Bind routes
for (var routeId in routes) {
var route = routes[routeId];
app.get(route.pattern, handler(route));
}
if (typeof process.env.NODE_SENTRY_DSN === 'string') {
var raven = require('raven');
app.get('/sentrythrow', function mainHandler () { throw new Error('Sentry Test'); });
// These handlers must be applied _AFTER_ other routes have been applied
app.use(raven.middleware.express.requestHandler(process.env.NODE_SENTRY_DSN));
app.use(raven.middleware.express.errorHandler(process.env.NODE_SENTRY_DSN));
app.use(function errorHandler (err, req, res, next) {
res.append('X-Sentry-ID:' + res.sentry);
res.status(500);
next(err);
});
raven.patchGlobal(process.env.NODE_SENTRY_DSN, function () {
process.exit(-1);
});
}
if (!isProduction) {
// Use webpack-dev-server in development
var webpackDevMiddleware = require('webpack-dev-middleware');
var webpack = require('webpack');
var compiler = webpack(require('../webpack.config.js'));
app.use(webpackDevMiddleware(compiler, {
headers: {
'X-From-Webpack': true
}
}));
var proxyHost = process.env.FALLBACK || '';
if (proxyHost !== '') {
// Fall back to scratchr2 in development
// This proxy middleware must come last
app.use('/', proxy(proxyHost));
}
}
// Start listening
var port = process.env.PORT || 8333;
app.listen(port, function () {
process.stdout.write('Server listening on port ' + port + '\n');
if (proxyHost) {
process.stdout.write('Proxy host: ' + proxyHost + '\n');
}
});

View file

@ -1,16 +0,0 @@
var defaults = require('lodash.defaults');
var fs = require('fs');
var mustache = require('mustache');
var path = require('path');
var config = require('./config');
module.exports = function (route) {
// Route definition
defaults(route, config);
// Render template
var location = path.resolve(__dirname, './template.html');
var template = fs.readFileSync(location, 'utf8');
return mustache.render(template, route);
};

View file

@ -1,39 +1,10 @@
var autoprefixer = require('autoprefixer');
var CopyWebpackPlugin = require('copy-webpack-plugin');
var MustacheRendererPlugin = require('./mustache-renderer-webpack-plugin');
var path = require('path');
var webpack = require('webpack');
var routes = require('./server/routes.json');
function HtmlGeneratorPlugin (options) {
this.options = options;
return this;
}
HtmlGeneratorPlugin.prototype.apply = function (compiler) {
var render = this.options.render;
var routes = this.options.routes;
compiler.plugin('emit', function (compilation, callback) {
var outputRoutes = {};
routes.forEach(function (route) {
var filename = route.view + '.html';
var content = render(route);
outputRoutes[route.pattern] = filename;
compilation.assets[filename] = {
source: function () {return content;},
size: function () {return content.length;}
};
});
var routeJson = JSON.stringify(outputRoutes);
compilation.assets['routes.json'] = {
source: function () {return routeJson;},
size: function () {return routeJson.length;}
};
callback();
});
};
var routes = require('./src/routes.json');
// Prepare all entry points
var entry = {
@ -86,9 +57,10 @@ module.exports = {
fs: 'empty'
},
plugins: [
new HtmlGeneratorPlugin({
render: require('./server/render.js'),
routes: routes
new MustacheRendererPlugin({
templatePath: path.resolve(__dirname, './src/template.html'),
routes: routes,
config: require('./src/template-config.js')
}),
new CopyWebpackPlugin([
{from: 'static'},