@@ -34,13 +34,18 @@ interface DevServerOptions {
3434 ReactHotModuleReplacement : boolean ;
3535}
3636
37- // We support these three kinds of webpack.config.js export. We don't currently support exported promises
38- // (though we might be able to add that in the future, if there's a need).
39- type WebpackConfigOrArray = webpack . Configuration | webpack . Configuration [ ] ;
37+ // We support these four kinds of webpack.config.js export
38+ type WebpackConfigOrArray = webpack . Configuration | webpack . Configuration [ ] | WebpackConfigThenable ;
39+ // Copied from DefinitelyTyped/types/es6-promise/index.d.ts, We only need this interface so avoiding pulling another dependancy
40+ interface Thenable < T > {
41+ then < U > ( onFulfilled ?: ( value : T ) => U | Thenable < U > , onRejected ?: ( error : any ) => U | Thenable < U > ) : Thenable < U > ;
42+ then < U > ( onFulfilled ?: ( value : T ) => U | Thenable < U > , onRejected ?: ( error : any ) => void ) : Thenable < U > ;
43+ }
44+ type WebpackConfigOrArrayOrThenable = WebpackConfigOrArray | Thenable < WebpackConfigOrArray > ;
4045interface WebpackConfigFunc {
41- ( env ?: any ) : WebpackConfigOrArray ;
46+ ( env ?: any ) : WebpackConfigOrArrayOrThenable ;
4247}
43- type WebpackConfigExport = WebpackConfigOrArray | WebpackConfigFunc ;
48+ type WebpackConfigExport = WebpackConfigOrArrayOrThenable | WebpackConfigFunc ;
4449type WebpackConfigModuleExports = WebpackConfigExport | EsModuleExports < WebpackConfigExport > ;
4550
4651function attachWebpackDevMiddleware ( app : any , webpackConfig : webpack . Configuration , enableHotModuleReplacement : boolean , enableReactHotModuleReplacement : boolean , hmrClientOptions : StringMap < string > , hmrServerEndpoint : string ) {
@@ -250,74 +255,88 @@ export function createWebpackDevServer(callback: CreateDevServerCallback, option
250255 // default env values in their webpack.config.js).
251256 webpackConfigExport = webpackConfigExport ( ) ;
252257 }
253- const webpackConfigArray = webpackConfigExport instanceof Array ? webpackConfigExport : [ webpackConfigExport ] ;
254-
255- const enableHotModuleReplacement = options . suppliedOptions . HotModuleReplacement ;
256- const enableReactHotModuleReplacement = options . suppliedOptions . ReactHotModuleReplacement ;
257- if ( enableReactHotModuleReplacement && ! enableHotModuleReplacement ) {
258- callback ( 'To use ReactHotModuleReplacement, you must also enable the HotModuleReplacement option.' , null ) ;
259- return ;
258+
259+ var webpackConfigThenable : Thenable < WebpackConfigOrArray > ;
260+ if ( ! webpackConfigExport . then ) {
261+ webpackConfigThenable = {
262+ then : function ( cb ) {
263+ cb ( webpackConfigExport ) ;
264+ }
265+ }
266+ } else {
267+ webpackConfigThenable = webpackConfigExport
260268 }
269+
270+ webpackConfigThenable . then ( function ( webpackConfigResolved ) {
271+ const webpackConfigArray = webpackConfigResolved instanceof Array ? webpackConfigResolved : [ webpackConfigResolved ] ;
272+
273+ const enableHotModuleReplacement = options . suppliedOptions . HotModuleReplacement ;
274+ const enableReactHotModuleReplacement = options . suppliedOptions . ReactHotModuleReplacement ;
275+ if ( enableReactHotModuleReplacement && ! enableHotModuleReplacement ) {
276+ callback ( 'To use ReactHotModuleReplacement, you must also enable the HotModuleReplacement option.' , null ) ;
277+ return ;
278+ }
261279
262- // The default value, 0, means 'choose randomly'
263- const suggestedHMRPortOrZero = options . suppliedOptions . HotModuleReplacementServerPort || 0 ;
280+ // The default value, 0, means 'choose randomly'
281+ const suggestedHMRPortOrZero = options . suppliedOptions . HotModuleReplacementServerPort || 0 ;
264282
265- const app = connect ( ) ;
266- const listener = app . listen ( suggestedHMRPortOrZero , ( ) => {
267- try {
268- // For each webpack config that specifies a public path, add webpack dev middleware for it
269- const normalizedPublicPaths : string [ ] = [ ] ;
270- webpackConfigArray . forEach ( webpackConfig => {
271- if ( webpackConfig . target === 'node' ) {
272- // For configs that target Node, it's meaningless to set up an HTTP listener, since
273- // Node isn't going to load those modules over HTTP anyway. It just loads them directly
274- // from disk. So the most relevant thing we can do with such configs is just write
275- // updated builds to disk, just like "webpack --watch".
276- beginWebpackWatcher ( webpackConfig ) ;
277- } else {
278- // For configs that target browsers, we can set up an HTTP listener, and dynamically
279- // modify the config to enable HMR etc. This just requires that we have a publicPath.
280- const publicPath = ( webpackConfig . output . publicPath || '' ) . trim ( ) ;
281- if ( ! publicPath ) {
282- 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)' ) ;
283- }
284- const publicPathNoTrailingSlash = removeTrailingSlash ( publicPath ) ;
285- normalizedPublicPaths . push ( publicPathNoTrailingSlash ) ;
286-
287- // This is the URL the client will connect to, except that since it's a relative URL
288- // (no leading slash), Webpack will resolve it against the runtime <base href> URL
289- // plus it also adds the publicPath
290- const hmrClientEndpoint = removeLeadingSlash ( options . hotModuleReplacementEndpointUrl ) ;
291-
292- // This is the URL inside the Webpack middleware Node server that we'll proxy to.
293- // We have to prefix with the public path because Webpack will add the publicPath
294- // when it resolves hmrClientEndpoint as a relative URL.
295- const hmrServerEndpoint = ensureLeadingSlash ( publicPathNoTrailingSlash + options . hotModuleReplacementEndpointUrl ) ;
296-
297- // We always overwrite the 'path' option as it needs to match what the .NET side is expecting
298- const hmrClientOptions = options . suppliedOptions . HotModuleReplacementClientOptions || < StringMap < string > > { } ;
299- hmrClientOptions [ 'path' ] = hmrClientEndpoint ;
300-
301- const dynamicPublicPathKey = 'dynamicPublicPath' ;
302- if ( ! ( dynamicPublicPathKey in hmrClientOptions ) ) {
303- // dynamicPublicPath default to true, so we can work with nonempty pathbases (virtual directories)
304- hmrClientOptions [ dynamicPublicPathKey ] = true ;
283+ const app = connect ( ) ;
284+ const listener = app . listen ( suggestedHMRPortOrZero , ( ) => {
285+ try {
286+ // For each webpack config that specifies a public path, add webpack dev middleware for it
287+ const normalizedPublicPaths : string [ ] = [ ] ;
288+ webpackConfigArray . forEach ( webpackConfig => {
289+ if ( webpackConfig . target === 'node' ) {
290+ // For configs that target Node, it's meaningless to set up an HTTP listener, since
291+ // Node isn't going to load those modules over HTTP anyway. It just loads them directly
292+ // from disk. So the most relevant thing we can do with such configs is just write
293+ // updated builds to disk, just like "webpack --watch".
294+ beginWebpackWatcher ( webpackConfig ) ;
305295 } else {
306- // ... but you can set it to any other value explicitly if you want (e.g., false)
307- hmrClientOptions [ dynamicPublicPathKey ] = JSON . parse ( hmrClientOptions [ dynamicPublicPathKey ] ) ;
296+ // For configs that target browsers, we can set up an HTTP listener, and dynamically
297+ // modify the config to enable HMR etc. This just requires that we have a publicPath.
298+ const publicPath = ( webpackConfig . output . publicPath || '' ) . trim ( ) ;
299+ if ( ! publicPath ) {
300+ 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)' ) ;
301+ }
302+ const publicPathNoTrailingSlash = removeTrailingSlash ( publicPath ) ;
303+ normalizedPublicPaths . push ( publicPathNoTrailingSlash ) ;
304+
305+ // This is the URL the client will connect to, except that since it's a relative URL
306+ // (no leading slash), Webpack will resolve it against the runtime <base href> URL
307+ // plus it also adds the publicPath
308+ const hmrClientEndpoint = removeLeadingSlash ( options . hotModuleReplacementEndpointUrl ) ;
309+
310+ // This is the URL inside the Webpack middleware Node server that we'll proxy to.
311+ // We have to prefix with the public path because Webpack will add the publicPath
312+ // when it resolves hmrClientEndpoint as a relative URL.
313+ const hmrServerEndpoint = ensureLeadingSlash ( publicPathNoTrailingSlash + options . hotModuleReplacementEndpointUrl ) ;
314+
315+ // We always overwrite the 'path' option as it needs to match what the .NET side is expecting
316+ const hmrClientOptions = options . suppliedOptions . HotModuleReplacementClientOptions || < StringMap < string > > { } ;
317+ hmrClientOptions [ 'path' ] = hmrClientEndpoint ;
318+
319+ const dynamicPublicPathKey = 'dynamicPublicPath' ;
320+ if ( ! ( dynamicPublicPathKey in hmrClientOptions ) ) {
321+ // dynamicPublicPath default to true, so we can work with nonempty pathbases (virtual directories)
322+ hmrClientOptions [ dynamicPublicPathKey ] = true ;
323+ } else {
324+ // ... but you can set it to any other value explicitly if you want (e.g., false)
325+ hmrClientOptions [ dynamicPublicPathKey ] = JSON . parse ( hmrClientOptions [ dynamicPublicPathKey ] ) ;
326+ }
327+
328+ attachWebpackDevMiddleware ( app , webpackConfig , enableHotModuleReplacement , enableReactHotModuleReplacement , hmrClientOptions , hmrServerEndpoint ) ;
308329 }
309-
310- attachWebpackDevMiddleware ( app , webpackConfig , enableHotModuleReplacement , enableReactHotModuleReplacement , hmrClientOptions , hmrServerEndpoint ) ;
311- }
312- } ) ;
313-
314- // Tell the ASP.NET app what addresses we're listening on, so that it can proxy requests here
315- callback ( null , {
316- Port : listener . address ( ) . port ,
317- PublicPaths : normalizedPublicPaths
318- } ) ;
319- } catch ( ex ) {
320- callback ( ex . stack , null ) ;
330+ } ) ;
331+
332+ // Tell the ASP.NET app what addresses we're listening on, so that it can proxy requests here
333+ callback ( null , {
334+ Port : listener . address ( ) . port ,
335+ PublicPaths : normalizedPublicPaths
336+ } ) ;
337+ } catch ( ex ) {
338+ callback ( ex . stack , null ) ;
339+ }
321340 }
322341 } ) ;
323342}
0 commit comments