@@ -50,7 +50,7 @@ class Server {
50
50
this . logger = this . compiler . getInfrastructureLogger ( this . name ) ;
51
51
52
52
normalizeOptions ( this . compiler , this . options ) ;
53
- this . applyDevServerPlugin ( ) ;
53
+ this . configureChildCompilers ( ) ;
54
54
55
55
this . SocketServerImplementation = getSocketServerImplementation (
56
56
this . options
@@ -84,14 +84,218 @@ class Server {
84
84
} , this ) ;
85
85
}
86
86
87
- applyDevServerPlugin ( ) {
88
- const DevServerPlugin = require ( './utils/DevServerPlugin' ) ;
87
+ configureChildCompilers ( ) {
88
+ const {
89
+ EntryPlugin,
90
+ ProvidePlugin,
91
+ HotModuleReplacementPlugin,
92
+ } = require ( 'webpack' ) ;
93
+ const createDomain = require ( './utils/createDomain' ) ;
94
+ const getSocketClientPath = require ( './utils/getSocketClientPath' ) ;
89
95
90
96
const compilers = this . compiler . compilers || [ this . compiler ] ;
97
+ const options = this . options ;
91
98
92
- // eslint-disable-next-line no-shadow
93
99
compilers . forEach ( ( compiler ) => {
94
- new DevServerPlugin ( this . options ) . apply ( compiler ) ;
100
+ /** @type {string } */
101
+ const domain = createDomain ( options ) ;
102
+
103
+ // SockJS is not supported server mode, so `host` and `port` can't specified, let's ignore them
104
+ // TODO show warning about this
105
+ const isSockJSType = options . webSocketServer . type === 'sockjs' ;
106
+
107
+ /** @type {string } */
108
+ let hostString = '' ;
109
+
110
+ if ( options . client && options . client . host ) {
111
+ hostString = `&host=${ options . client . host } ` ;
112
+ } else if ( options . webSocketServer . options . host && ! isSockJSType ) {
113
+ hostString = `&host=${ options . webSocketServer . options . host } ` ;
114
+ }
115
+
116
+ /** @type {string } */
117
+ let portString = '' ;
118
+
119
+ if ( options . client && options . client . port ) {
120
+ portString = `&port=${ options . client . port } ` ;
121
+ } else if ( options . webSocketServer . options . port && ! isSockJSType ) {
122
+ portString = `&port=${ options . webSocketServer . options . port } ` ;
123
+ } else {
124
+ portString = `&port=${ options . port } ` ;
125
+ }
126
+
127
+ /** @type {string } */
128
+ let pathString = '' ;
129
+
130
+ // Only add the path if it is not default
131
+ if ( options . client && options . client . path && options . client . path ) {
132
+ pathString = `&path=${ options . client . path } ` ;
133
+ } else if ( options . webSocketServer . options . path ) {
134
+ pathString = `&path=${ options . webSocketServer . options . path } ` ;
135
+ }
136
+
137
+ /** @type {string } */
138
+ const logging =
139
+ options . client && options . client . logging
140
+ ? `&logging=${ options . client . logging } `
141
+ : '' ;
142
+
143
+ /** @type {string } */
144
+ const clientEntry = `${ require . resolve (
145
+ '../../client/index.js'
146
+ ) } ?${ domain } ${ hostString } ${ pathString } ${ portString } ${ logging } `;
147
+
148
+ /** @type {(string[] | string) } */
149
+ let hotEntry ;
150
+
151
+ if ( options . hot === 'only' ) {
152
+ hotEntry = require . resolve ( 'webpack/hot/only-dev-server' ) ;
153
+ } else if ( options . hot ) {
154
+ hotEntry = require . resolve ( 'webpack/hot/dev-server' ) ;
155
+ }
156
+ /**
157
+ * prependEntry Method for webpack 4
158
+ * @param {Entry } originalEntry
159
+ * @param {Entry } additionalEntries
160
+ * @returns {Entry }
161
+ */
162
+ const prependEntry = ( originalEntry , additionalEntries ) => {
163
+ if ( typeof originalEntry === 'function' ) {
164
+ return ( ) =>
165
+ Promise . resolve ( originalEntry ( ) ) . then ( ( entry ) =>
166
+ prependEntry ( entry , additionalEntries )
167
+ ) ;
168
+ }
169
+
170
+ if (
171
+ typeof originalEntry === 'object' &&
172
+ ! Array . isArray ( originalEntry )
173
+ ) {
174
+ /** @type {Object<string,string> } */
175
+ const clone = { } ;
176
+
177
+ Object . keys ( originalEntry ) . forEach ( ( key ) => {
178
+ // entry[key] should be a string here
179
+ const entryDescription = originalEntry [ key ] ;
180
+ clone [ key ] = prependEntry ( entryDescription , additionalEntries ) ;
181
+ } ) ;
182
+
183
+ return clone ;
184
+ }
185
+
186
+ // in this case, entry is a string or an array.
187
+ // make sure that we do not add duplicates.
188
+ /** @type {Entry } */
189
+ const entriesClone = additionalEntries . slice ( 0 ) ;
190
+ [ ] . concat ( originalEntry ) . forEach ( ( newEntry ) => {
191
+ if ( ! entriesClone . includes ( newEntry ) ) {
192
+ entriesClone . push ( newEntry ) ;
193
+ }
194
+ } ) ;
195
+ return entriesClone ;
196
+ } ;
197
+
198
+ /**
199
+ *
200
+ * Description of the option for checkInject method
201
+ * @typedef {Function } checkInjectOptionsParam
202
+ * @param {Object } _config - compilerConfig
203
+ * @return {Boolean }
204
+ */
205
+
206
+ /**
207
+ *
208
+ * @param {Boolean | checkInjectOptionsParam } option - inject(Hot|Client) it is Boolean | fn => Boolean
209
+ * @param {Object } _config
210
+ * @param {Boolean } defaultValue
211
+ * @return {Boolean }
212
+ */
213
+ // eslint-disable-next-line no-shadow
214
+ const checkInject = ( option , _config , defaultValue ) => {
215
+ if ( typeof option === 'boolean' ) {
216
+ return option ;
217
+ }
218
+
219
+ if ( typeof option === 'function' ) {
220
+ return option ( _config ) ;
221
+ }
222
+
223
+ return defaultValue ;
224
+ } ;
225
+
226
+ const compilerOptions = compiler . options ;
227
+
228
+ compilerOptions . plugins = compilerOptions . plugins || [ ] ;
229
+
230
+ /** @type {boolean } */
231
+ const isWebTarget = compilerOptions . externalsPresets
232
+ ? compilerOptions . externalsPresets . web
233
+ : [
234
+ 'web' ,
235
+ 'webworker' ,
236
+ 'electron-renderer' ,
237
+ 'node-webkit' ,
238
+ // eslint-disable-next-line no-undefined
239
+ undefined ,
240
+ null ,
241
+ ] . includes ( compilerOptions . target ) ;
242
+
243
+ /** @type {Entry } */
244
+ const additionalEntries = checkInject (
245
+ options . client ? options . client . needClientEntry : null ,
246
+ compilerOptions ,
247
+ isWebTarget
248
+ )
249
+ ? [ clientEntry ]
250
+ : [ ] ;
251
+
252
+ if (
253
+ hotEntry &&
254
+ checkInject (
255
+ options . client ? options . client . hotEntry : null ,
256
+ compilerOptions ,
257
+ true
258
+ )
259
+ ) {
260
+ additionalEntries . push ( hotEntry ) ;
261
+ }
262
+
263
+ // use a hook to add entries if available
264
+ if ( EntryPlugin ) {
265
+ for ( const additionalEntry of additionalEntries ) {
266
+ new EntryPlugin ( compiler . context , additionalEntry , {
267
+ // eslint-disable-next-line no-undefined
268
+ name : undefined ,
269
+ } ) . apply ( compiler ) ;
270
+ }
271
+ } else {
272
+ compilerOptions . entry = prependEntry (
273
+ compilerOptions . entry || './src' ,
274
+ additionalEntries
275
+ ) ;
276
+ compiler . hooks . entryOption . call (
277
+ compilerOptions . context ,
278
+ compilerOptions . entry
279
+ ) ;
280
+ }
281
+
282
+ const providePlugin = new ProvidePlugin ( {
283
+ __webpack_dev_server_client__ : getSocketClientPath ( options ) ,
284
+ } ) ;
285
+
286
+ providePlugin . apply ( compiler ) ;
287
+
288
+ if (
289
+ hotEntry &&
290
+ ! compilerOptions . plugins . find (
291
+ ( p ) => p . constructor === HotModuleReplacementPlugin
292
+ )
293
+ ) {
294
+ // apply the HMR plugin, if it didn't exist before.
295
+ const plugin = new HotModuleReplacementPlugin ( ) ;
296
+
297
+ plugin . apply ( compiler ) ;
298
+ }
95
299
} ) ;
96
300
}
97
301
0 commit comments