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

Commit 66617ff

Browse files
waterfoulAaron Aichlmayr
authored andcommitted
Added support for Thenables
1 parent 4af2e86 commit 66617ff

File tree

2 files changed

+112
-70
lines changed

2 files changed

+112
-70
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
/node_modules/
22
/*.js
33
/*.d.ts
4+
/.idea
5+
*.sln

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

Lines changed: 110 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -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
3944
type WebpackConfigOrArray = webpack.Configuration | webpack.Configuration[];
45+
type WebpackConfigOrArrayOrThenable = WebpackConfigOrArray | Thenable<WebpackConfigOrArray>;
4046
interface WebpackConfigFunc {
41-
(env?: any): WebpackConfigOrArray;
47+
(env?: any): WebpackConfigOrArrayOrThenable;
4248
}
43-
type WebpackConfigExport = WebpackConfigOrArray | WebpackConfigFunc;
49+
type WebpackConfigExport = WebpackConfigOrArrayOrThenable | WebpackConfigFunc;
4450
type WebpackConfigModuleExports = WebpackConfigExport | EsModuleExports<WebpackConfigExport>;
4551

52+
function isThenable(config: WebpackConfigModuleExports): config is Thenable<WebpackConfigOrArray> {
53+
return (<Thenable<WebpackConfigOrArray>>config).then !== undefined;
54+
}
55+
4656
function 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

325365
function removeLeadingSlash(str: string) {

0 commit comments

Comments
 (0)