Skip to content

Commit 418f79e

Browse files
committed
refactor: add react-error-overlay
1 parent 45e7df1 commit 418f79e

12 files changed

+919
-3
lines changed

Diff for: .eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ packages/webpack-mini-css-extract
55
packages/dev-utils/lib/open-browser.js
66
packages/driver-webpack/lib/webpack/plugins/InlineChunkHtmlPlugin.js
77
packages/driver-webpack/lib/webpack/plugins/WatchMissingNodeModulesPlugin.js
8+
packages/driver-webpack/lib/webpack/overlay

Diff for: packages/driver-webpack/lib/index.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const path = require( 'path' )
22
const exit = require( 'exit' )
33
const chalk = require( 'chalk' )
44
const { Driver } = require( '@nut-project/core' )
5-
const { chain, serve, build, hot, webpack } = require( '@nut-project/webpack' )
5+
const { chain, serve, build, webpack } = require( '@nut-project/webpack' )
66
const { logger, detectPort } = require( '@nut-project/dev-utils' )
77
const { exposeWebpack, extendWebpack, extendDevServer } = require( './webpack' )
88
const schema = require( './schema' )
@@ -140,8 +140,6 @@ class WebpackDriver extends Driver {
140140

141141
this.callHook( 'dangerously_serverOptions', serverOptions )
142142

143-
hot( webpackConfig, serverOptions )
144-
145143
const compiler = webpack( webpackConfig )
146144

147145
this.callHook( 'compiler', compiler )

Diff for: packages/driver-webpack/lib/webpack/error-overlay.js

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const errorOverlayMiddleware = require( './overlay/error-overlay-middleware' )
2+
3+
exports.extend = function ( config, context = {} ) {
4+
const { env = '' } = context
5+
6+
if ( env === 'production' ) {
7+
return
8+
}
9+
10+
const hotClientEntry = require.resolve( './overlay/client' )
11+
12+
const entryKeys = config.entryPoints.store.keys()
13+
for ( const key of entryKeys ) {
14+
if ( config.entryPoints.has( key ) ) {
15+
config.entry( key ).prepend( hotClientEntry )
16+
}
17+
}
18+
}
19+
20+
exports.extendDevServer = function ( serverOptions, context = {} ) {
21+
const { env = '' } = context
22+
23+
if ( env === 'production' ) {
24+
return
25+
}
26+
27+
serverOptions.transportMode = 'ws'
28+
serverOptions.injectClient = false
29+
serverOptions.overlay = false
30+
31+
const oldBefore = serverOptions.before
32+
33+
serverOptions.before = function ( app, server ) {
34+
if ( oldBefore ) {
35+
oldBefore( app, server )
36+
}
37+
38+
app.use( errorOverlayMiddleware() )
39+
}
40+
}

Diff for: packages/driver-webpack/lib/webpack/hot.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const { webpack } = require( '@nut-project/webpack' )
2+
3+
exports.extend = function ( config, context = {} ) {
4+
const { env = '' } = context
5+
6+
if ( env === 'production' ) {
7+
return
8+
}
9+
10+
config.plugin( 'hmr' )
11+
.use( webpack.HotModuleReplacementPlugin, [] )
12+
}
13+
14+
exports.extendDevServer = function ( serverOptions, context = {} ) {
15+
const { env = '' } = context
16+
17+
if ( env === 'production' ) {
18+
return
19+
}
20+
21+
serverOptions.hot = true
22+
}

Diff for: packages/driver-webpack/lib/webpack/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ const abilities = [
33
require( './public-path' ),
44
require( './filename' ),
55
require( './entry' ),
6+
// error-overlay should be after entry
7+
require( './error-overlay' ),
68
require( './babel' ),
79
require( './css' ),
810
require( './html' ),
+276
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
/**
2+
* from: create-react-app
3+
* Copyright (c) 2015-present, Facebook, Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
// This alternative WebpackDevServer combines the functionality of:
12+
// https://github.com/webpack/webpack-dev-server/blob/webpack-1/client/index.js
13+
// https://github.com/webpack/webpack/blob/webpack-1/hot/dev-server.js
14+
15+
// It only supports their simplest configuration (hot updates on same server).
16+
// It makes some opinionated choices on top, like adding a syntax error overlay
17+
// that looks similar to our console output. The error overlay is inspired by:
18+
// https://github.com/glenjamin/webpack-hot-middleware
19+
20+
var stripAnsi = require('strip-ansi');
21+
var url = require('url');
22+
var launchEditorEndpoint = require('./launch-editor-endpoint');
23+
var formatWebpackMessages = require('./format-webpack-messages');
24+
var ErrorOverlay = require('react-error-overlay');
25+
26+
ErrorOverlay.setEditorHandler(function editorHandler(errorLocation) {
27+
// Keep this sync with errorOverlayMiddleware.js
28+
fetch(
29+
launchEditorEndpoint +
30+
'?fileName=' +
31+
window.encodeURIComponent(errorLocation.fileName) +
32+
'&lineNumber=' +
33+
window.encodeURIComponent(errorLocation.lineNumber || 1) +
34+
'&colNumber=' +
35+
window.encodeURIComponent(errorLocation.colNumber || 1)
36+
);
37+
});
38+
39+
// We need to keep track of if there has been a runtime error.
40+
// Essentially, we cannot guarantee application state was not corrupted by the
41+
// runtime error. To prevent confusing behavior, we forcibly reload the entire
42+
// application. This is handled below when we are notified of a compile (code
43+
// change).
44+
// See https://github.com/facebook/create-react-app/issues/3096
45+
var hadRuntimeError = false;
46+
ErrorOverlay.startReportingRuntimeErrors({
47+
onError: function() {
48+
hadRuntimeError = true;
49+
},
50+
filename: '/static/js/bundle.js',
51+
});
52+
53+
if (module.hot && typeof module.hot.dispose === 'function') {
54+
module.hot.dispose(function() {
55+
// TODO: why do we need this?
56+
ErrorOverlay.stopReportingRuntimeErrors();
57+
});
58+
}
59+
60+
// Connect to WebpackDevServer via a socket.
61+
var connection = new WebSocket(
62+
url.format({
63+
protocol: 'ws',
64+
hostname: window.location.hostname,
65+
port: window.location.port,
66+
// Hardcoded in WebpackDevServer
67+
pathname: '/sockjs-node',
68+
})
69+
);
70+
71+
// Unlike WebpackDevServer client, we won't try to reconnect
72+
// to avoid spamming the console. Disconnect usually happens
73+
// when developer stops the server.
74+
connection.onclose = function() {
75+
if (typeof console !== 'undefined' && typeof console.info === 'function') {
76+
console.info(
77+
'The development server has disconnected.\nRefresh the page if necessary.'
78+
);
79+
}
80+
};
81+
82+
// Remember some state related to hot module replacement.
83+
var isFirstCompilation = true;
84+
var mostRecentCompilationHash = null;
85+
var hasCompileErrors = false;
86+
87+
function clearOutdatedErrors() {
88+
// Clean up outdated compile errors, if any.
89+
if (typeof console !== 'undefined' && typeof console.clear === 'function') {
90+
if (hasCompileErrors) {
91+
console.clear();
92+
}
93+
}
94+
}
95+
96+
// Successful compilation.
97+
function handleSuccess() {
98+
clearOutdatedErrors();
99+
100+
var isHotUpdate = !isFirstCompilation;
101+
isFirstCompilation = false;
102+
hasCompileErrors = false;
103+
104+
// Attempt to apply hot updates or reload.
105+
if (isHotUpdate) {
106+
tryApplyUpdates(function onHotUpdateSuccess() {
107+
// Only dismiss it when we're sure it's a hot update.
108+
// Otherwise it would flicker right before the reload.
109+
tryDismissErrorOverlay();
110+
});
111+
}
112+
}
113+
114+
// Compilation with warnings (e.g. ESLint).
115+
function handleWarnings(warnings) {
116+
clearOutdatedErrors();
117+
118+
var isHotUpdate = !isFirstCompilation;
119+
isFirstCompilation = false;
120+
hasCompileErrors = false;
121+
122+
function printWarnings() {
123+
// Print warnings to the console.
124+
var formatted = formatWebpackMessages({
125+
warnings: warnings,
126+
errors: [],
127+
});
128+
129+
if (typeof console !== 'undefined' && typeof console.warn === 'function') {
130+
for (var i = 0; i < formatted.warnings.length; i++) {
131+
if (i === 5) {
132+
console.warn(
133+
'There were more warnings in other files.\n' +
134+
'You can find a complete log in the terminal.'
135+
);
136+
break;
137+
}
138+
console.warn(stripAnsi(formatted.warnings[i]));
139+
}
140+
}
141+
}
142+
143+
printWarnings();
144+
145+
// Attempt to apply hot updates or reload.
146+
if (isHotUpdate) {
147+
tryApplyUpdates(function onSuccessfulHotUpdate() {
148+
// Only dismiss it when we're sure it's a hot update.
149+
// Otherwise it would flicker right before the reload.
150+
tryDismissErrorOverlay();
151+
});
152+
}
153+
}
154+
155+
// Compilation with errors (e.g. syntax error or missing modules).
156+
function handleErrors(errors) {
157+
clearOutdatedErrors();
158+
159+
isFirstCompilation = false;
160+
hasCompileErrors = true;
161+
162+
// "Massage" webpack messages.
163+
var formatted = formatWebpackMessages({
164+
errors: errors,
165+
warnings: [],
166+
});
167+
168+
// Only show the first error.
169+
ErrorOverlay.reportBuildError(formatted.errors[0]);
170+
171+
// Also log them to the console.
172+
if (typeof console !== 'undefined' && typeof console.error === 'function') {
173+
for (var i = 0; i < formatted.errors.length; i++) {
174+
console.error(stripAnsi(formatted.errors[i]));
175+
}
176+
}
177+
178+
// Do not attempt to reload now.
179+
// We will reload on next success instead.
180+
}
181+
182+
function tryDismissErrorOverlay() {
183+
if (!hasCompileErrors) {
184+
ErrorOverlay.dismissBuildError();
185+
}
186+
}
187+
188+
// There is a newer version of the code available.
189+
function handleAvailableHash(hash) {
190+
// Update last known compilation hash.
191+
mostRecentCompilationHash = hash;
192+
}
193+
194+
// Handle messages from the server.
195+
connection.onmessage = function(e) {
196+
var message = JSON.parse(e.data);
197+
switch (message.type) {
198+
case 'hash':
199+
handleAvailableHash(message.data);
200+
break;
201+
case 'still-ok':
202+
case 'ok':
203+
handleSuccess();
204+
break;
205+
case 'content-changed':
206+
// Triggered when a file from `contentBase` changed.
207+
window.location.reload();
208+
break;
209+
case 'warnings':
210+
handleWarnings(message.data);
211+
break;
212+
case 'errors':
213+
handleErrors(message.data);
214+
break;
215+
default:
216+
// Do nothing.
217+
}
218+
};
219+
220+
// Is there a newer version of this code available?
221+
function isUpdateAvailable() {
222+
/* globals __webpack_hash__ */
223+
// __webpack_hash__ is the hash of the current compilation.
224+
// It's a global variable injected by Webpack.
225+
return mostRecentCompilationHash !== __webpack_hash__;
226+
}
227+
228+
// Webpack disallows updates in other states.
229+
function canApplyUpdates() {
230+
return module.hot.status() === 'idle';
231+
}
232+
233+
// Attempt to update code on the fly, fall back to a hard reload.
234+
function tryApplyUpdates(onHotUpdateSuccess) {
235+
if (!module.hot) {
236+
// HotModuleReplacementPlugin is not in Webpack configuration.
237+
window.location.reload();
238+
return;
239+
}
240+
241+
if (!isUpdateAvailable() || !canApplyUpdates()) {
242+
return;
243+
}
244+
245+
function handleApplyUpdates(err, updatedModules) {
246+
if (err || !updatedModules || hadRuntimeError) {
247+
window.location.reload();
248+
return;
249+
}
250+
251+
if (typeof onHotUpdateSuccess === 'function') {
252+
// Maybe we want to do something.
253+
onHotUpdateSuccess();
254+
}
255+
256+
if (isUpdateAvailable()) {
257+
// While we were updating, there was a new update! Do it again.
258+
tryApplyUpdates();
259+
}
260+
}
261+
262+
// https://webpack.github.io/docs/hot-module-replacement.html#check
263+
var result = module.hot.check(/* autoApply */ true, handleApplyUpdates);
264+
265+
// // Webpack 2 returns a Promise instead of invoking a callback
266+
if (result && result.then) {
267+
result.then(
268+
function(updatedModules) {
269+
handleApplyUpdates(null, updatedModules);
270+
},
271+
function(err) {
272+
handleApplyUpdates(err, null);
273+
}
274+
);
275+
}
276+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* from: create-react-app
3+
* Copyright (c) 2015-present, Facebook, Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
'use strict';
9+
10+
const launchEditor = require('./launch-editor');
11+
const launchEditorEndpoint = require('./launch-editor-endpoint');
12+
13+
module.exports = function createLaunchEditorMiddleware() {
14+
return function launchEditorMiddleware(req, res, next) {
15+
if (req.url.startsWith(launchEditorEndpoint)) {
16+
const lineNumber = parseInt(req.query.lineNumber, 10) || 1;
17+
const colNumber = parseInt(req.query.colNumber, 10) || 1;
18+
launchEditor(req.query.fileName, lineNumber, colNumber);
19+
res.end();
20+
} else {
21+
next();
22+
}
23+
};
24+
};

0 commit comments

Comments
 (0)