diff --git a/package.json b/package.json
index a01f058aa08..0a1f1773e67 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"index.js"
],
"dependencies": {
+ "ansi-to-html": "^0.6.4",
"babel-code-frame": "^6.26.0",
"babel-core": "^6.25.0",
"babel-generator": "^6.25.0",
diff --git a/src/Bundler.js b/src/Bundler.js
index 83248f190e5..0e8d38bb72c 100644
--- a/src/Bundler.js
+++ b/src/Bundler.js
@@ -60,7 +60,7 @@ class Bundler extends EventEmitter {
this.watcher = null;
this.hmr = null;
this.bundleHashes = null;
- this.errored = false;
+ this.error = null;
this.buildQueue = new PromiseQueue(this.processAsset.bind(this));
this.rebuildTimeout = null;
@@ -213,7 +213,7 @@ class Bundler extends EventEmitter {
let isInitialBundle = !this.entryAssets;
let startTime = Date.now();
this.pending = true;
- this.errored = false;
+ this.error = null;
logger.clear();
logger.status(emoji.progress, 'Building...');
@@ -290,7 +290,7 @@ class Bundler extends EventEmitter {
this.emit('bundled', this.mainBundle);
return this.mainBundle;
} catch (err) {
- this.errored = true;
+ this.error = err;
logger.error(err);
if (this.hmr) {
this.hmr.emitError(err);
@@ -493,7 +493,7 @@ class Bundler extends EventEmitter {
return;
}
- if (!this.errored) {
+ if (!this.error) {
logger.status(emoji.progress, `Building ${asset.basename}...`);
}
@@ -713,7 +713,11 @@ class Bundler extends EventEmitter {
async serve(port = 1234, https = false) {
this.server = await Server.serve(this, port, https);
- this.bundle();
+ try {
+ await this.bundle();
+ } catch (e) {
+ // ignore: server can still work with errored bundler
+ }
return this.server;
}
}
diff --git a/src/Server.js b/src/Server.js
index cf5f1d6fc1e..08d29dbb483 100644
--- a/src/Server.js
+++ b/src/Server.js
@@ -5,10 +5,14 @@ const getPort = require('get-port');
const serverErrors = require('./utils/customErrors').serverErrors;
const generateCertificate = require('./utils/generateCertificate');
const getCertificate = require('./utils/getCertificate');
+const prettyError = require('./utils/prettyError');
+const AnsiToHtml = require('ansi-to-html');
const logger = require('./Logger');
const path = require('path');
const url = require('url');
+const ansiToHtml = new AnsiToHtml({newline: true});
+
serveStatic.mime.define({
'application/wasm': ['wasm']
});
@@ -45,8 +49,8 @@ function middleware(bundler) {
function respond() {
let {pathname} = url.parse(req.url);
- if (bundler.errored) {
- return send500();
+ if (bundler.error) {
+ return send500(bundler.error);
} else if (
!pathname.startsWith(bundler.options.publicURL) ||
path.extname(pathname) === ''
@@ -71,10 +75,28 @@ function middleware(bundler) {
}
}
- function send500() {
- res.setHeader('Content-Type', 'text/plain; charset=utf-8');
+ function send500(error) {
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.writeHead(500);
- res.end('🚨 Build error, check the console for details.');
+ let errorMesssge = '
🚨 Build Error
';
+ if (process.env.NODE_ENV === 'production') {
+ errorMesssge += 'Check the console for details.
';
+ } else {
+ const {message, stack} = prettyError(error, {color: true});
+ errorMesssge += `${message}
`;
+ if (stack) {
+ errorMesssge += `${ansiToHtml.toHtml(
+ stack
+ )}
`;
+ }
+ }
+ res.end(
+ [
+ ``,
+ `🚨 Build Error`,
+ `${errorMesssge}`
+ ].join('')
+ );
}
function send404() {
diff --git a/test/integration/bundler-error-syntax-error/index.html b/test/integration/bundler-error-syntax-error/index.html
new file mode 100644
index 00000000000..46839a96b99
--- /dev/null
+++ b/test/integration/bundler-error-syntax-error/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+ Hello world
+
+
+
+
diff --git a/test/integration/bundler-error-syntax-error/index.js b/test/integration/bundler-error-syntax-error/index.js
new file mode 100644
index 00000000000..78ffccdd0f8
--- /dev/null
+++ b/test/integration/bundler-error-syntax-error/index.js
@@ -0,0 +1 @@
+.invalid_js
diff --git a/test/server.js b/test/server.js
index 40cea6b06ab..53a67c19e53 100644
--- a/test/server.js
+++ b/test/server.js
@@ -23,14 +23,13 @@ describe('server', function() {
rejectUnauthorized: false
},
res => {
- if (res.statusCode !== 200) {
- return reject(new Error('Request failed: ' + res.statusCode));
- }
-
res.setEncoding('utf8');
let data = '';
res.on('data', c => (data += c));
res.on('end', () => {
+ if (res.statusCode !== 200) {
+ return reject(data);
+ }
resolve(data);
});
}
@@ -85,7 +84,6 @@ describe('server', function() {
try {
await get('/');
- throw new Error('GET / responded with 200');
} catch (err) {
assert.equal(err.message, 'Request failed: 500');
}
@@ -94,6 +92,57 @@ describe('server', function() {
await get('/');
});
+ it('should serve a 500 response with error stack trace when bundler has errors', async function() {
+ let b = bundler(
+ __dirname + '/integration/bundler-error-syntax-error/index.html'
+ );
+
+ server = await b.serve(0);
+ let resp;
+ try {
+ await get('/');
+ } catch (e) {
+ resp = e;
+ }
+
+ assert(resp.includes('🚨 Build Error'), 'has title');
+ assert(resp.includes('🚨 Build Error
'), 'has h1');
+ assert(
+ resp.includes(''),
+ 'has code frame'
+ );
+ assert(resp.includes('invalid_js'), 'code frame has invalid code');
+ });
+
+ it('should serve a 500 response without stack trace when bundler has errors in production', async function() {
+ let NODE_ENV = process.env.NODE_ENV;
+ process.env.NODE_ENV = 'production';
+ let b = bundler(
+ __dirname + '/integration/bundler-error-syntax-error/index.html'
+ );
+
+ server = await b.serve(0);
+ let resp;
+ try {
+ await get('/');
+ } catch (e) {
+ resp = e;
+ }
+
+ assert(resp.includes('
🚨 Build Error'), 'has title');
+ assert(resp.includes('
🚨 Build Error
'), 'has h1');
+ assert(
+ resp.includes('
Check the console for details.
'),
+ 'has description'
+ );
+ assert(
+ !resp.includes('
'),
+ 'do not have code frame'
+ );
+ assert(!resp.includes('invalid_js'), 'source code is not shown');
+ process.env.NODE_ENV = NODE_ENV;
+ });
+
it('should support HTTPS', async function() {
let b = bundler(__dirname + '/integration/commonjs/index.js');
server = await b.serve(0, true);
diff --git a/yarn.lock b/yarn.lock
index 202ca80dd7b..6550c5c8f36 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -145,6 +145,12 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
+ansi-to-html@^0.6.4:
+ version "0.6.4"
+ resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.4.tgz#8b14ace87f8b3d25367d03cd5300d60be17cf9e0"
+ dependencies:
+ entities "^1.1.1"
+
any-observable@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b"