| 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 |
|
|---|
| 19 | var stripAnsi = require('strip-ansi');
|
|---|
| 20 | var url = require('url');
|
|---|
| 21 | var launchEditorEndpoint = require('./launchEditorEndpoint');
|
|---|
| 22 | var formatWebpackMessages = require('./formatWebpackMessages');
|
|---|
| 23 | var ErrorOverlay = require('react-error-overlay');
|
|---|
| 24 |
|
|---|
| 25 | ErrorOverlay.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
|
|---|
| 44 | var hadRuntimeError = false;
|
|---|
| 45 | ErrorOverlay.startReportingRuntimeErrors({
|
|---|
| 46 | onError: function () {
|
|---|
| 47 | hadRuntimeError = true;
|
|---|
| 48 | },
|
|---|
| 49 | filename: '/static/js/bundle.js',
|
|---|
| 50 | });
|
|---|
| 51 |
|
|---|
| 52 | if (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.
|
|---|
| 60 | var 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 || '/ws',
|
|---|
| 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.
|
|---|
| 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 | function canAcceptErrors() {
|
|---|
| 234 | // NOTE: This var is injected by Webpack's DefinePlugin, and is a boolean instead of string.
|
|---|
| 235 | const hasReactRefresh = process.env.FAST_REFRESH;
|
|---|
| 236 |
|
|---|
| 237 | const status = module.hot.status();
|
|---|
| 238 | // React refresh can handle hot-reloading over errors.
|
|---|
| 239 | // However, when hot-reload status is abort or fail,
|
|---|
| 240 | // it indicates the current update cannot be applied safely,
|
|---|
| 241 | // and thus we should bail out to a forced reload for consistency.
|
|---|
| 242 | return hasReactRefresh && ['abort', 'fail'].indexOf(status) === -1;
|
|---|
| 243 | }
|
|---|
| 244 |
|
|---|
| 245 | // Attempt to update code on the fly, fall back to a hard reload.
|
|---|
| 246 | function tryApplyUpdates(onHotUpdateSuccess) {
|
|---|
| 247 | if (!module.hot) {
|
|---|
| 248 | // HotModuleReplacementPlugin is not in webpack configuration.
|
|---|
| 249 | window.location.reload();
|
|---|
| 250 | return;
|
|---|
| 251 | }
|
|---|
| 252 |
|
|---|
| 253 | if (!isUpdateAvailable() || !canApplyUpdates()) {
|
|---|
| 254 | return;
|
|---|
| 255 | }
|
|---|
| 256 |
|
|---|
| 257 | function handleApplyUpdates(err, updatedModules) {
|
|---|
| 258 | const haveErrors = err || hadRuntimeError;
|
|---|
| 259 | // When there is no error but updatedModules is unavailable,
|
|---|
| 260 | // it indicates a critical failure in hot-reloading,
|
|---|
| 261 | // e.g. server is not ready to serve new bundle,
|
|---|
| 262 | // and hence we need to do a forced reload.
|
|---|
| 263 | const needsForcedReload = !err && !updatedModules;
|
|---|
| 264 | if ((haveErrors && !canAcceptErrors()) || needsForcedReload) {
|
|---|
| 265 | window.location.reload();
|
|---|
| 266 | return;
|
|---|
| 267 | }
|
|---|
| 268 |
|
|---|
| 269 | if (typeof onHotUpdateSuccess === 'function') {
|
|---|
| 270 | // Maybe we want to do something.
|
|---|
| 271 | onHotUpdateSuccess();
|
|---|
| 272 | }
|
|---|
| 273 |
|
|---|
| 274 | if (isUpdateAvailable()) {
|
|---|
| 275 | // While we were updating, there was a new update! Do it again.
|
|---|
| 276 | tryApplyUpdates();
|
|---|
| 277 | }
|
|---|
| 278 | }
|
|---|
| 279 |
|
|---|
| 280 | // https://webpack.github.io/docs/hot-module-replacement.html#check
|
|---|
| 281 | var result = module.hot.check(/* autoApply */ true, handleApplyUpdates);
|
|---|
| 282 |
|
|---|
| 283 | // // webpack 2 returns a Promise instead of invoking a callback
|
|---|
| 284 | if (result && result.then) {
|
|---|
| 285 | result.then(
|
|---|
| 286 | function (updatedModules) {
|
|---|
| 287 | handleApplyUpdates(null, updatedModules);
|
|---|
| 288 | },
|
|---|
| 289 | function (err) {
|
|---|
| 290 | handleApplyUpdates(err, null);
|
|---|
| 291 | }
|
|---|
| 292 | );
|
|---|
| 293 | }
|
|---|
| 294 | }
|
|---|