Skip to content

Commit d725fd9

Browse files
authored
Implement error overlay (#764)
1 parent 61e5403 commit d725fd9

File tree

10 files changed

+178
-2
lines changed

10 files changed

+178
-2
lines changed

client/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
var url = require("url");
33
var stripAnsi = require("strip-ansi");
44
var socket = require("./socket");
5+
var overlay = require("./overlay");
56

67
function getCurrentScriptSource() {
78
// `document.currentScript` is the most accurate way to find the current script,
@@ -32,6 +33,7 @@ var hot = false;
3233
var initial = true;
3334
var currentHash = "";
3435
var logLevel = "info";
36+
var useOverlay = false;
3537

3638
function log(level, msg) {
3739
if(logLevel === "info" && level === "info")
@@ -71,8 +73,14 @@ var onSocketMsg = {
7173
"log-level": function(level) {
7274
logLevel = level;
7375
},
76+
"overlay": function(overlay) {
77+
if(typeof document !== "undefined") {
78+
useOverlay = overlay;
79+
}
80+
},
7481
ok: function() {
7582
sendMsg("Ok");
83+
if(useOverlay) overlay.clear();
7684
if(initial) return initial = false;
7785
reloadApp();
7886
},
@@ -99,6 +107,7 @@ var onSocketMsg = {
99107
sendMsg("Errors", strippedErrors);
100108
for(var i = 0; i < strippedErrors.length; i++)
101109
console.error(strippedErrors[i]);
110+
if(useOverlay) overlay.showErrors(errors);
102111
},
103112
close: function() {
104113
log("error", "[WDS] Disconnected!");

client/overlay.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// The error overlay is inspired (and mostly copied) from Create React App (https://github.com/facebookincubator/create-react-app)
2+
// They, in turn, got inspired by webpack-hot-middleware (https://github.com/glenjamin/webpack-hot-middleware).
3+
var ansiHTML = require("ansi-html");
4+
var Entities = require("html-entities").AllHtmlEntities;
5+
var entities = new Entities();
6+
7+
var colors = {
8+
reset: ["transparent", "transparent"],
9+
black: "181818",
10+
red: "E36049",
11+
green: "B3CB74",
12+
yellow: "FFD080",
13+
blue: "7CAFC2",
14+
magenta: "7FACCA",
15+
cyan: "C3C2EF",
16+
lightgrey: "EBE7E3",
17+
darkgrey: "6D7891"
18+
};
19+
ansiHTML.setColors(colors);
20+
21+
function createOverlayIframe(onIframeLoad) {
22+
var iframe = document.createElement("iframe");
23+
iframe.id = "webpack-dev-server-client-overlay";
24+
iframe.src = "about:blank";
25+
iframe.style.position = "fixed";
26+
iframe.style.left = 0;
27+
iframe.style.top = 0;
28+
iframe.style.right = 0;
29+
iframe.style.bottom = 0;
30+
iframe.style.width = "100vw";
31+
iframe.style.height = "100vh";
32+
iframe.style.border = "none";
33+
iframe.style.zIndex = 9999999999;
34+
iframe.onload = onIframeLoad;
35+
return iframe;
36+
}
37+
38+
function addOverlayDivTo(iframe) {
39+
var div = iframe.contentDocument.createElement("div");
40+
div.id = "webpack-dev-server-client-overlay-div";
41+
div.style.position = "fixed";
42+
div.style.boxSizing = "border-box";
43+
div.style.left = 0;
44+
div.style.top = 0;
45+
div.style.right = 0;
46+
div.style.bottom = 0;
47+
div.style.width = "100vw";
48+
div.style.height = "100vh";
49+
div.style.backgroundColor = "black";
50+
div.style.color = "#E8E8E8";
51+
div.style.fontFamily = "Menlo, Consolas, monospace";
52+
div.style.fontSize = "large";
53+
div.style.padding = "2rem";
54+
div.style.lineHeight = "1.2";
55+
div.style.whiteSpace = "pre-wrap";
56+
div.style.overflow = "auto";
57+
iframe.contentDocument.body.appendChild(div);
58+
return div;
59+
}
60+
61+
var overlayIframe = null;
62+
var overlayDiv = null;
63+
var lastOnOverlayDivReady = null;
64+
65+
function ensureOverlayDivExists(onOverlayDivReady) {
66+
if(overlayDiv) {
67+
// Everything is ready, call the callback right away.
68+
onOverlayDivReady(overlayDiv);
69+
return;
70+
}
71+
72+
// Creating an iframe may be asynchronous so we'll schedule the callback.
73+
// In case of multiple calls, last callback wins.
74+
lastOnOverlayDivReady = onOverlayDivReady;
75+
76+
if(overlayIframe) {
77+
// We're already creating it.
78+
return;
79+
}
80+
81+
// Create iframe and, when it is ready, a div inside it.
82+
overlayIframe = createOverlayIframe(function onIframeLoad() {
83+
overlayDiv = addOverlayDivTo(overlayIframe);
84+
// Now we can talk!
85+
lastOnOverlayDivReady(overlayDiv);
86+
});
87+
88+
// Zalgo alert: onIframeLoad() will be called either synchronously
89+
// or asynchronously depending on the browser.
90+
// We delay adding it so `overlayIframe` is set when `onIframeLoad` fires.
91+
document.body.appendChild(overlayIframe);
92+
}
93+
94+
function showErrorOverlay(message) {
95+
ensureOverlayDivExists(function onOverlayDivReady(overlayDiv) {
96+
// Make it look similar to our terminal.
97+
overlayDiv.innerHTML =
98+
"<span style=\"color: #" +
99+
colors.red +
100+
"\">Failed to compile.</span><br><br>" +
101+
ansiHTML(entities.encode(message));
102+
});
103+
}
104+
105+
function destroyErrorOverlay() {
106+
if(!overlayDiv) {
107+
// It is not there in the first place.
108+
return;
109+
}
110+
111+
// Clean up and reset internal state.
112+
document.body.removeChild(overlayIframe);
113+
overlayDiv = null;
114+
overlayIframe = null;
115+
lastOnOverlayDivReady = null;
116+
}
117+
118+
// Successful compilation.
119+
exports.clear = function handleSuccess() {
120+
destroyErrorOverlay();
121+
}
122+
123+
// Compilation with errors (e.g. syntax error or missing modules).
124+
exports.showErrors = function handleErrors(errors) {
125+
showErrorOverlay(errors[0]);
126+
}

examples/overlay/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Overlay
2+
3+
```shell
4+
node ../../bin/webpack-dev-server.js --open
5+
```
6+
7+
## What should happen
8+
9+
The script should open the browser and show a heading with "Example: overlay".
10+
11+
In `app.js`, make a syntax error. The page should now refresh and show a full screen error overlay that shows the syntax error.

examples/overlay/app.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
document.write("It's working.");
2+
3+
// This results in an error:
4+
// if(!window) require("test");

examples/overlay/index.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<script src="/bundle.js" type="text/javascript" charset="utf-8"></script>
5+
</head>
6+
<body>
7+
<h1>Example: overlay</h1>
8+
</body>
9+
</html>

examples/overlay/webpack.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
context: __dirname,
3+
entry: "./app.js",
4+
devServer: {
5+
overlay: true
6+
}
7+
}

lib/Server.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ function Server(compiler, options) {
3434
this.hot = options.hot || options.hotOnly;
3535
this.headers = options.headers;
3636
this.clientLogLevel = options.clientLogLevel;
37+
this.clientOverlay = options.overlay;
3738
this.sockets = [];
3839
this.contentBaseWatchers = [];
3940

@@ -400,6 +401,9 @@ Server.prototype.listen = function() {
400401
if(this.clientLogLevel)
401402
this.sockWrite([conn], "log-level", this.clientLogLevel);
402403

404+
if(this.clientOverlay)
405+
this.sockWrite([conn], "overlay", this.clientOverlay);
406+
403407
if(this.hot) this.sockWrite([conn], "hot");
404408

405409
if(!this._stats) return;

lib/optionsSchema.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@
6767
"error"
6868
]
6969
},
70+
"overlay": {
71+
"description": "Shows an error overlay in browser.",
72+
"type": "boolean"
73+
},
7074
"key": {
7175
"description": "The contents of a SSL key.",
7276
"anyOf": [

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
"webpack": "^2.2.0"
88
},
99
"dependencies": {
10+
"ansi-html": "0.0.7",
1011
"chokidar": "^1.6.0",
1112
"compression": "^1.5.2",
1213
"connect-history-api-fallback": "^1.3.0",
1314
"express": "^4.13.3",
15+
"html-entities": "^1.2.0",
1416
"http-proxy-middleware": "~0.17.1",
1517
"opn": "4.0.2",
1618
"portfinder": "^1.0.9",
@@ -64,7 +66,7 @@
6466
"client-live": "webpack ./client/live.js client/live.bundle.js --color --config client/webpack.config.js -p",
6567
"client-index": "webpack ./client/index.js client/index.bundle.js --color --config client/webpack.config.js -p",
6668
"client-sockjs": "webpack ./client/sockjs.js client/sockjs.bundle.js --color --config client/webpack.sockjs.config.js -p",
67-
"lint": "eslint bin lib test examples client/{index,live,socket,sockjs,webpack.config}.js",
69+
"lint": "eslint bin lib test examples client/{index,live,socket,sockjs,overlay,webpack.config}.js",
6870
"beautify": "npm run lint -- --fix",
6971
"test": "mocha --full-trace --check-leaks",
7072
"posttest": "npm run -s lint",

test/Validation.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe("Validation", function() {
3636
message: [
3737
" - configuration has an unknown property 'asdf'. These properties are valid:",
3838
" object { hot?, hotOnly?, lazy?, host?, filename?, publicPath?, port?, socket?, " +
39-
"watchOptions?, headers?, clientLogLevel?, key?, cert?, ca?, pfx?, pfxPassphrase?, " +
39+
"watchOptions?, headers?, clientLogLevel?, overlay?, key?, cert?, ca?, pfx?, pfxPassphrase?, " +
4040
"inline?, public?, https?, contentBase?, watchContentBase?, open?, features?, " +
4141
"compress?, proxy?, historyApiFallback?, staticOptions?, setup?, stats?, reporter?, " +
4242
"noInfo?, quiet?, serverSideRender?, index?, log?, warn? }"

0 commit comments

Comments
 (0)