@@ -35,15 +35,25 @@ interface DevServerOptions {
35
35
EnvParam : any ;
36
36
}
37
37
38
- // We support these three kinds of webpack.config.js export. We don't currently support exported promises
39
- // (though we might be able to add that in the future, if there's a need).
38
+ // Interface as defined in es6-promise
39
+ interface Thenable < T > {
40
+ then < U > ( onFulfilled ?: ( value : T ) => U | Thenable < U > , onRejected ?: ( error : any ) => U | Thenable < U > ) : Thenable < U > ;
41
+ then < U > ( onFulfilled ?: ( value : T ) => U | Thenable < U > , onRejected ?: ( error : any ) => void ) : Thenable < U > ;
42
+ }
43
+
44
+ // We support these four kinds of webpack.config.js export
40
45
type WebpackConfigOrArray = webpack . Configuration | webpack . Configuration [ ] ;
46
+ type WebpackConfigOrArrayOrThenable = WebpackConfigOrArray | Thenable < WebpackConfigOrArray > ;
41
47
interface WebpackConfigFunc {
42
- ( env ?: any ) : WebpackConfigOrArray ;
48
+ ( env ?: any ) : WebpackConfigOrArrayOrThenable ;
43
49
}
44
- type WebpackConfigExport = WebpackConfigOrArray | WebpackConfigFunc ;
50
+ type WebpackConfigExport = WebpackConfigOrArrayOrThenable | WebpackConfigFunc ;
45
51
type WebpackConfigModuleExports = WebpackConfigExport | EsModuleExports < WebpackConfigExport > ;
46
52
53
+ function isThenable ( obj : any ) {
54
+ return obj && typeof ( < Thenable < any > > obj ) . then === 'function' ;
55
+ }
56
+
47
57
function attachWebpackDevMiddleware ( app : any , webpackConfig : webpack . Configuration , enableHotModuleReplacement : boolean , enableReactHotModuleReplacement : boolean , hmrClientOptions : StringMap < string > , hmrServerEndpoint : string ) {
48
58
// Build the final Webpack config based on supplied options
49
59
if ( enableHotModuleReplacement ) {
@@ -251,76 +261,85 @@ export function createWebpackDevServer(callback: CreateDevServerCallback, option
251
261
// your Startup.cs.
252
262
webpackConfigExport = webpackConfigExport ( options . suppliedOptions . EnvParam ) ;
253
263
}
254
- const webpackConfigArray = webpackConfigExport instanceof Array ? webpackConfigExport : [ webpackConfigExport ] ;
255
264
256
- const enableHotModuleReplacement = options . suppliedOptions . HotModuleReplacement ;
257
- const enableReactHotModuleReplacement = options . suppliedOptions . ReactHotModuleReplacement ;
258
- if ( enableReactHotModuleReplacement && ! enableHotModuleReplacement ) {
259
- callback ( 'To use ReactHotModuleReplacement, you must also enable the HotModuleReplacement option.' , null ) ;
260
- return ;
261
- }
265
+ const webpackConfigThenable = isThenable ( webpackConfigExport )
266
+ ? webpackConfigExport
267
+ : { then : callback => callback ( webpackConfigExport ) } ;
262
268
263
- // The default value, 0, means 'choose randomly'
264
- const suggestedHMRPortOrZero = options . suppliedOptions . HotModuleReplacementServerPort || 0 ;
269
+ webpackConfigThenable . then ( webpackConfigResolved => {
270
+ const webpackConfigArray = webpackConfigResolved instanceof Array ? webpackConfigResolved : [ webpackConfigResolved ] ;
265
271
266
- const app = connect ( ) ;
267
- const listener = app . listen ( suggestedHMRPortOrZero , ( ) => {
268
- try {
269
- // For each webpack config that specifies a public path, add webpack dev middleware for it
270
- const normalizedPublicPaths : string [ ] = [ ] ;
271
- webpackConfigArray . forEach ( webpackConfig => {
272
- if ( webpackConfig . target === 'node' ) {
273
- // For configs that target Node, it's meaningless to set up an HTTP listener, since
274
- // Node isn't going to load those modules over HTTP anyway. It just loads them directly
275
- // from disk. So the most relevant thing we can do with such configs is just write
276
- // updated builds to disk, just like "webpack --watch".
277
- beginWebpackWatcher ( webpackConfig ) ;
278
- } else {
279
- // For configs that target browsers, we can set up an HTTP listener, and dynamically
280
- // modify the config to enable HMR etc. This just requires that we have a publicPath.
281
- const publicPath = ( webpackConfig . output . publicPath || '' ) . trim ( ) ;
282
- if ( ! publicPath ) {
283
- throw new Error ( 'To use the Webpack dev server, you must specify a value for \'publicPath\' on the \'output\' section of your webpack config (for any configuration that targets browsers)' ) ;
284
- }
285
- const publicPathNoTrailingSlash = removeTrailingSlash ( publicPath ) ;
286
- normalizedPublicPaths . push ( publicPathNoTrailingSlash ) ;
287
-
288
- // This is the URL the client will connect to, except that since it's a relative URL
289
- // (no leading slash), Webpack will resolve it against the runtime <base href> URL
290
- // plus it also adds the publicPath
291
- const hmrClientEndpoint = removeLeadingSlash ( options . hotModuleReplacementEndpointUrl ) ;
292
-
293
- // This is the URL inside the Webpack middleware Node server that we'll proxy to.
294
- // We have to prefix with the public path because Webpack will add the publicPath
295
- // when it resolves hmrClientEndpoint as a relative URL.
296
- const hmrServerEndpoint = ensureLeadingSlash ( publicPathNoTrailingSlash + options . hotModuleReplacementEndpointUrl ) ;
297
-
298
- // We always overwrite the 'path' option as it needs to match what the .NET side is expecting
299
- const hmrClientOptions = options . suppliedOptions . HotModuleReplacementClientOptions || < StringMap < string > > { } ;
300
- hmrClientOptions [ 'path' ] = hmrClientEndpoint ;
301
-
302
- const dynamicPublicPathKey = 'dynamicPublicPath' ;
303
- if ( ! ( dynamicPublicPathKey in hmrClientOptions ) ) {
304
- // dynamicPublicPath default to true, so we can work with nonempty pathbases (virtual directories)
305
- hmrClientOptions [ dynamicPublicPathKey ] = true ;
306
- } else {
307
- // ... but you can set it to any other value explicitly if you want (e.g., false)
308
- hmrClientOptions [ dynamicPublicPathKey ] = JSON . parse ( hmrClientOptions [ dynamicPublicPathKey ] ) ;
309
- }
272
+ const enableHotModuleReplacement = options . suppliedOptions . HotModuleReplacement ;
273
+ const enableReactHotModuleReplacement = options . suppliedOptions . ReactHotModuleReplacement ;
274
+ if ( enableReactHotModuleReplacement && ! enableHotModuleReplacement ) {
275
+ callback ( 'To use ReactHotModuleReplacement, you must also enable the HotModuleReplacement option.' , null ) ;
276
+ return ;
277
+ }
310
278
311
- attachWebpackDevMiddleware ( app , webpackConfig , enableHotModuleReplacement , enableReactHotModuleReplacement , hmrClientOptions , hmrServerEndpoint ) ;
312
- }
313
- } ) ;
279
+ // The default value, 0, means 'choose randomly'
280
+ const suggestedHMRPortOrZero = options . suppliedOptions . HotModuleReplacementServerPort || 0 ;
314
281
315
- // Tell the ASP.NET app what addresses we're listening on, so that it can proxy requests here
316
- callback ( null , {
317
- Port : listener . address ( ) . port ,
318
- PublicPaths : normalizedPublicPaths
319
- } ) ;
320
- } catch ( ex ) {
321
- callback ( ex . stack , null ) ;
322
- }
323
- } ) ;
282
+ const app = connect ( ) ;
283
+ const listener = app . listen ( suggestedHMRPortOrZero , ( ) => {
284
+ try {
285
+ // For each webpack config that specifies a public path, add webpack dev middleware for it
286
+ const normalizedPublicPaths : string [ ] = [ ] ;
287
+ webpackConfigArray . forEach ( webpackConfig => {
288
+ if ( webpackConfig . target === 'node' ) {
289
+ // For configs that target Node, it's meaningless to set up an HTTP listener, since
290
+ // Node isn't going to load those modules over HTTP anyway. It just loads them directly
291
+ // from disk. So the most relevant thing we can do with such configs is just write
292
+ // updated builds to disk, just like "webpack --watch".
293
+ beginWebpackWatcher ( webpackConfig ) ;
294
+ } else {
295
+ // For configs that target browsers, we can set up an HTTP listener, and dynamically
296
+ // modify the config to enable HMR etc. This just requires that we have a publicPath.
297
+ const publicPath = ( webpackConfig . output . publicPath || '' ) . trim ( ) ;
298
+ if ( ! publicPath ) {
299
+ throw new Error ( 'To use the Webpack dev server, you must specify a value for \'publicPath\' on the \'output\' section of your webpack config (for any configuration that targets browsers)' ) ;
300
+ }
301
+ const publicPathNoTrailingSlash = removeTrailingSlash ( publicPath ) ;
302
+ normalizedPublicPaths . push ( publicPathNoTrailingSlash ) ;
303
+
304
+ // This is the URL the client will connect to, except that since it's a relative URL
305
+ // (no leading slash), Webpack will resolve it against the runtime <base href> URL
306
+ // plus it also adds the publicPath
307
+ const hmrClientEndpoint = removeLeadingSlash ( options . hotModuleReplacementEndpointUrl ) ;
308
+
309
+ // This is the URL inside the Webpack middleware Node server that we'll proxy to.
310
+ // We have to prefix with the public path because Webpack will add the publicPath
311
+ // when it resolves hmrClientEndpoint as a relative URL.
312
+ const hmrServerEndpoint = ensureLeadingSlash ( publicPathNoTrailingSlash + options . hotModuleReplacementEndpointUrl ) ;
313
+
314
+ // We always overwrite the 'path' option as it needs to match what the .NET side is expecting
315
+ const hmrClientOptions = options . suppliedOptions . HotModuleReplacementClientOptions || < StringMap < string > > { } ;
316
+ hmrClientOptions [ 'path' ] = hmrClientEndpoint ;
317
+
318
+ const dynamicPublicPathKey = 'dynamicPublicPath' ;
319
+ if ( ! ( dynamicPublicPathKey in hmrClientOptions ) ) {
320
+ // dynamicPublicPath default to true, so we can work with nonempty pathbases (virtual directories)
321
+ hmrClientOptions [ dynamicPublicPathKey ] = true ;
322
+ } else {
323
+ // ... but you can set it to any other value explicitly if you want (e.g., false)
324
+ hmrClientOptions [ dynamicPublicPathKey ] = JSON . parse ( hmrClientOptions [ dynamicPublicPathKey ] ) ;
325
+ }
326
+
327
+ attachWebpackDevMiddleware ( app , webpackConfig , enableHotModuleReplacement , enableReactHotModuleReplacement , hmrClientOptions , hmrServerEndpoint ) ;
328
+ }
329
+ } ) ;
330
+
331
+ // Tell the ASP.NET app what addresses we're listening on, so that it can proxy requests here
332
+ callback ( null , {
333
+ Port : listener . address ( ) . port ,
334
+ PublicPaths : normalizedPublicPaths
335
+ } ) ;
336
+ } catch ( ex ) {
337
+ callback ( ex . stack , null ) ;
338
+ }
339
+ } ) ;
340
+ } ,
341
+ err => callback ( err . stack , null )
342
+ ) ;
324
343
}
325
344
326
345
function removeLeadingSlash ( str : string ) {
0 commit comments