Skip to content
This repository was archived by the owner on Apr 8, 2020. It is now read-only.

Commit 5f6f288

Browse files
waterfoulSteveSandersonMS
authored andcommitted
Added support for Thenables
1 parent 78e583d commit 5f6f288

File tree

1 file changed

+88
-69
lines changed

1 file changed

+88
-69
lines changed

src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts

Lines changed: 88 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -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
4045
type WebpackConfigOrArray = webpack.Configuration | webpack.Configuration[];
46+
type WebpackConfigOrArrayOrThenable = WebpackConfigOrArray | Thenable<WebpackConfigOrArray>;
4147
interface WebpackConfigFunc {
42-
(env?: any): WebpackConfigOrArray;
48+
(env?: any): WebpackConfigOrArrayOrThenable;
4349
}
44-
type WebpackConfigExport = WebpackConfigOrArray | WebpackConfigFunc;
50+
type WebpackConfigExport = WebpackConfigOrArrayOrThenable | WebpackConfigFunc;
4551
type WebpackConfigModuleExports = WebpackConfigExport | EsModuleExports<WebpackConfigExport>;
4652

53+
function isThenable(obj: any) {
54+
return obj && typeof (<Thenable<any>>obj).then === 'function';
55+
}
56+
4757
function 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

326345
function removeLeadingSlash(str: string) {

0 commit comments

Comments
 (0)