@@ -34,15 +34,25 @@ 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).
37+ // This is copied in from es6-promise since adding the module caused conflicts with the es5 modules
38+ interface Thenable < T > {
39+ then < U > ( onFulfilled ?: ( value : T ) => U | Thenable < U > , onRejected ?: ( error : any ) => U | Thenable < U > ) : Thenable < U > ;
40+ then < U > ( onFulfilled ?: ( value : T ) => U | Thenable < U > , onRejected ?: ( error : any ) => void ) : Thenable < U > ;
41+ }
42+
43+ // We support these four kinds of webpack.config.js export
3944type WebpackConfigOrArray = webpack . Configuration | webpack . Configuration [ ] ;
45+ type WebpackConfigOrArrayOrThenable = WebpackConfigOrArray | Thenable < WebpackConfigOrArray > ;
4046interface WebpackConfigFunc {
41- ( env ?: any ) : WebpackConfigOrArray ;
47+ ( env ?: any ) : WebpackConfigOrArrayOrThenable ;
4248}
43- type WebpackConfigExport = WebpackConfigOrArray | WebpackConfigFunc ;
49+ type WebpackConfigExport = WebpackConfigOrArrayOrThenable | WebpackConfigFunc ;
4450type WebpackConfigModuleExports = WebpackConfigExport | EsModuleExports < WebpackConfigExport > ;
4551
52+ function isThenable ( config : WebpackConfigModuleExports ) : config is Thenable < WebpackConfigOrArray > {
53+ return ( < Thenable < WebpackConfigOrArray > > config ) . then !== undefined ;
54+ }
55+
4656function attachWebpackDevMiddleware ( app : any , webpackConfig : webpack . Configuration , enableHotModuleReplacement : boolean , enableReactHotModuleReplacement : boolean , hmrClientOptions : StringMap < string > , hmrServerEndpoint : string ) {
4757 // Build the final Webpack config based on supplied options
4858 if ( enableHotModuleReplacement ) {
@@ -250,76 +260,106 @@ export function createWebpackDevServer(callback: CreateDevServerCallback, option
250260 // default env values in their webpack.config.js).
251261 webpackConfigExport = webpackConfigExport ( ) ;
252262 }
253- const webpackConfigArray = webpackConfigExport instanceof Array ? webpackConfigExport : [ webpackConfigExport ] ;
254263
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 ;
264+ var webpackConfigThenable : Thenable < WebpackConfigOrArray > ;
265+ if ( isThenable ( webpackConfigExport ) ) {
266+ webpackConfigThenable = webpackConfigExport ;
267+ } else {
268+ webpackConfigThenable = {
269+ then : function ( onFulfilled ) {
270+ return onFulfilled ( webpackConfigExport ) ;
271+ }
272+ }
260273 }
261274
262- // The default value, 0, means 'choose randomly'
263- const suggestedHMRPortOrZero = options . suppliedOptions . HotModuleReplacementServerPort || 0 ;
264-
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 ;
305- } 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 ] ) ;
308- }
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 ) ;
275+ webpackConfigThenable . then (
276+ ( webpackConfigResolved ) => {
277+ const webpackConfigArray = webpackConfigResolved instanceof Array ? webpackConfigResolved : [ webpackConfigResolved ] ;
278+
279+ const enableHotModuleReplacement = options . suppliedOptions . HotModuleReplacement ;
280+ const enableReactHotModuleReplacement = options . suppliedOptions . ReactHotModuleReplacement ;
281+ if ( enableReactHotModuleReplacement && ! enableHotModuleReplacement ) {
282+ callback (
283+ 'To use ReactHotModuleReplacement, you must also enable the HotModuleReplacement option.' ,
284+ null
285+ ) ;
286+ return ;
287+ }
288+
289+ // The default value, 0, means 'choose randomly'
290+ const suggestedHMRPortOrZero = options . suppliedOptions . HotModuleReplacementServerPort || 0 ;
291+
292+ const app = connect ( ) ;
293+ const listener = app . listen ( suggestedHMRPortOrZero , ( ) => {
294+ try {
295+ // For each webpack config that specifies a public path, add webpack dev middleware for it
296+ const normalizedPublicPaths : string [ ] = [ ] ;
297+ webpackConfigArray . forEach ( webpackConfig => {
298+ if ( webpackConfig . target === 'node' ) {
299+ // For configs that target Node, it's meaningless to set up an HTTP listener, since
300+ // Node isn't going to load those modules over HTTP anyway. It just loads them directly
301+ // from disk. So the most relevant thing we can do with such configs is just write
302+ // updated builds to disk, just like "webpack --watch".
303+ beginWebpackWatcher ( webpackConfig ) ;
304+ } else {
305+ // For configs that target browsers, we can set up an HTTP listener, and dynamically
306+ // modify the config to enable HMR etc. This just requires that we have a publicPath.
307+ const publicPath = ( webpackConfig . output . publicPath || '' ) . trim ( ) ;
308+ if ( ! publicPath ) {
309+ throw new Error (
310+ '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)' ) ;
311+ }
312+ const publicPathNoTrailingSlash = removeTrailingSlash ( publicPath ) ;
313+ normalizedPublicPaths . push ( publicPathNoTrailingSlash ) ;
314+
315+ // This is the URL the client will connect to, except that since it's a relative URL
316+ // (no leading slash), Webpack will resolve it against the runtime <base href> URL
317+ // plus it also adds the publicPath
318+ const hmrClientEndpoint = removeLeadingSlash ( options . hotModuleReplacementEndpointUrl ) ;
319+
320+ // This is the URL inside the Webpack middleware Node server that we'll proxy to.
321+ // We have to prefix with the public path because Webpack will add the publicPath
322+ // when it resolves hmrClientEndpoint as a relative URL.
323+ const hmrServerEndpoint = ensureLeadingSlash ( publicPathNoTrailingSlash + options . hotModuleReplacementEndpointUrl ) ;
324+
325+ // We always overwrite the 'path' option as it needs to match what the .NET side is expecting
326+ const hmrClientOptions = options . suppliedOptions . HotModuleReplacementClientOptions || < StringMap < string > > { } ;
327+ hmrClientOptions [ 'path' ] = hmrClientEndpoint ;
328+
329+ const dynamicPublicPathKey = 'dynamicPublicPath' ;
330+ if ( ! ( dynamicPublicPathKey in hmrClientOptions ) ) {
331+ // dynamicPublicPath default to true, so we can work with nonempty pathbases (virtual directories)
332+ hmrClientOptions [ dynamicPublicPathKey ] = true ;
333+ } else {
334+ // ... but you can set it to any other value explicitly if you want (e.g., false)
335+ hmrClientOptions [ dynamicPublicPathKey ] = JSON . parse ( hmrClientOptions [ dynamicPublicPathKey ] ) ;
336+ }
337+
338+ attachWebpackDevMiddleware (
339+ app ,
340+ webpackConfig ,
341+ enableHotModuleReplacement ,
342+ enableReactHotModuleReplacement ,
343+ hmrClientOptions ,
344+ hmrServerEndpoint
345+ ) ;
346+ }
347+ } ) ;
348+
349+ // Tell the ASP.NET app what addresses we're listening on, so that it can proxy requests here
350+ callback ( null , {
351+ Port : listener . address ( ) . port ,
352+ PublicPaths : normalizedPublicPaths
353+ } ) ;
354+ } catch ( ex ) {
355+ callback ( ex . stack , null ) ;
356+ }
357+ } ) ;
358+ } ,
359+ ( err ) => {
360+ callback ( err . stack , null ) ;
321361 }
322- } ) ;
362+ ) ;
323363}
324364
325365function removeLeadingSlash ( str : string ) {
0 commit comments