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

Commit 2241c55

Browse files
Support for array-style webpack configs in aspnet-webpack. Fixes #291.
1 parent 85eba9c commit 2241c55

File tree

3 files changed

+105
-68
lines changed

3 files changed

+105
-68
lines changed

src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,20 @@ public static void UseWebpackDevMiddleware(
5858
var devServerOptions = new
5959
{
6060
webpackConfigPath = Path.Combine(nodeServicesOptions.ProjectPath, options.ConfigFile ?? DefaultConfigFile),
61-
suppliedOptions = options
61+
suppliedOptions = options,
62+
understandsMultiplePublicPaths = true
6263
};
6364
var devServerInfo =
6465
nodeServices.InvokeExportAsync<WebpackDevServerInfo>(nodeScript.FileName, "createWebpackDevServer",
6566
JsonConvert.SerializeObject(devServerOptions)).Result;
6667

68+
// Older versions of aspnet-webpack just returned a single 'publicPath', but now we support multiple
69+
if (devServerInfo.PublicPaths == null)
70+
{
71+
throw new InvalidOperationException(
72+
"To enable Webpack dev middleware, you must update to a newer version of the aspnet-webpack NPM package.");
73+
}
74+
6775
// Proxy the corresponding requests through ASP.NET and into the Node listener
6876
// Note that this is hardcoded to make requests to "localhost" regardless of the hostname of the
6977
// server as far as the client is concerned. This is because ConditionalProxyMiddlewareOptions is
@@ -73,7 +81,10 @@ public static void UseWebpackDevMiddleware(
7381
// able to make outbound requests to it from here.
7482
var proxyOptions = new ConditionalProxyMiddlewareOptions(WebpackDevMiddlewareScheme,
7583
"localhost", devServerInfo.Port.ToString());
76-
appBuilder.UseMiddleware<ConditionalProxyMiddleware>(devServerInfo.PublicPath, proxyOptions);
84+
foreach (var publicPath in devServerInfo.PublicPaths)
85+
{
86+
appBuilder.UseMiddleware<ConditionalProxyMiddleware>(publicPath, proxyOptions);
87+
}
7788

7889
// While it would be nice to proxy the /__webpack_hmr requests too, these return an EventStream,
7990
// and the Microsoft.AspNetCore.Proxy code doesn't handle that entirely - it throws an exception after
@@ -95,7 +106,7 @@ public static void UseWebpackDevMiddleware(
95106
class WebpackDevServerInfo
96107
{
97108
public int Port { get; set; }
98-
public string PublicPath { get; set; }
109+
public string[] PublicPaths { get; set; }
99110
}
100111
}
101112
#pragma warning restore CS0649

src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "aspnet-webpack",
3-
"version": "1.0.14",
3+
"version": "1.0.15",
44
"description": "Helpers for using Webpack in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.",
55
"main": "index.js",
66
"scripts": {

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

Lines changed: 90 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import * as url from 'url';
44
import { requireNewCopy } from './RequireNewCopy';
55

66
export interface CreateDevServerCallback {
7-
(error: any, result: { Port: number, PublicPath: string }): void;
7+
(error: any, result: { Port: number, PublicPaths: string[] }): void;
88
}
99

1010
// These are the options passed by WebpackDevMiddleware.cs
1111
interface CreateDevServerOptions {
12+
understandsMultiplePublicPaths: boolean; // For checking that the NuGet package is recent enough. Can be removed when we no longer need back-compatibility.
1213
webpackConfigPath: string;
1314
suppliedOptions: DevServerOptions;
1415
}
@@ -20,11 +21,83 @@ interface DevServerOptions {
2021
ReactHotModuleReplacement: boolean;
2122
}
2223

24+
function attachWebpackDevMiddleware(app: any, webpackConfig: webpack.Configuration, enableHotModuleReplacement: boolean, enableReactHotModuleReplacement: boolean) {
25+
// Build the final Webpack config based on supplied options
26+
if (enableHotModuleReplacement) {
27+
// For this, we only support the key/value config format, not string or string[], since
28+
// those ones don't clearly indicate what the resulting bundle name will be
29+
const entryPoints = webpackConfig.entry;
30+
const isObjectStyleConfig = entryPoints
31+
&& typeof entryPoints === 'object'
32+
&& !(entryPoints instanceof Array);
33+
if (!isObjectStyleConfig) {
34+
throw new Error('To use HotModuleReplacement, your webpack config must specify an \'entry\' value as a key-value object (e.g., "entry: { main: \'ClientApp/boot-client.ts\' }")');
35+
}
36+
37+
// Augment all entry points so they support HMR
38+
Object.getOwnPropertyNames(entryPoints).forEach(entryPointName => {
39+
if (typeof entryPoints[entryPointName] === 'string') {
40+
entryPoints[entryPointName] = ['webpack-hot-middleware/client', entryPoints[entryPointName]];
41+
} else {
42+
entryPoints[entryPointName].unshift('webpack-hot-middleware/client');
43+
}
44+
});
45+
46+
webpackConfig.plugins = webpackConfig.plugins || [];
47+
webpackConfig.plugins.push(
48+
new webpack.HotModuleReplacementPlugin()
49+
);
50+
51+
// Set up React HMR support if requested. This requires the 'aspnet-webpack-react' package.
52+
if (enableReactHotModuleReplacement) {
53+
let aspNetWebpackReactModule: any;
54+
try {
55+
aspNetWebpackReactModule = require('aspnet-webpack-react');
56+
} catch(ex) {
57+
throw new Error('ReactHotModuleReplacement failed because of an error while loading \'aspnet-webpack-react\'. Error was: ' + ex.stack);
58+
}
59+
60+
aspNetWebpackReactModule.addReactHotModuleReplacementBabelTransform(webpackConfig);
61+
}
62+
}
63+
64+
// Attach Webpack dev middleware and optional 'hot' middleware
65+
const compiler = webpack(webpackConfig);
66+
app.use(require('webpack-dev-middleware')(compiler, {
67+
noInfo: true,
68+
publicPath: webpackConfig.output.publicPath
69+
}));
70+
71+
if (enableHotModuleReplacement) {
72+
let webpackHotMiddlewareModule;
73+
try {
74+
webpackHotMiddlewareModule = require('webpack-hot-middleware');
75+
} catch (ex) {
76+
throw new Error('HotModuleReplacement failed because of an error while loading \'webpack-hot-middleware\'. Error was: ' + ex.stack);
77+
}
78+
app.use(webpackHotMiddlewareModule(compiler));
79+
}
80+
}
81+
2382
export function createWebpackDevServer(callback: CreateDevServerCallback, optionsJson: string) {
2483
const options: CreateDevServerOptions = JSON.parse(optionsJson);
25-
const webpackConfig: webpack.Configuration = requireNewCopy(options.webpackConfigPath);
26-
const publicPath = (webpackConfig.output.publicPath || '').trim();
27-
if (!publicPath) {
84+
85+
if (!options.understandsMultiplePublicPaths) {
86+
callback('To use Webpack dev server, you must update to a newer version of the Microsoft.AspNetCore.SpaServices package', null);
87+
return;
88+
}
89+
90+
// Read the webpack config's export, and normalize it into the more general 'array of configs' format
91+
let webpackConfigArray: webpack.Configuration[] = requireNewCopy(options.webpackConfigPath);
92+
if (!(webpackConfigArray instanceof Array)) {
93+
webpackConfigArray = [webpackConfigArray as webpack.Configuration];
94+
}
95+
96+
// Check that at least one of the configurations specifies a publicPath. Those are the only ones we'll
97+
// enable middleware for, and if there aren't any, you must be making a mistake.
98+
const webpackConfigsWithPublicPath = webpackConfigArray
99+
.filter(webpackConfig => (webpackConfig.output.publicPath || '').trim() !== '');
100+
if (webpackConfigsWithPublicPath.length === 0) {
28101
callback('To use the Webpack dev server, you must specify a value for \'publicPath\' on the \'output\' section of your webpack.config.', null);
29102
return;
30103
}
@@ -41,69 +114,22 @@ export function createWebpackDevServer(callback: CreateDevServerCallback, option
41114

42115
const app = connect();
43116
const listener = app.listen(suggestedHMRPortOrZero, () => {
44-
// Build the final Webpack config based on supplied options
45-
if (enableHotModuleReplacement) {
46-
// For this, we only support the key/value config format, not string or string[], since
47-
// those ones don't clearly indicate what the resulting bundle name will be
48-
const entryPoints = webpackConfig.entry;
49-
const isObjectStyleConfig = entryPoints
50-
&& typeof entryPoints === 'object'
51-
&& !(entryPoints instanceof Array);
52-
if (!isObjectStyleConfig) {
53-
callback('To use HotModuleReplacement, your webpack config must specify an \'entry\' value as a key-value object (e.g., "entry: { main: \'ClientApp/boot-client.ts\' }")', null);
54-
return;
55-
}
56-
57-
// Augment all entry points so they support HMR
58-
Object.getOwnPropertyNames(entryPoints).forEach(entryPointName => {
59-
if (typeof entryPoints[entryPointName] === 'string') {
60-
entryPoints[entryPointName] = ['webpack-hot-middleware/client', entryPoints[entryPointName]];
61-
} else {
62-
entryPoints[entryPointName].unshift('webpack-hot-middleware/client');
63-
}
117+
try {
118+
// For each webpack config that specifies a public path, add webpack dev middleware for it
119+
webpackConfigsWithPublicPath.forEach(webpackConfig => {
120+
attachWebpackDevMiddleware(app, webpackConfig, enableHotModuleReplacement, enableReactHotModuleReplacement);
64121
});
65122

66-
webpackConfig.plugins.push(
67-
new webpack.HotModuleReplacementPlugin()
68-
);
69-
70-
// Set up React HMR support if requested. This requires the 'aspnet-webpack-react' package.
71-
if (enableReactHotModuleReplacement) {
72-
let aspNetWebpackReactModule: any;
73-
try {
74-
aspNetWebpackReactModule = require('aspnet-webpack-react');
75-
} catch(ex) {
76-
callback('ReactHotModuleReplacement failed because of an error while loading \'aspnet-webpack-react\'. Error was: ' + ex.stack, null);
77-
return;
78-
}
79-
80-
aspNetWebpackReactModule.addReactHotModuleReplacementBabelTransform(webpackConfig);
81-
}
82-
}
83-
84-
// Attach Webpack dev middleware and optional 'hot' middleware
85-
const compiler = webpack(webpackConfig);
86-
app.use(require('webpack-dev-middleware')(compiler, {
87-
noInfo: true,
88-
publicPath: publicPath
89-
}));
90-
91-
if (enableHotModuleReplacement) {
92-
let webpackHotMiddlewareModule;
93-
try {
94-
webpackHotMiddlewareModule = require('webpack-hot-middleware');
95-
} catch (ex) {
96-
callback('HotModuleReplacement failed because of an error while loading \'webpack-hot-middleware\'. Error was: ' + ex.stack, null);
97-
return;
98-
}
99-
app.use(webpackHotMiddlewareModule(compiler));
123+
// Tell the ASP.NET app what addresses we're listening on, so that it can proxy requests here
124+
callback(null, {
125+
Port: listener.address().port,
126+
PublicPaths: webpackConfigsWithPublicPath.map(webpackConfig =>
127+
removeTrailingSlash(getPath(webpackConfig.output.publicPath))
128+
)
129+
});
130+
} catch (ex) {
131+
callback(ex.stack, null);
100132
}
101-
102-
// Tell the ASP.NET app what addresses we're listening on, so that it can proxy requests here
103-
callback(null, {
104-
Port: listener.address().port,
105-
PublicPath: removeTrailingSlash(getPath(publicPath))
106-
});
107133
});
108134
}
109135

0 commit comments

Comments
 (0)