1
+ 'use strict' ;
2
+
3
+ /* global __resourceQuery WorkerGlobalScope self */
4
+ /* eslint prefer-destructuring: off */
5
+
6
+ var url = require ( 'url' ) ;
7
+ var stripAnsi = require ( 'strip-ansi' ) ;
8
+ var log = require ( 'loglevel' ) . getLogger ( 'webpack-dev-server' ) ;
9
+ var socket = require ( './socket' ) ;
10
+ var overlay = require ( './overlay' ) ;
11
+
12
+ function getCurrentScriptSource ( ) {
13
+ // `document.currentScript` is the most accurate way to find the current script,
14
+ // but is not supported in all browsers.
15
+ if ( document . currentScript ) {
16
+ return document . currentScript . getAttribute ( 'src' ) ;
17
+ }
18
+ // Fall back to getting all scripts in the document.
19
+ var scriptElements = document . scripts || [ ] ;
20
+ var currentScript = scriptElements [ scriptElements . length - 1 ] ;
21
+ if ( currentScript ) {
22
+ return currentScript . getAttribute ( 'src' ) ;
23
+ }
24
+ // Fail as there was no script to use.
25
+ throw new Error ( '[WDS] Failed to get current script source.' ) ;
26
+ }
27
+
28
+ var urlParts = void 0 ;
29
+ var hotReload = true ;
30
+ if ( typeof window !== 'undefined' ) {
31
+ var qs = window . location . search . toLowerCase ( ) ;
32
+ hotReload = qs . indexOf ( 'hotreload=false' ) === - 1 ;
33
+ }
34
+ if ( typeof __resourceQuery === 'string' && __resourceQuery ) {
35
+ // If this bundle is inlined, use the resource query to get the correct url.
36
+ urlParts = url . parse ( __resourceQuery . substr ( 1 ) ) ;
37
+ } else {
38
+ // Else, get the url from the <script> this file was called with.
39
+ var scriptHost = getCurrentScriptSource ( ) ;
40
+ // eslint-disable-next-line no-useless-escape
41
+ scriptHost = scriptHost . replace ( / \/ [ ^ \/ ] + $ / , '' ) ;
42
+ urlParts = url . parse ( scriptHost || '/' , false , true ) ;
43
+ }
44
+
45
+ if ( ! urlParts . port || urlParts . port === '0' ) {
46
+ urlParts . port = self . location . port ;
47
+ }
48
+
49
+ var _hot = false ;
50
+ var initial = true ;
51
+ var currentHash = '' ;
52
+ var useWarningOverlay = false ;
53
+ var useErrorOverlay = false ;
54
+ var useProgress = false ;
55
+
56
+ var INFO = 'info' ;
57
+ var WARNING = 'warning' ;
58
+ var ERROR = 'error' ;
59
+ var NONE = 'none' ;
60
+
61
+ // Set the default log level
62
+ log . setDefaultLevel ( INFO ) ;
63
+
64
+ // Send messages to the outside, so plugins can consume it.
65
+ function sendMsg ( type , data ) {
66
+ if ( typeof self !== 'undefined' && ( typeof WorkerGlobalScope === 'undefined' || ! ( self instanceof WorkerGlobalScope ) ) ) {
67
+ self . postMessage ( {
68
+ type : 'webpack' + type ,
69
+ data : data
70
+ } , '*' ) ;
71
+ }
72
+ }
73
+
74
+ var onSocketMsg = {
75
+ hot : function hot ( ) {
76
+ _hot = true ;
77
+ log . info ( '[WDS] Hot Module Replacement enabled.' ) ;
78
+ } ,
79
+ invalid : function invalid ( ) {
80
+ log . info ( '[WDS] App updated. Recompiling...' ) ;
81
+ // fixes #1042. overlay doesn't clear if errors are fixed but warnings remain.
82
+ if ( useWarningOverlay || useErrorOverlay ) overlay . clear ( ) ;
83
+ sendMsg ( 'Invalid' ) ;
84
+ } ,
85
+ hash : function hash ( _hash ) {
86
+ currentHash = _hash ;
87
+ } ,
88
+
89
+ 'still-ok' : function stillOk ( ) {
90
+ log . info ( '[WDS] Nothing changed.' ) ;
91
+ if ( useWarningOverlay || useErrorOverlay ) overlay . clear ( ) ;
92
+ sendMsg ( 'StillOk' ) ;
93
+ } ,
94
+ 'log-level' : function logLevel ( level ) {
95
+ var hotCtx = require . context ( 'webpack/hot' , false , / ^ \. \/ l o g $ / ) ;
96
+ if ( hotCtx . keys ( ) . indexOf ( './log' ) !== - 1 ) {
97
+ hotCtx ( './log' ) . setLogLevel ( level ) ;
98
+ }
99
+ switch ( level ) {
100
+ case INFO :
101
+ case ERROR :
102
+ log . setLevel ( level ) ;
103
+ break ;
104
+ case WARNING :
105
+ // loglevel's warning name is different from webpack's
106
+ log . setLevel ( 'warn' ) ;
107
+ break ;
108
+ case NONE :
109
+ log . disableAll ( ) ;
110
+ break ;
111
+ default :
112
+ log . error ( '[WDS] Unknown clientLogLevel \'' + level + '\'' ) ;
113
+ }
114
+ } ,
115
+ overlay : function overlay ( value ) {
116
+ if ( typeof document !== 'undefined' ) {
117
+ if ( typeof value === 'boolean' ) {
118
+ useWarningOverlay = false ;
119
+ useErrorOverlay = value ;
120
+ } else if ( value ) {
121
+ useWarningOverlay = value . warnings ;
122
+ useErrorOverlay = value . errors ;
123
+ }
124
+ }
125
+ } ,
126
+ progress : function progress ( _progress ) {
127
+ if ( typeof document !== 'undefined' ) {
128
+ useProgress = _progress ;
129
+ }
130
+ } ,
131
+
132
+ 'progress-update' : function progressUpdate ( data ) {
133
+ if ( useProgress ) log . info ( '[WDS] ' + data . percent + '% - ' + data . msg + '.' ) ;
134
+ } ,
135
+ ok : function ok ( ) {
136
+ sendMsg ( 'Ok' ) ;
137
+ if ( useWarningOverlay || useErrorOverlay ) overlay . clear ( ) ;
138
+ if ( initial ) return initial = false ; // eslint-disable-line no-return-assign
139
+ reloadApp ( ) ;
140
+ } ,
141
+
142
+ 'content-changed' : function contentChanged ( ) {
143
+ log . info ( '[WDS] Content base changed. Reloading...' ) ;
144
+ self . location . reload ( ) ;
145
+ } ,
146
+ warnings : function warnings ( _warnings ) {
147
+ log . warn ( '[WDS] Warnings while compiling.' ) ;
148
+ var strippedWarnings = _warnings . map ( function ( warning ) {
149
+ return stripAnsi ( warning ) ;
150
+ } ) ;
151
+ sendMsg ( 'Warnings' , strippedWarnings ) ;
152
+ for ( var i = 0 ; i < strippedWarnings . length ; i ++ ) {
153
+ log . warn ( strippedWarnings [ i ] ) ;
154
+ }
155
+ if ( useWarningOverlay ) overlay . showMessage ( _warnings ) ;
156
+
157
+ if ( initial ) return initial = false ; // eslint-disable-line no-return-assign
158
+ reloadApp ( ) ;
159
+ } ,
160
+ errors : function errors ( _errors ) {
161
+ log . error ( '[WDS] Errors while compiling. Reload prevented.' ) ;
162
+ var strippedErrors = _errors . map ( function ( error ) {
163
+ return stripAnsi ( error ) ;
164
+ } ) ;
165
+ sendMsg ( 'Errors' , strippedErrors ) ;
166
+ for ( var i = 0 ; i < strippedErrors . length ; i ++ ) {
167
+ log . error ( strippedErrors [ i ] ) ;
168
+ }
169
+ if ( useErrorOverlay ) overlay . showMessage ( _errors ) ;
170
+ initial = false ;
171
+ } ,
172
+ error : function error ( _error ) {
173
+ log . error ( _error ) ;
174
+ } ,
175
+ close : function close ( ) {
176
+ log . error ( '[WDS] Disconnected!' ) ;
177
+ sendMsg ( 'Close' ) ;
178
+ }
179
+ } ;
180
+
181
+ var hostname = urlParts . hostname ;
182
+ var protocol = urlParts . protocol ;
183
+
184
+ // check ipv4 and ipv6 `all hostname`
185
+ if ( hostname === '0.0.0.0' || hostname === '::' ) {
186
+ // why do we need this check?
187
+ // hostname n/a for file protocol (example, when using electron, ionic)
188
+ // see: https://github.com/webpack/webpack-dev-server/pull/384
189
+ // eslint-disable-next-line no-bitwise
190
+ if ( self . location . hostname && ! ! ~ self . location . protocol . indexOf ( 'http' ) ) {
191
+ hostname = self . location . hostname ;
192
+ }
193
+ }
194
+
195
+ // `hostname` can be empty when the script path is relative. In that case, specifying
196
+ // a protocol would result in an invalid URL.
197
+ // When https is used in the app, secure websockets are always necessary
198
+ // because the browser doesn't accept non-secure websockets.
199
+ if ( hostname && ( self . location . protocol === 'https:' || urlParts . hostname === '0.0.0.0' ) ) {
200
+ protocol = self . location . protocol ;
201
+ }
202
+
203
+ var socketUrl = url . format ( {
204
+ protocol : protocol ,
205
+ auth : urlParts . auth ,
206
+ hostname : hostname ,
207
+ port : urlParts . port ,
208
+ pathname : urlParts . path == null || urlParts . path === '/' ? '/sockjs-node' : urlParts . path
209
+ } ) ;
210
+
211
+ socket ( socketUrl , onSocketMsg ) ;
212
+
213
+ var isUnloading = false ;
214
+ self . addEventListener ( 'beforeunload' , function ( ) {
215
+ isUnloading = true ;
216
+ } ) ;
217
+
218
+ function reloadApp ( ) {
219
+ if ( isUnloading || ! hotReload ) {
220
+ return ;
221
+ }
222
+ if ( _hot ) {
223
+ log . info ( '[WDS] App hot update...' ) ;
224
+ // eslint-disable-next-line global-require
225
+ var hotEmitter = require ( 'webpack/hot/emitter' ) ;
226
+ hotEmitter . emit ( 'webpackHotUpdate' , currentHash ) ;
227
+ if ( typeof self !== 'undefined' && self . window ) {
228
+ // broadcast update to window
229
+ self . postMessage ( 'webpackHotUpdate' + currentHash , '*' ) ;
230
+ }
231
+ } else {
232
+ var rootWindow = self ;
233
+ // use parent window for reload (in case we're in an iframe with no valid src)
234
+ var intervalId = self . setInterval ( function ( ) {
235
+ if ( rootWindow . location . protocol !== 'about:' ) {
236
+ // reload immediately if protocol is valid
237
+ applyReload ( rootWindow , intervalId ) ;
238
+ } else {
239
+ rootWindow = rootWindow . parent ;
240
+ if ( rootWindow . parent === rootWindow ) {
241
+ // if parent equals current window we've reached the root which would continue forever, so trigger a reload anyways
242
+ applyReload ( rootWindow , intervalId ) ;
243
+ }
244
+ }
245
+ } ) ;
246
+ }
247
+
248
+ function applyReload ( rootWindow , intervalId ) {
249
+ clearInterval ( intervalId ) ;
250
+ log . info ( '[WDS] App updated. Reloading...' ) ;
251
+ rootWindow . location . reload ( ) ;
252
+ }
253
+ }
0 commit comments