@@ -35,15 +35,25 @@ interface DevServerOptions {
3535 EnvParam : any ;
3636}
3737
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
4045type WebpackConfigOrArray = webpack . Configuration | webpack . Configuration [ ] ;
46+ type WebpackConfigOrArrayOrThenable = WebpackConfigOrArray | Thenable < WebpackConfigOrArray > ;
4147interface WebpackConfigFunc {
42- ( env ?: any ) : WebpackConfigOrArray ;
48+ ( env ?: any ) : WebpackConfigOrArrayOrThenable ;
4349}
44- type WebpackConfigExport = WebpackConfigOrArray | WebpackConfigFunc ;
50+ type WebpackConfigExport = WebpackConfigOrArrayOrThenable | WebpackConfigFunc ;
4551type WebpackConfigModuleExports = WebpackConfigExport | EsModuleExports < WebpackConfigExport > ;
4652
53+ function isThenable ( obj : any ) {
54+ return obj && typeof ( < Thenable < any > > obj ) . then === 'function' ;
55+ }
56+
4757function attachWebpackDevMiddleware ( app : any , webpackConfig : webpack . Configuration , enableHotModuleReplacement : boolean , enableReactHotModuleReplacement : boolean , hmrClientOptions : StringMap < string > , hmrServerEndpoint : string ) {
4858 // Build the final Webpack config based on supplied options
4959 if ( enableHotModuleReplacement ) {
@@ -251,76 +261,85 @@ export function createWebpackDevServer(callback: CreateDevServerCallback, option
251261 // your Startup.cs.
252262 webpackConfigExport = webpackConfigExport ( options . suppliedOptions . EnvParam ) ;
253263 }
254- const webpackConfigArray = webpackConfigExport instanceof Array ? webpackConfigExport : [ webpackConfigExport ] ;
255264
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 ) } ;
262268
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 ] ;
265271
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+ }
310278
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 ;
314281
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+ ) ;
324343}
325344
326345function removeLeadingSlash ( str : string ) {
0 commit comments