Skip to content

Commit 8ef9ab3

Browse files
Enable Webpack dev middleware and React hot module replacement
1 parent 0039187 commit 8ef9ab3

6 files changed

Lines changed: 185 additions & 3 deletions

File tree

samples/react/ReactGrid/Startup.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.AspNet.Builder;
22
using Microsoft.AspNet.Hosting;
3+
using Microsoft.AspNet.SpaServices;
34
using Microsoft.Extensions.Configuration;
45
using Microsoft.Extensions.DependencyInjection;
56
using Microsoft.Extensions.Logging;
@@ -51,6 +52,11 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF
5152
// send the request to the following path or controller action.
5253
app.UseExceptionHandler("/Home/Error");
5354
}
55+
56+
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {
57+
HotModuleReplacement = true,
58+
ReactHotModuleReplacement = true
59+
});
5460

5561
// Add static files to the request pipeline.
5662
app.UseStaticFiles();

samples/react/ReactGrid/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@
1515
},
1616
"devDependencies": {
1717
"babel-loader": "^6.2.1",
18+
"babel-plugin-react-transform": "^2.0.0",
1819
"babel-preset-es2015": "^6.3.13",
1920
"babel-preset-react": "^6.3.13",
2021
"css-loader": "^0.21.0",
22+
"express": "^4.13.4",
2123
"extract-text-webpack-plugin": "^0.8.2",
2224
"file-loader": "^0.8.4",
25+
"react-transform-hmr": "^1.0.1",
2326
"style-loader": "^0.13.0",
2427
"url-loader": "^0.5.6",
25-
"webpack": "^1.12.2"
28+
"webpack": "^1.12.2",
29+
"webpack-dev-middleware": "^1.5.1",
30+
"webpack-hot-middleware": "^2.6.4"
2631
}
2732
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
var express = require('express');
2+
var webpack = require('webpack');
3+
var defaultPort = 0; // 0 means 'choose randomly'. Could allow an explicit value to be supplied instead.
4+
5+
module.exports = {
6+
createWebpackDevServer: function(callback, optionsJson) {
7+
var options = JSON.parse(optionsJson);
8+
var webpackConfig = require(options.webpackConfigPath);
9+
var publicPath = (webpackConfig.output.publicPath || '').trim();
10+
if (!publicPath) {
11+
throw new Error('To use the Webpack dev server, you must specify a value for \'publicPath\' on the \'output\' section of your webpack.config.');
12+
}
13+
14+
var enableHotModuleReplacement = options.suppliedOptions.HotModuleReplacement;
15+
var enableReactHotModuleReplacement = options.suppliedOptions.ReactHotModuleReplacement;
16+
17+
var app = new express();
18+
var listener = app.listen(defaultPort, function() {
19+
// Build the final Webpack config based on supplied options
20+
if (enableHotModuleReplacement) {
21+
webpackConfig.entry.main.unshift('webpack-hot-middleware/client');
22+
webpackConfig.plugins.push(
23+
new webpack.HotModuleReplacementPlugin(),
24+
new webpack.NoErrorsPlugin()
25+
);
26+
27+
if (enableReactHotModuleReplacement) {
28+
addReactHotModuleReplacementBabelTransform(webpackConfig);
29+
}
30+
}
31+
32+
// Attach Webpack dev middleware and optional 'hot' middleware
33+
var compiler = webpack(webpackConfig);
34+
app.use(require('webpack-dev-middleware')(compiler, {
35+
noInfo: true,
36+
publicPath: publicPath
37+
}));
38+
39+
if (enableHotModuleReplacement) {
40+
app.use(require('webpack-hot-middleware')(compiler));
41+
}
42+
43+
// Tell the ASP.NET app what addresses we're listening on, so that it can proxy requests here
44+
callback(null, {
45+
Port: listener.address().port,
46+
PublicPath: removeTrailingSlash(publicPath)
47+
});
48+
});
49+
}
50+
};
51+
52+
function addReactHotModuleReplacementBabelTransform(webpackConfig) {
53+
webpackConfig.module.loaders.forEach(function(loaderConfig) {
54+
if (loaderConfig.loader && loaderConfig.loader.match(/\bbabel-loader\b/)) {
55+
// Ensure the babel-loader options includes a 'query'
56+
var query = loaderConfig.query = loaderConfig.query || {};
57+
58+
// Ensure Babel plugins includes 'react-transform'
59+
var plugins = query.plugins = query.plugins || [];
60+
if (!plugins.some(function(pluginConfig) {
61+
return pluginConfig && pluginConfig[0] === 'react-transform';
62+
})) {
63+
plugins.push(['react-transform', {}]);
64+
}
65+
66+
// Ensure 'react-transform' plugin is configured to use 'react-transform-hmr'
67+
plugins.forEach(function(pluginConfig) {
68+
if (pluginConfig && pluginConfig[0] === 'react-transform') {
69+
var pluginOpts = pluginConfig[1] = pluginConfig[1] || {};
70+
var transforms = pluginOpts.transforms = pluginOpts.transforms || [];
71+
if (!transforms.some(function(transform) {
72+
return transform.transform === 'react-transform-hmr';
73+
})) {
74+
transforms.push({
75+
transform: "react-transform-hmr",
76+
imports: ["react"],
77+
locals: ["module"] // Important for Webpack HMR
78+
});
79+
}
80+
}
81+
});
82+
}
83+
});
84+
}
85+
86+
function removeTrailingSlash(str) {
87+
if (str.lastIndexOf('/') === str.length - 1) {
88+
str = str.substring(0, str.length - 1);
89+
}
90+
91+
return str;
92+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System;
2+
using System.IO;
3+
using Microsoft.AspNet.NodeServices;
4+
using Microsoft.AspNet.Proxy;
5+
using Microsoft.AspNet.SpaServices;
6+
using Microsoft.Extensions.PlatformAbstractions;
7+
using Newtonsoft.Json;
8+
9+
// Putting in this namespace so it's always available whenever MapRoute is
10+
namespace Microsoft.AspNet.Builder
11+
{
12+
public static class WebpackDevMiddleware
13+
{
14+
const string WebpackDevMiddlewareHostname = "localhost";
15+
const string WebpackHotMiddlewareEndpoint = "/__webpack_hmr";
16+
17+
static INodeServices fallbackNodeServices; // Used only if no INodeServices was registered with DI
18+
19+
public static void UseWebpackDevMiddleware(this IApplicationBuilder appBuilder, WebpackDevMiddlewareOptions options = null) {
20+
// Validate options
21+
if (options != null) {
22+
if (options.ReactHotModuleReplacement && !options.HotModuleReplacement) {
23+
throw new ArgumentException("To enable ReactHotModuleReplacement, you must also enable HotModuleReplacement.");
24+
}
25+
}
26+
27+
// Get the NodeServices instance from DI
28+
var nodeServices = (INodeServices)appBuilder.ApplicationServices.GetService(typeof (INodeServices)) ?? fallbackNodeServices;
29+
30+
// Consider removing the following. Having it means you can get away with not putting app.AddNodeServices()
31+
// in your startup file, but then again it might be confusing that you don't need to.
32+
var appEnv = (IApplicationEnvironment)appBuilder.ApplicationServices.GetService(typeof(IApplicationEnvironment));
33+
if (nodeServices == null) {
34+
nodeServices = fallbackNodeServices = Configuration.CreateNodeServices(NodeHostingModel.Http, appEnv.ApplicationBasePath);
35+
}
36+
37+
// Get a filename matching the middleware Node script
38+
var script = EmbeddedResourceReader.Read(typeof (WebpackDevMiddleware), "/Content/Node/webpack-dev-middleware.js");
39+
var nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit
40+
41+
// Tell Node to start the server hosting webpack-dev-middleware
42+
var devServerOptions = new {
43+
webpackConfigPath = Path.Combine(appEnv.ApplicationBasePath, "webpack.config.js"),
44+
suppliedOptions = options ?? new WebpackDevMiddlewareOptions()
45+
};
46+
var devServerInfo = nodeServices.InvokeExport<WebpackDevServerInfo>(nodeScript.FileName, "createWebpackDevServer", JsonConvert.SerializeObject(devServerOptions)).Result;
47+
48+
// Proxy the corresponding requests through ASP.NET and into the Node listener
49+
appBuilder.Map(devServerInfo.PublicPath, builder => {
50+
builder.RunProxy(new ProxyOptions {
51+
Host = WebpackDevMiddlewareHostname,
52+
Port = devServerInfo.Port.ToString()
53+
});
54+
});
55+
appBuilder.Map(WebpackHotMiddlewareEndpoint, builder => {
56+
builder.RunProxy(new ProxyOptions {
57+
Host = WebpackDevMiddlewareHostname,
58+
Port = devServerInfo.Port.ToString()
59+
});
60+
});
61+
}
62+
63+
class WebpackDevServerInfo {
64+
public int Port;
65+
public string PublicPath;
66+
}
67+
}
68+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Microsoft.AspNet.SpaServices {
2+
public class WebpackDevMiddlewareOptions {
3+
public bool HotModuleReplacement { get; set; }
4+
public bool ReactHotModuleReplacement { get; set; }
5+
}
6+
}

src/Microsoft.AspNet.SpaServices/project.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@
1414
"licenseUrl": "",
1515
"dependencies": {
1616
"Microsoft.AspNet.Mvc": "6.0.0-rc1-final",
17-
"Microsoft.AspNet.Routing": "1.0.0-rc1-final"
17+
"Microsoft.AspNet.Routing": "1.0.0-rc1-final",
18+
"Microsoft.AspNet.Proxy": "1.0.0-rc1-final",
19+
"Microsoft.AspNet.NodeServices": "1.0.0-alpha7"
1820
},
1921
"frameworks": {
2022
"net451": { },
2123
"dotnet5.4": {
2224
"dependencies": {
2325
}
2426
}
25-
}
27+
},
28+
"resource": [
29+
"Content/**/*"
30+
]
2631
}

0 commit comments

Comments
 (0)