UNPKG

8.29 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2015-present, Facebook, Inc.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8'use strict';
9
10// This alternative WebpackDevServer combines the functionality of:
11// https://github.com/webpack/webpack-dev-server/blob/webpack-1/client/index.js
12// https://github.com/webpack/webpack/blob/webpack-1/hot/dev-server.js
13
14// It only supports their simplest configuration (hot updates on same server).
15// It makes some opinionated choices on top, like adding a syntax error overlay
16// that looks similar to our console output. The error overlay is inspired by:
17// https://github.com/glenjamin/webpack-hot-middleware
18
19var stripAnsi = require('strip-ansi');
20var url = require('url');
21var launchEditorEndpoint = require('./launchEditorEndpoint');
22var formatWebpackMessages = require('./formatWebpackMessages');
23var ErrorOverlay = require('react-error-overlay');
24
25ErrorOverlay.setEditorHandler(function editorHandler(errorLocation) {
26 // Keep this sync with errorOverlayMiddleware.js
27 fetch(
28 launchEditorEndpoint +
29 '?fileName=' +
30 window.encodeURIComponent(errorLocation.fileName) +
31 '&lineNumber=' +
32 window.encodeURIComponent(errorLocation.lineNumber || 1) +
33 '&colNumber=' +
34 window.encodeURIComponent(errorLocation.colNumber || 1)
35 );
36});
37
38// We need to keep track of if there has been a runtime error.
39// Essentially, we cannot guarantee application state was not corrupted by the
40// runtime error. To prevent confusing behavior, we forcibly reload the entire
41// application. This is handled below when we are notified of a compile (code
42// change).
43// See https://github.com/facebook/create-react-app/issues/3096
44var hadRuntimeError = false;
45ErrorOverlay.startReportingRuntimeErrors({
46 onError: function () {
47 hadRuntimeError = true;
48 },
49 filename: '/static/js/bundle.js',
50});
51
52if (module.hot && typeof module.hot.dispose === 'function') {
53 module.hot.dispose(function () {
54 // TODO: why do we need this?
55 ErrorOverlay.stopReportingRuntimeErrors();
56 });
57}
58
59// Connect to WebpackDevServer via a socket.
60var connection = new WebSocket(
61 url.format({
62 protocol: window.location.protocol === 'https:' ? 'wss' : 'ws',
63 hostname: process.env.WDS_SOCKET_HOST || window.location.hostname,
64 port: process.env.WDS_SOCKET_PORT || window.location.port,
65 // Hardcoded in WebpackDevServer
66 pathname: process.env.WDS_SOCKET_PATH || '/sockjs-node',
67 slashes: true,
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.
74connection.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.
83var isFirstCompilation = true;
84var mostRecentCompilationHash = null;
85var hasCompileErrors = false;
86
87function 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.
97function 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).
115function 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).
156function 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
182function tryDismissErrorOverlay() {
183 if (!hasCompileErrors) {
184 ErrorOverlay.dismissBuildError();
185 }
186}
187
188// There is a newer version of the code available.
189function handleAvailableHash(hash) {
190 // Update last known compilation hash.
191 mostRecentCompilationHash = hash;
192}
193
194// Handle messages from the server.
195connection.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?
221function 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.
229function canApplyUpdates() {
230 return module.hot.status() === 'idle';
231}
232
233// Attempt to update code on the fly, fall back to a hard reload.
234function 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 // NOTE: This var is injected by Webpack's DefinePlugin, and is a boolean instead of string.
247 const hasReactRefresh = process.env.FAST_REFRESH;
248 const wantsForcedReload = err || !updatedModules || hadRuntimeError;
249 // React refresh can handle hot-reloading over errors.
250 if (!hasReactRefresh && wantsForcedReload) {
251 window.location.reload();
252 return;
253 }
254
255 if (typeof onHotUpdateSuccess === 'function') {
256 // Maybe we want to do something.
257 onHotUpdateSuccess();
258 }
259
260 if (isUpdateAvailable()) {
261 // While we were updating, there was a new update! Do it again.
262 tryApplyUpdates();
263 }
264 }
265
266 // https://webpack.github.io/docs/hot-module-replacement.html#check
267 var result = module.hot.check(/* autoApply */ true, handleApplyUpdates);
268
269 // // webpack 2 returns a Promise instead of invoking a callback
270 if (result && result.then) {
271 result.then(
272 function (updatedModules) {
273 handleApplyUpdates(null, updatedModules);
274 },
275 function (err) {
276 handleApplyUpdates(err, null);
277 }
278 );
279 }
280}