diff --git a/build/dev-runtime.js b/build/dev-runtime.js index ddf53b0e..9318f6ee 100644 --- a/build/dev-runtime.js +++ b/build/dev-runtime.js @@ -163,7 +163,8 @@ module.exports.DevelopmentRuntime = function({ req.pipe(proxyReq).pipe(res); }, error => { - renderError(res, error); + res.write(renderError(error)); + res.end(); } ); }); diff --git a/build/server-error.js b/build/server-error.js index 54040b8a..f56505cb 100644 --- a/build/server-error.js +++ b/build/server-error.js @@ -3,19 +3,19 @@ const React = require('react'); const RedBox = require('redbox-react').RedBoxError; const ReactDOMServer = require('react-dom/server'); -function renderError(res, error) { - res.write( - 'Server error' - ); +function renderError(error) { + const content = [ + 'Server error', + ]; const displayError = typeof error === 'string' ? new Error(error) : error; const errorComponent = ReactDOMServer.renderToString( React.createElement(RedBox, {error: displayError}) ); - res.write(errorComponent); + content.push(errorComponent); - res.write(''); - res.end(); + content.push(''); + return content.join(''); } module.exports.renderError = renderError; diff --git a/entries/server-entry.js b/entries/server-entry.js index ecadb5c2..9816ad51 100644 --- a/entries/server-entry.js +++ b/entries/server-entry.js @@ -5,10 +5,12 @@ import main from '__FRAMEWORK_SHARED_ENTRY__'; import CompilationMetaDataFactory from '../plugins/compilation-metadata-plugin'; import AssetsFactory from '../plugins/assets-plugin'; import ContextFactory from '../plugins/context-plugin'; +import ServerErrorFactory from '../plugins/server-error-plugin'; const CompilationMetaData = CompilationMetaDataFactory(); const Assets = AssetsFactory(); const Context = ContextFactory(); +const ServerErrorHandling = ServerErrorFactory(); /* Webpack has a configuration option called `publicPath`, which determines the @@ -59,6 +61,9 @@ export async function start({port}) { async function reload() { const app = await initialize(); app.plugins = [Assets, Context].concat(app.plugins); + if (__DEV__) { + app.plugins.unshift(ServerErrorHandling); + } state.serve = app.callback(); state.app = app; } diff --git a/package.json b/package.json index d6ff365e..f859d148 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "compose-middleware": "4.0.0", "compression-webpack-plugin": "^1.1.3", "core-js": "^2.5.3", + "debug": "^3.1.0", "enzyme-adapter-react-16": "^1.1.1", "enzyme-to-json": "^3.3.0", "es6-object-assign": "^1.1.0", diff --git a/plugins/server-error-plugin.js b/plugins/server-error-plugin.js new file mode 100644 index 00000000..637c116e --- /dev/null +++ b/plugins/server-error-plugin.js @@ -0,0 +1,14 @@ +/* eslint-env node */ + +const renderError = require('../build/server-error').renderError; + +module.exports = function() { + return async function middleware(ctx, next) { + try { + await next(); + } catch (err) { + ctx.status = err.statusCode || err.status || 500; + ctx.body = renderError(err); + } + }; +}; diff --git a/test/cli/dev.js b/test/cli/dev.js index 7d1216a7..68d955b7 100644 --- a/test/cli/dev.js +++ b/test/cli/dev.js @@ -65,12 +65,17 @@ test('`fusion dev` works with assets with cdnUrl', async t => { }); test('`fusion dev` top-level error', async t => { - const dir = path.resolve( - __dirname, - '../fixtures/server-error-route-component' - ); + const dir = path.resolve(__dirname, '../fixtures/server-startup-error'); + const {res, proc} = await dev(`--dir=${dir}`); + t.ok(res.includes('server-startup-error')); + proc.kill(); + t.end(); +}); + +test('`fusion dev` server render error', async t => { + const dir = path.resolve(__dirname, '../fixtures/server-render-error'); const {res, proc} = await dev(`--dir=${dir}`); - t.ok(res.includes('top-level-route-error')); + t.ok(res.includes('server-render-error')); proc.kill(); t.end(); }); diff --git a/test/fixtures/server-error-route-component/src/home.js b/test/fixtures/server-error-route-component/src/home.js deleted file mode 100644 index dff3613d..00000000 --- a/test/fixtures/server-error-route-component/src/home.js +++ /dev/null @@ -1 +0,0 @@ -throw new Error('top-level-route-error'); diff --git a/test/fixtures/server-render-error/src/home.js b/test/fixtures/server-render-error/src/home.js new file mode 100644 index 00000000..d9cafe48 --- /dev/null +++ b/test/fixtures/server-render-error/src/home.js @@ -0,0 +1,7 @@ +import React from 'react'; + +const Home = () => { + throw new Error('server-render-error'); + return null; +}; +export default Home; diff --git a/test/fixtures/server-render-error/src/main.js b/test/fixtures/server-render-error/src/main.js new file mode 100644 index 00000000..c58c3502 --- /dev/null +++ b/test/fixtures/server-render-error/src/main.js @@ -0,0 +1,13 @@ +import React from 'react'; +import App from 'fusion-react'; +import Router, {Route, Switch} from 'fusion-plugin-react-router'; + +import Home from './home.js'; + +export default () => { + const app = new App( + + ); + app.plugin(Router, {}); + return app; +}; diff --git a/test/fixtures/server-startup-error/src/home.js b/test/fixtures/server-startup-error/src/home.js new file mode 100644 index 00000000..37a9689e --- /dev/null +++ b/test/fixtures/server-startup-error/src/home.js @@ -0,0 +1 @@ +throw new Error('server-startup-error'); diff --git a/test/fixtures/server-error-route-component/src/main.js b/test/fixtures/server-startup-error/src/main.js similarity index 100% rename from test/fixtures/server-error-route-component/src/main.js rename to test/fixtures/server-startup-error/src/main.js diff --git a/test/run-command.js b/test/run-command.js index a7e32287..ca8eaea8 100644 --- a/test/run-command.js +++ b/test/run-command.js @@ -75,7 +75,13 @@ async function waitForServer(port) { }); started = true; } catch (e) { - numTries++; + // Allow returning true for 500 status code errors to test error states + if (e.statusCode === 500) { + started = true; + res = e.response.body; + } else { + numTries++; + } } } if (!started) {