UNPKG

7.76 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
19const url = require('url')
20const SockJS = require('sockjs-client')
21const stripAnsi = require('@mara/devkit/lib/stripAnsi')
22const launchEditorEndpoint = require('react-dev-utils/launchEditorEndpoint')
23const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages')
24const ErrorOverlay = require('react-error-overlay')
25
26ErrorOverlay.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
45let hadRuntimeError = false
46ErrorOverlay.startReportingRuntimeErrors({
47 onError: function() {
48 hadRuntimeError = true
49 },
50 filename: '/static/js/bundle.js'
51})
52
53if (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.
61const connection = new SockJS(
62 url.format({
63 protocol: window.location.protocol,
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.
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.
83let isFirstCompilation = true
84let mostRecentCompilationHash = null
85let 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 const isHotUpdate = !isFirstCompilation
101 isFirstCompilation = false
102 hasCompileErrors = false
103
104 // Attempt to apply hot updates or reload.
105 if (isHotUpdate) {
106 tryDismissErrorOverlay()
107 tryApplyUpdates()
108 }
109}
110
111// Compilation with warnings (e.g. ESLint).
112function handleWarnings(warnings) {
113 clearOutdatedErrors()
114
115 const isHotUpdate = !isFirstCompilation
116 isFirstCompilation = false
117 hasCompileErrors = false
118
119 function printWarnings() {
120 // Print warnings to the console.
121 const formatted = formatWebpackMessages({
122 warnings: warnings,
123 errors: []
124 })
125
126 if (typeof console !== 'undefined' && typeof console.warn === 'function') {
127 for (let i = 0; i < formatted.warnings.length; i++) {
128 if (i === 5) {
129 console.warn(
130 'There were more warnings in other files.\n' +
131 'You can find a complete log in the terminal.'
132 )
133 break
134 }
135 console.warn(stripAnsi(formatted.warnings[i]))
136 }
137 }
138 }
139
140 printWarnings()
141
142 // Attempt to apply hot updates or reload.
143 if (isHotUpdate) {
144 tryApplyUpdates(function onSuccessfulHotUpdate() {
145 // Only dismiss it when we're sure it's a hot update.
146 // Otherwise it would flicker right before the reload.
147 tryDismissErrorOverlay()
148 })
149 }
150}
151
152// Compilation with errors (e.g. syntax error or missing modules).
153function handleErrors(errors) {
154 clearOutdatedErrors()
155
156 isFirstCompilation = false
157 hasCompileErrors = true
158
159 // "Massage" webpack messages.
160 const formatted = formatWebpackMessages({
161 errors: errors,
162 warnings: []
163 })
164
165 // Only show the first error.
166 ErrorOverlay.reportBuildError(formatted.errors[0])
167
168 // Also log them to the console.
169 if (typeof console !== 'undefined' && typeof console.error === 'function') {
170 for (let i = 0; i < formatted.errors.length; i++) {
171 console.error(stripAnsi(formatted.errors[i]))
172 }
173 }
174
175 // Do not attempt to reload now.
176 // We will reload on next success instead.
177}
178
179function tryDismissErrorOverlay() {
180 if (!hasCompileErrors) {
181 ErrorOverlay.dismissBuildError()
182 }
183}
184
185// There is a newer version of the code available.
186function handleAvailableHash(hash) {
187 // Update last known compilation hash.
188 mostRecentCompilationHash = hash
189}
190
191// Handle messages from the server.
192connection.onmessage = function(e) {
193 const message = JSON.parse(e.data)
194 switch (message.type) {
195 case 'hash':
196 handleAvailableHash(message.data)
197 break
198 case 'still-ok':
199 case 'ok':
200 handleSuccess()
201 break
202 case 'content-changed':
203 // Triggered when a file from `contentBase` changed.
204 window.location.reload()
205 break
206 case 'warnings':
207 handleWarnings(message.data)
208 break
209 case 'errors':
210 handleErrors(message.data)
211 break
212 default:
213 // Do nothing.
214 }
215}
216
217// Is there a newer version of this code available?
218function isUpdateAvailable() {
219 /* globals __webpack_hash__ */
220 // __webpack_hash__ is the hash of the current compilation.
221 // It's a global variable injected by Webpack.
222 return mostRecentCompilationHash !== __webpack_hash__
223}
224
225// Webpack disallows updates in other states.
226function canApplyUpdates() {
227 const status = module.hot.status()
228 return status === 'idle'
229}
230
231// Attempt to update code on the fly, fall back to a hard reload.
232function tryApplyUpdates(onHotUpdateSuccess) {
233 if (!module.hot) {
234 // HotModuleReplacementPlugin is not in Webpack configuration.
235 window.location.reload()
236 return
237 }
238
239 if (!isUpdateAvailable() || !canApplyUpdates()) {
240 return
241 }
242
243 function handleApplyUpdates(err, updatedModules) {
244 if (err || !updatedModules || hadRuntimeError) {
245 window.location.reload()
246 return
247 }
248
249 if (typeof onHotUpdateSuccess === 'function') {
250 // Maybe we want to do something.
251 onHotUpdateSuccess()
252 }
253
254 if (isUpdateAvailable()) {
255 // While we were updating, there was a new update! Do it again.
256 tryApplyUpdates()
257 }
258 }
259
260 // https://webpack.github.io/docs/hot-module-replacement.html#check
261 const result = module.hot.check(/* autoApply */ true, handleApplyUpdates)
262
263 // // Webpack 2 returns a Promise instead of invoking a callback
264 if (result && result.then) {
265 result.then(
266 function(updatedModules) {
267 handleApplyUpdates(null, updatedModules)
268 },
269 function(err) {
270 handleApplyUpdates(err, null)
271 }
272 )
273 }
274}