1
+ "use strict" ;
2
+
3
+ /* global document */
4
+ /*
5
+ eslint-disable
6
+ no-console,
7
+ func-names
8
+ */
9
+
10
+ var normalizeUrl = require ( "./normalize-url" ) ;
11
+ var srcByModuleId = Object . create ( null ) ;
12
+ var noDocument = typeof document === "undefined" ;
13
+ var forEach = Array . prototype . forEach ;
14
+
15
+ // eslint-disable-next-line jsdoc/no-restricted-syntax
16
+ /**
17
+ * @param {Function } fn any function
18
+ * @param {number } time time
19
+ * @returns {() => void } wrapped function
20
+ */
21
+ function debounce ( fn , time ) {
22
+ var timeout = 0 ;
23
+ return function ( ) {
24
+ // @ts -expect-error
25
+ var self = this ;
26
+ // eslint-disable-next-line prefer-rest-params
27
+ var args = arguments ;
28
+ // eslint-disable-next-line func-style
29
+ var functionCall = function functionCall ( ) {
30
+ return fn . apply ( self , args ) ;
31
+ } ;
32
+ clearTimeout ( timeout ) ;
33
+
34
+ // @ts -expect-error
35
+ timeout = setTimeout ( functionCall , time ) ;
36
+ } ;
37
+ }
38
+
39
+ /**
40
+ * @returns {void }
41
+ */
42
+ function noop ( ) { }
43
+
44
+ /** @typedef {(filename?: string) => string[] } GetScriptSrc */
45
+
46
+ /**
47
+ * @param {string | number } moduleId a module id
48
+ * @returns {GetScriptSrc } current script url
49
+ */
50
+ function getCurrentScriptUrl ( moduleId ) {
51
+ var src = srcByModuleId [ moduleId ] ;
52
+ if ( ! src ) {
53
+ if ( document . currentScript ) {
54
+ src = ( /** @type {HTMLScriptElement } */ document . currentScript ) . src ;
55
+ } else {
56
+ var scripts = document . getElementsByTagName ( "script" ) ;
57
+ var lastScriptTag = scripts [ scripts . length - 1 ] ;
58
+ if ( lastScriptTag ) {
59
+ src = lastScriptTag . src ;
60
+ }
61
+ }
62
+ srcByModuleId [ moduleId ] = src ;
63
+ }
64
+
65
+ /** @type {GetScriptSrc } */
66
+ return function ( fileMap ) {
67
+ if ( ! src ) {
68
+ return [ ] ;
69
+ }
70
+ var splitResult = src . split ( / ( [ ^ \\ / ] + ) \. j s $ / ) ;
71
+ var filename = splitResult && splitResult [ 1 ] ;
72
+ if ( ! filename ) {
73
+ return [ src . replace ( ".js" , ".css" ) ] ;
74
+ }
75
+ if ( ! fileMap ) {
76
+ return [ src . replace ( ".js" , ".css" ) ] ;
77
+ }
78
+ return fileMap . split ( "," ) . map ( function ( mapRule ) {
79
+ var reg = new RegExp ( "" . concat ( filename , "\\.js$" ) , "g" ) ;
80
+ return normalizeUrl ( src . replace ( reg , "" . concat ( mapRule . replace ( / { f i l e N a m e } / g, filename ) , ".css" ) ) ) ;
81
+ } ) ;
82
+ } ;
83
+ }
84
+
85
+ /**
86
+ * @param {string } url URL
87
+ * @returns {boolean } true when URL can be request, otherwise false
88
+ */
89
+ function isUrlRequest ( url ) {
90
+ // An URL is not an request if
91
+
92
+ // It is not http or https
93
+ if ( ! / ^ [ a - z A - Z ] [ a - z A - Z \d + \- . ] * : / . test ( url ) ) {
94
+ return false ;
95
+ }
96
+ return true ;
97
+ }
98
+
99
+ /** @typedef {HTMLLinkElement & { isLoaded: boolean, visited: boolean } } HotHTMLLinkElement */
100
+
101
+ /**
102
+ * @param {HotHTMLLinkElement } el html link element
103
+ * @param {string= } url a URL
104
+ */
105
+ function updateCss ( el , url ) {
106
+ if ( ! url ) {
107
+ if ( ! el . href ) {
108
+ return ;
109
+ }
110
+
111
+ // eslint-disable-next-line
112
+ url = el . href . split ( "?" ) [ 0 ] ;
113
+ }
114
+ if ( ! isUrlRequest ( /** @type {string } */ url ) ) {
115
+ return ;
116
+ }
117
+ if ( el . isLoaded === false ) {
118
+ // We seem to be about to replace a css link that hasn't loaded yet.
119
+ // We're probably changing the same file more than once.
120
+ return ;
121
+ }
122
+
123
+ // eslint-disable-next-line unicorn/prefer-includes
124
+ if ( ! url || ! ( url . indexOf ( ".css" ) > - 1 ) ) {
125
+ return ;
126
+ }
127
+ el . visited = true ;
128
+ var newEl = /** @type {HotHTMLLinkElement } */
129
+ el . cloneNode ( ) ;
130
+ newEl . isLoaded = false ;
131
+ newEl . addEventListener ( "load" , function ( ) {
132
+ if ( newEl . isLoaded ) {
133
+ return ;
134
+ }
135
+ newEl . isLoaded = true ;
136
+ if ( el . parentNode ) {
137
+ el . parentNode . removeChild ( el ) ;
138
+ }
139
+ } ) ;
140
+ newEl . addEventListener ( "error" , function ( ) {
141
+ if ( newEl . isLoaded ) {
142
+ return ;
143
+ }
144
+ newEl . isLoaded = true ;
145
+ if ( el . parentNode ) {
146
+ el . parentNode . removeChild ( el ) ;
147
+ }
148
+ } ) ;
149
+ newEl . href = "" . concat ( url , "?" ) . concat ( Date . now ( ) ) ;
150
+ if ( el . parentNode ) {
151
+ if ( el . nextSibling ) {
152
+ el . parentNode . insertBefore ( newEl , el . nextSibling ) ;
153
+ } else {
154
+ el . parentNode . appendChild ( newEl ) ;
155
+ }
156
+ }
157
+ }
158
+
159
+ /**
160
+ * @param {string } href href
161
+ * @param {string[] } src src
162
+ * @returns {undefined | string } a reload url
163
+ */
164
+ function getReloadUrl ( href , src ) {
165
+ var ret ;
166
+ href = normalizeUrl ( href ) ;
167
+ src . some (
168
+ /**
169
+ * @param {string } url url
170
+ */
171
+ // eslint-disable-next-line array-callback-return
172
+ function ( url ) {
173
+ // @ts -expect-error fix me in the next major release
174
+ // eslint-disable-next-line unicorn/prefer-includes
175
+ if ( href . indexOf ( src ) > - 1 ) {
176
+ ret = url ;
177
+ }
178
+ } ) ;
179
+ return ret ;
180
+ }
181
+
182
+ /**
183
+ * @param {string[] } src source
184
+ * @returns {boolean } true when loaded, otherwise false
185
+ */
186
+ function reloadStyle ( src ) {
187
+ var elements = document . querySelectorAll ( "link" ) ;
188
+ var loaded = false ;
189
+ forEach . call ( elements , function ( el ) {
190
+ if ( ! el . href ) {
191
+ return ;
192
+ }
193
+ var url = getReloadUrl ( el . href , src ) ;
194
+ if ( url && ! isUrlRequest ( url ) ) {
195
+ return ;
196
+ }
197
+ if ( el . visited === true ) {
198
+ return ;
199
+ }
200
+ if ( url ) {
201
+ updateCss ( el , url ) ;
202
+ loaded = true ;
203
+ }
204
+ } ) ;
205
+ return loaded ;
206
+ }
207
+
208
+ /**
209
+ * @returns {void }
210
+ */
211
+ function reloadAll ( ) {
212
+ var elements = document . querySelectorAll ( "link" ) ;
213
+ forEach . call ( elements , function ( el ) {
214
+ if ( el . visited === true ) {
215
+ return ;
216
+ }
217
+ updateCss ( el ) ;
218
+ } ) ;
219
+ }
220
+
221
+ /**
222
+ * @param {number | string } moduleId a module id
223
+ * @param {{ filename?: string, locals?: boolean } } options options
224
+ * @returns {() => void } wrapper function
225
+ */
226
+ module . exports = function ( moduleId , options ) {
227
+ if ( noDocument ) {
228
+ console . log ( "no window.document found, will not HMR CSS" ) ;
229
+ return noop ;
230
+ }
231
+ var getScriptSrc = getCurrentScriptUrl ( moduleId ) ;
232
+
233
+ /**
234
+ * @returns {void }
235
+ */
236
+ function update ( ) {
237
+ var src = getScriptSrc ( options . filename ) ;
238
+ var reloaded = reloadStyle ( src ) ;
239
+ if ( options . locals ) {
240
+ console . log ( "[HMR] Detected local css modules. Reload all css" ) ;
241
+ reloadAll ( ) ;
242
+ return ;
243
+ }
244
+ if ( reloaded ) {
245
+ console . log ( "[HMR] css reload %s" , src . join ( " " ) ) ;
246
+ } else {
247
+ console . log ( "[HMR] Reload all css" ) ;
248
+ reloadAll ( ) ;
249
+ }
250
+ }
251
+ return debounce ( update , 50 ) ;
252
+ } ;
0 commit comments