webpackHotDevClient.js 7.75 KB
Newer Older
1
2
3
/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
Sophie Alpert's avatar
Sophie Alpert committed
4
5
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
6
7
 */

8
9
'use strict';

10
11
12
13
14
15
16
17
18
19
20
21
// This alternative WebpackDevServer combines the functionality of:
// https://github.com/webpack/webpack-dev-server/blob/webpack-1/client/index.js
// https://github.com/webpack/webpack/blob/webpack-1/hot/dev-server.js

// It only supports their simplest configuration (hot updates on same server).
// It makes some opinionated choices on top, like adding a syntax error overlay
// that looks similar to our console output. The error overlay is inspired by:
// https://github.com/glenjamin/webpack-hot-middleware

var SockJS = require('sockjs-client');
var stripAnsi = require('strip-ansi');
var url = require('url');
22
var launchEditorEndpoint = require('./launchEditorEndpoint');
23
var formatWebpackMessages = require('./formatWebpackMessages');
24
25
var ErrorOverlay = require('react-error-overlay');

26
27
28
ErrorOverlay.setEditorHandler(function editorHandler(errorLocation) {
  // Keep this sync with errorOverlayMiddleware.js
  fetch(
29
30
    launchEditorEndpoint +
      '?fileName=' +
31
32
33
34
35
36
      window.encodeURIComponent(errorLocation.fileName) +
      '&lineNumber=' +
      window.encodeURIComponent(errorLocation.lineNumber || 1)
  );
});

37
38
39
40
41
42
43
// We need to keep track of if there has been a runtime error.
// Essentially, we cannot guarantee application state was not corrupted by the
// runtime error. To prevent confusing behavior, we forcibly reload the entire
// application. This is handled below when we are notified of a compile (code
// change).
// See https://github.com/facebookincubator/create-react-app/issues/3096
var hadRuntimeError = false;
44
45
ErrorOverlay.startReportingRuntimeErrors({
  onError: function() {
46
    hadRuntimeError = true;
47
  },
Dan Abramov's avatar
Dan Abramov committed
48
  filename: '/static/js/bundle.js',
49
});
50

51
52
53
54
if (module.hot && typeof module.hot.dispose === 'function') {
  module.hot.dispose(function() {
    // TODO: why do we need this?
    ErrorOverlay.stopReportingRuntimeErrors();
55
  });
56
57
58
}

// Connect to WebpackDevServer via a socket.
59
60
61
62
63
64
65
66
67
var connection = new SockJS(
  url.format({
    protocol: window.location.protocol,
    hostname: window.location.hostname,
    port: window.location.port,
    // Hardcoded in WebpackDevServer
    pathname: '/sockjs-node',
  })
);
68
69
70
71
72

// Unlike WebpackDevServer client, we won't try to reconnect
// to avoid spamming the console. Disconnect usually happens
// when developer stops the server.
connection.onclose = function() {
73
  if (typeof console !== 'undefined' && typeof console.info === 'function') {
74
75
76
77
    console.info(
      'The development server has disconnected.\nRefresh the page if necessary.'
    );
  }
78
};
79
80
81
82

// Remember some state related to hot module replacement.
var isFirstCompilation = true;
var mostRecentCompilationHash = null;
83
84
85
86
var hasCompileErrors = false;

function clearOutdatedErrors() {
  // Clean up outdated compile errors, if any.
87
88
  if (typeof console !== 'undefined' && typeof console.clear === 'function') {
    if (hasCompileErrors) {
89
90
      console.clear();
    }
91
92
  }
}
93
94
95

// Successful compilation.
function handleSuccess() {
96
97
  clearOutdatedErrors();

98
99
  var isHotUpdate = !isFirstCompilation;
  isFirstCompilation = false;
100
  hasCompileErrors = false;
101
102
103

  // Attempt to apply hot updates or reload.
  if (isHotUpdate) {
104
    tryApplyUpdates(function onHotUpdateSuccess() {
105
      // Only dismiss it when we're sure it's a hot update.
106
      // Otherwise it would flicker right before the reload.
107
      ErrorOverlay.dismissBuildError();
108
    });
109
110
111
112
113
  }
}

// Compilation with warnings (e.g. ESLint).
function handleWarnings(warnings) {
114
115
  clearOutdatedErrors();

116
117
  var isHotUpdate = !isFirstCompilation;
  isFirstCompilation = false;
118
  hasCompileErrors = false;
119
120
121

  function printWarnings() {
    // Print warnings to the console.
122
123
124
125
126
    var formatted = formatWebpackMessages({
      warnings: warnings,
      errors: [],
    });

127
    if (typeof console !== 'undefined' && typeof console.warn === 'function') {
128
      for (var i = 0; i < formatted.warnings.length; i++) {
129
130
131
132
133
134
135
        if (i === 5) {
          console.warn(
            'There were more warnings in other files.\n' +
              'You can find a complete log in the terminal.'
          );
          break;
        }
136
137
        console.warn(stripAnsi(formatted.warnings[i]));
      }
138
139
140
141
142
143
144
145
146
    }
  }

  // Attempt to apply hot updates or reload.
  if (isHotUpdate) {
    tryApplyUpdates(function onSuccessfulHotUpdate() {
      // Only print warnings if we aren't refreshing the page.
      // Otherwise they'll disappear right away anyway.
      printWarnings();
147
      // Only dismiss it when we're sure it's a hot update.
148
      // Otherwise it would flicker right before the reload.
149
      ErrorOverlay.dismissBuildError();
150
151
152
153
154
155
156
157
158
    });
  } else {
    // Print initial warnings immediately.
    printWarnings();
  }
}

// Compilation with errors (e.g. syntax error or missing modules).
function handleErrors(errors) {
159
160
  clearOutdatedErrors();

161
  isFirstCompilation = false;
162
  hasCompileErrors = true;
163
164
165
166

  // "Massage" webpack messages.
  var formatted = formatWebpackMessages({
    errors: errors,
167
    warnings: [],
168
169
170
  });

  // Only show the first error.
171
  ErrorOverlay.reportBuildError(formatted.errors[0]);
172
173

  // Also log them to the console.
174
  if (typeof console !== 'undefined' && typeof console.error === 'function') {
175
176
177
    for (var i = 0; i < formatted.errors.length; i++) {
      console.error(stripAnsi(formatted.errors[i]));
    }
178
179
  }

180
181
182
183
184
185
186
187
188
189
190
191
192
193
  // Do not attempt to reload now.
  // We will reload on next success instead.
}

// There is a newer version of the code available.
function handleAvailableHash(hash) {
  // Update last known compilation hash.
  mostRecentCompilationHash = hash;
}

// Handle messages from the server.
connection.onmessage = function(e) {
  var message = JSON.parse(e.data);
  switch (message.type) {
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
    case 'hash':
      handleAvailableHash(message.data);
      break;
    case 'still-ok':
    case 'ok':
      handleSuccess();
      break;
    case 'content-changed':
      // Triggered when a file from `contentBase` changed.
      window.location.reload();
      break;
    case 'warnings':
      handleWarnings(message.data);
      break;
    case 'errors':
      handleErrors(message.data);
      break;
    default:
212
213
    // Do nothing.
  }
214
};
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240

// Is there a newer version of this code available?
function isUpdateAvailable() {
  /* globals __webpack_hash__ */
  // __webpack_hash__ is the hash of the current compilation.
  // It's a global variable injected by Webpack.
  return mostRecentCompilationHash !== __webpack_hash__;
}

// Webpack disallows updates in other states.
function canApplyUpdates() {
  return module.hot.status() === 'idle';
}

// Attempt to update code on the fly, fall back to a hard reload.
function tryApplyUpdates(onHotUpdateSuccess) {
  if (!module.hot) {
    // HotModuleReplacementPlugin is not in Webpack configuration.
    window.location.reload();
    return;
  }

  if (!isUpdateAvailable() || !canApplyUpdates()) {
    return;
  }

241
  function handleApplyUpdates(err, updatedModules) {
242
    if (err || !updatedModules || hadRuntimeError) {
243
244
245
246
247
248
249
250
251
252
253
254
255
      window.location.reload();
      return;
    }

    if (typeof onHotUpdateSuccess === 'function') {
      // Maybe we want to do something.
      onHotUpdateSuccess();
    }

    if (isUpdateAvailable()) {
      // While we were updating, there was a new update! Do it again.
      tryApplyUpdates();
    }
256
257
258
  }

  // https://webpack.github.io/docs/hot-module-replacement.html#check
259
  var result = module.hot.check(/* autoApply */ true, handleApplyUpdates);
260
261
262
263
264
265
266
267
268
269
270
271

  // // Webpack 2 returns a Promise instead of invoking a callback
  if (result && result.then) {
    result.then(
      function(updatedModules) {
        handleApplyUpdates(null, updatedModules);
      },
      function(err) {
        handleApplyUpdates(err, null);
      }
    );
  }
272
}