commit e188388f87069cdc7d501b385d6b0e46c98fed60
parent 4d62b4fd00897193bb4fc124203f29fc3beb1d3d
Author: Konstantin Tarkus <hello@tarkus.me>
Date: Tue, 1 Mar 2016 21:21:04 +0300
Add global error handling, compiled Jade templates for index and error pages
Diffstat:
7 files changed, 123 insertions(+), 77 deletions(-)
diff --git a/package.json b/package.json
@@ -5,8 +5,8 @@
"npm": ">=3.3 <4"
},
"dependencies": {
- "babel-polyfill": "6.5.0",
- "babel-runtime": "6.5.0",
+ "babel-polyfill": "6.6.1",
+ "babel-runtime": "6.6.1",
"bluebird": "3.3.3",
"body-parser": "1.15.0",
"classnames": "2.2.3",
@@ -24,12 +24,13 @@
"isomorphic-style-loader": "0.0.10",
"jade": "1.11.0",
"jsonwebtoken": "5.7.0",
- "markdown-it": "^6.0.0",
+ "markdown-it": "6.0.0",
"node-fetch": "1.3.3",
"normalize.css": "3.0.3",
"passport": "0.3.2",
"passport-facebook": "2.1.0",
"pg": "4.5.1",
+ "pretty-error": "2.0.0",
"react": "0.14.7",
"react-dom": "0.14.7",
"react-routing": "0.0.7",
@@ -39,16 +40,16 @@
"devDependencies": {
"assets-webpack-plugin": "^3.3.0",
"autoprefixer": "^6.3.3",
- "babel-cli": "^6.5.1",
+ "babel-cli": "^6.6.0",
"babel-eslint": "^5.0.0",
"babel-loader": "^6.2.4",
"babel-plugin-react-transform": "^2.0.0",
- "babel-plugin-transform-runtime": "^6.5.2",
- "babel-preset-es2015": "^6.5.0",
+ "babel-plugin-transform-runtime": "^6.6.0",
+ "babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-0": "^6.5.0",
"browser-sync": "^2.11.1",
- "core-js": "^2.1.1",
+ "core-js": "^2.1.3",
"css-loader": "^0.23.1",
"csscomb": "^3.1.8",
"del": "^2.2.0",
@@ -61,15 +62,16 @@
"gaze": "^0.5.2",
"git-repository": "^0.1.1",
"glob": "^7.0.0",
+ "jade-loader": "^0.8.0",
"jest-cli": "^0.8.2",
"jscs": "^2.10.1",
"json-loader": "^0.5.4",
"mkdirp": "^0.5.1",
"ncp": "^2.0.0",
- "postcss": "^5.0.17",
+ "postcss": "^5.0.18",
"postcss-import": "^8.0.2",
"postcss-loader": "^0.8.1",
- "postcss-scss": "^0.1.5",
+ "postcss-scss": "^0.1.6",
"precss": "^1.4.0",
"raw-loader": "^0.5.1",
"react-transform-catch-errors": "^1.0.2",
diff --git a/src/components/Html/Html.js b/src/components/Html/Html.js
@@ -1,57 +0,0 @@
-/**
- * React Starter Kit (https://www.reactstarterkit.com/)
- *
- * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE.txt file in the root directory of this source tree.
- */
-
-import React, { Component, PropTypes } from 'react';
-import { analytics } from '../../config';
-
-// https://analytics.google.com/
-const trackingCode =
- 'window.ga=function(){ga.q.push(arguments)};ga.q=[];ga.l=+new Date;' +
- `ga('create','${analytics.google.trackingId}','auto');ga('send','pageview')`;
-
-class Html extends Component {
-
- static propTypes = {
- title: PropTypes.string,
- description: PropTypes.string,
- css: PropTypes.string,
- body: PropTypes.string.isRequired,
- entry: PropTypes.string.isRequired,
- };
-
- static defaultProps = {
- title: '',
- description: '',
- };
-
- render() {
- return (
- <html className="no-js" lang="">
- <head>
- <meta charSet="utf-8" />
- <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
- <title>{this.props.title}</title>
- <meta name="description" content={this.props.description} />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <link rel="apple-touch-icon" href="apple-touch-icon.png" />
- <style id="css" dangerouslySetInnerHTML={{ __html: this.props.css }} />
- </head>
- <body>
- <div id="app" dangerouslySetInnerHTML={{ __html: this.props.body }} />
- <script src={this.props.entry}></script>
- <script dangerouslySetInnerHTML={{ __html: trackingCode }} />
- <script src="https://www.google-analytics.com/analytics.js" async defer />
- </body>
- </html>
- );
- }
-
-}
-
-export default Html;
diff --git a/src/components/Html/package.json b/src/components/Html/package.json
@@ -1,6 +0,0 @@
-{
- "name": "Html",
- "version": "0.0.0",
- "private": true,
- "main": "./Html.js"
-}
diff --git a/src/server.js b/src/server.js
@@ -15,14 +15,13 @@ import bodyParser from 'body-parser';
import expressJwt from 'express-jwt';
import expressGraphQL from 'express-graphql';
import jwt from 'jsonwebtoken';
-import React from 'react';
import ReactDOM from 'react-dom/server';
+import PrettyError from 'pretty-error';
import passport from './core/passport';
import schema from './data/schema';
import Router from './routes';
-import Html from './components/Html';
import assets from './assets';
-import { port, auth } from './config';
+import { port, auth, analytics } from './config';
const server = global.server = express();
@@ -82,7 +81,13 @@ server.use('/graphql', expressGraphQL(req => ({
server.get('*', async (req, res, next) => {
try {
let statusCode = 200;
+ const template = require('./views/index.jade');
const data = { title: '', description: '', css: '', body: '', entry: assets.main.js };
+
+ if (process.env.NODE_ENV === 'production') {
+ data.trackingId = analytics.google.trackingId;
+ }
+
const css = [];
const context = {
insertCss: styles => css.push(styles._getCss()),
@@ -96,14 +101,32 @@ server.get('*', async (req, res, next) => {
data.css = css.join('');
});
- const html = ReactDOM.renderToStaticMarkup(<Html {...data} />);
- res.status(statusCode).send(`<!doctype html>\n${html}`);
+ res.status(statusCode);
+ res.send(template(data));
} catch (err) {
next(err);
}
});
//
+// Error handling
+// -----------------------------------------------------------------------------
+const pe = new PrettyError();
+pe.skipNodeFiles();
+pe.skipPackage('express');
+
+server.use((err, req, res, next) => { // eslint-disable-line no-unused-vars
+ console.log(pe.render(err)); // eslint-disable-line no-console
+ const template = require('./views/error.jade');
+ const statusCode = err.status || 500;
+ res.status(statusCode);
+ res.send(template({
+ message: err.message,
+ stack: process.env.NODE_ENV === 'production' ? '' : err.stack,
+ }));
+});
+
+//
// Launch the server
// -----------------------------------------------------------------------------
server.listen(port, () => {
diff --git a/src/views/error.jade b/src/views/error.jade
@@ -0,0 +1,63 @@
+doctype html
+html(lang="en")
+ head
+ meta(charset="utf-8")
+ title Internal Server Error
+ meta(name="viewport", content="width=device-width, initial-scale=1")
+ style.
+
+ * {
+ line-height: 1.2;
+ margin: 0;
+ }
+
+ html {
+ color: #888;
+ display: table;
+ font-family: sans-serif;
+ height: 100%;
+ text-align: center;
+ width: 100%;
+ }
+
+ body {
+ display: table-cell;
+ vertical-align: middle;
+ margin: 2em auto;
+ }
+
+ h1 {
+ color: #555;
+ font-size: 2em;
+ font-weight: 400;
+ }
+
+ p {
+ margin: 0 auto;
+ width: 280px;
+ }
+
+ pre {
+ text-align: left;
+ max-width: 1000px;
+ margin: 0 auto;
+ }
+
+ @media only screen and (max-width: 280px) {
+
+ body, p {
+ width: 95%;
+ }
+
+ h1 {
+ font-size: 1.5em;
+ margin: 0 0 0.3em;
+ }
+
+ }
+
+ body
+ h1 Internal Server Error
+ p Sorry, something went wrong.
+ pre= stack
+// IE needs 512+ bytes: http://blogs.msdn.com/b/ieinternals/archive/2010/08/19/http-error-pages-in-internet-explorer.aspx
diff --git a/src/views/index.jade b/src/views/index.jade
@@ -0,0 +1,18 @@
+doctype html
+html(class="no-js", lang="")
+ head
+ meta(charset="utf-8")
+ meta(http-equiv="x-ua-compatible", content="ie=edge")
+ title= title
+ meta(name="description", description=description)
+ meta(name="viewport", content="width=device-width, initial-scale=1")
+ link(rel="apple-touch-icon", href="apple-touch-icon.png")
+ style#css!= css
+ body
+ #app!= body
+ script(src=entry)
+ script.
+ window.ga=function(){ga.q.push(arguments)};ga.q=[];ga.l=+new Date;
+ ga('create','#{trackingId}','auto');ga('send','pageview')
+ if trackingId
+ script(src="https://www.google-analytics.com/analytics.js", async=true, defer=true)
diff --git a/tools/webpack.config.js b/tools/webpack.config.js
@@ -92,6 +92,9 @@ const config = {
}, {
test: /\.(eot|ttf|wav|mp3)$/,
loader: 'file-loader',
+ }, {
+ test: /\.jade$/,
+ loader: 'jade-loader',
},
],
},