Skip to content

Commit 2e9a43d

Browse files
WebpackDevMiddleware should run in a separate Node instance that doesn't restart when files change (otherwise there's no point in running it at all)
1 parent 6c903f3 commit 2e9a43d

6 files changed

Lines changed: 76 additions & 24 deletions

File tree

src/Microsoft.AspNet.AngularServices/AngularPrerenderTagHelper.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ public AngularPrerenderTagHelper(IServiceProvider serviceProvider, IHttpContextA
3434
// in your startup file, but then again it might be confusing that you don't need to.
3535
if (this.nodeServices == null) {
3636
var appEnv = (IApplicationEnvironment)serviceProvider.GetService(typeof (IApplicationEnvironment));
37-
this.nodeServices = fallbackNodeServices = Configuration.CreateNodeServices(NodeHostingModel.Http, appEnv.ApplicationBasePath);
37+
this.nodeServices = fallbackNodeServices = Configuration.CreateNodeServices(new NodeServicesOptions {
38+
HostingModel = NodeHostingModel.Http,
39+
ProjectPath = appEnv.ApplicationBasePath
40+
});
3841
}
3942
}
4043

src/Microsoft.AspNet.NodeServices/Configuration.cs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,40 @@
33

44
namespace Microsoft.AspNet.NodeServices {
55
public static class Configuration {
6-
public static void AddNodeServices(this IServiceCollection serviceCollection, NodeHostingModel hostingModel = NodeHostingModel.Http) {
6+
private static string[] defaultWatchFileExtensions = new[] { ".js", ".jsx", ".ts", ".tsx", ".json", ".html" };
7+
8+
public static void AddNodeServices(this IServiceCollection serviceCollection, NodeServicesOptions options) {
79
serviceCollection.AddSingleton(typeof(INodeServices), (serviceProvider) => {
810
var appEnv = serviceProvider.GetRequiredService<IApplicationEnvironment>();
9-
return CreateNodeServices(hostingModel, appEnv.ApplicationBasePath);
11+
if (string.IsNullOrEmpty(options.ProjectPath)) {
12+
options.ProjectPath = appEnv.ApplicationBasePath;
13+
}
14+
return CreateNodeServices(options);
1015
});
1116
}
1217

13-
public static INodeServices CreateNodeServices(NodeHostingModel hostingModel, string projectPath)
18+
public static INodeServices CreateNodeServices(NodeServicesOptions options)
1419
{
15-
switch (hostingModel)
20+
var watchFileExtensions = options.WatchFileExtensions ?? defaultWatchFileExtensions;
21+
switch (options.HostingModel)
1622
{
1723
case NodeHostingModel.Http:
18-
return new HttpNodeInstance(projectPath);
24+
return new HttpNodeInstance(options.ProjectPath, /* port */ 0, watchFileExtensions);
1925
case NodeHostingModel.InputOutputStream:
20-
return new InputOutputStreamNodeInstance(projectPath);
26+
return new InputOutputStreamNodeInstance(options.ProjectPath);
2127
default:
22-
throw new System.ArgumentException("Unknown hosting model: " + hostingModel.ToString());
28+
throw new System.ArgumentException("Unknown hosting model: " + options.HostingModel.ToString());
2329
}
2430
}
2531
}
32+
33+
public class NodeServicesOptions {
34+
public NodeHostingModel HostingModel { get; set; }
35+
public string ProjectPath { get; set; }
36+
public string[] WatchFileExtensions { get; set; }
37+
38+
public NodeServicesOptions() {
39+
this.HostingModel = NodeHostingModel.Http;
40+
}
41+
}
2642
}

src/Microsoft.AspNet.NodeServices/Content/Node/entrypoint-http.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
// but simplifies things for the consumer of this module.
33
var http = require('http');
44
var path = require('path');
5-
var requestedPortOrZero = parseInt(process.argv[2]) || 0; // 0 means 'let the OS decide'
5+
var parsedArgs = parseArgs(process.argv);
6+
var requestedPortOrZero = parsedArgs.port || 0; // 0 means 'let the OS decide'
67

7-
autoQuitOnFileChange(process.cwd(), ['.js', '.jsx', '.ts', '.tsx', '.json', '.html']);
8+
if (parsedArgs.watch) {
9+
autoQuitOnFileChange(process.cwd(), parsedArgs.watch.split(','));
10+
}
811

912
var server = http.createServer(function(req, res) {
1013
readRequestBodyAsJson(req, function(bodyJson) {
@@ -75,3 +78,22 @@ function autoQuitOnFileChange(rootDir, extensions) {
7578
}
7679
});
7780
}
81+
82+
function parseArgs(args) {
83+
// Very simplistic parsing which is sufficient for the cases needed. We don't want to bring in any external
84+
// dependencies (such as an args-parsing library) to this file.
85+
var result = {};
86+
var currentKey = null;
87+
args.forEach(function(arg) {
88+
if (arg.indexOf('--') === 0) {
89+
var argName = arg.substring(2);
90+
result[argName] = undefined;
91+
currentKey = argName;
92+
} else if (currentKey) {
93+
result[currentKey] = arg;
94+
currentKey = null;
95+
}
96+
});
97+
98+
return result;
99+
}

src/Microsoft.AspNet.NodeServices/HostingModels/HttpNodeInstance.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,19 @@ internal class HttpNodeInstance : OutOfProcessNodeInstance {
1717

1818
private int _portNumber;
1919

20-
public HttpNodeInstance(string projectPath, int port = 0)
21-
: base(EmbeddedResourceReader.Read(typeof(HttpNodeInstance), "/Content/Node/entrypoint-http.js"), projectPath, port.ToString())
20+
public HttpNodeInstance(string projectPath, int port = 0, string[] watchFileExtensions = null)
21+
: base(EmbeddedResourceReader.Read(typeof(HttpNodeInstance), "/Content/Node/entrypoint-http.js"), projectPath, MakeCommandLineOptions(port, watchFileExtensions))
2222
{
2323
}
2424

25+
private static string MakeCommandLineOptions(int port, string[] watchFileExtensions) {
26+
var result = "--port " + port.ToString();
27+
if (watchFileExtensions != null && watchFileExtensions.Length > 0) {
28+
result += " --watch " + string.Join(",", watchFileExtensions);
29+
}
30+
return result;
31+
}
32+
2533
public override async Task<T> Invoke<T>(NodeInvocationInfo invocationInfo) {
2634
await this.EnsureReady();
2735

src/Microsoft.AspNet.SpaServices/Prerendering/PrerenderTagHelper.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ public PrerenderTagHelper(IServiceProvider serviceProvider, IHttpContextAccessor
3636
// in your startup file, but then again it might be confusing that you don't need to.
3737
if (this.nodeServices == null) {
3838
var appEnv = (IApplicationEnvironment)serviceProvider.GetService(typeof(IApplicationEnvironment));
39-
this.nodeServices = fallbackNodeServices = Configuration.CreateNodeServices(NodeHostingModel.Http, appEnv.ApplicationBasePath);
39+
this.nodeServices = fallbackNodeServices = Configuration.CreateNodeServices(new NodeServicesOptions {
40+
HostingModel = NodeHostingModel.Http,
41+
ProjectPath = appEnv.ApplicationBasePath
42+
});
4043
}
4144
}
4245

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,26 @@ public static class WebpackDevMiddleware
1515
const string WebpackDevMiddlewareHostname = "localhost";
1616
const string WebpackHotMiddlewareEndpoint = "/__webpack_hmr";
1717

18-
static INodeServices fallbackNodeServices; // Used only if no INodeServices was registered with DI
19-
2018
public static void UseWebpackDevMiddleware(this IApplicationBuilder appBuilder, WebpackDevMiddlewareOptions options = null) {
2119
// Validate options
2220
if (options != null) {
2321
if (options.ReactHotModuleReplacement && !options.HotModuleReplacement) {
2422
throw new ArgumentException("To enable ReactHotModuleReplacement, you must also enable HotModuleReplacement.");
2523
}
2624
}
27-
28-
// Get the NodeServices instance from DI
29-
var nodeServices = (INodeServices)appBuilder.ApplicationServices.GetService(typeof (INodeServices)) ?? fallbackNodeServices;
3025

31-
// Consider removing the following. Having it means you can get away with not putting app.AddNodeServices()
32-
// in your startup file, but then again it might be confusing that you don't need to.
26+
// Unlike other consumers of NodeServices, WebpackDevMiddleware dosen't share Node instances, nor does it
27+
// use your DI configuration. It's important for WebpackDevMiddleware to have its own private Node instance
28+
// because it must *not* restart when files change (if it did, you'd lose all the benefits of Webpack
29+
// middleware). And since this is a dev-time-only feature, it doesn't matter if the default transport isn't
30+
// as fast as some theoretical future alternative.
3331
var appEnv = (IApplicationEnvironment)appBuilder.ApplicationServices.GetService(typeof(IApplicationEnvironment));
34-
if (nodeServices == null) {
35-
nodeServices = fallbackNodeServices = Configuration.CreateNodeServices(NodeHostingModel.Http, appEnv.ApplicationBasePath);
36-
}
37-
32+
var nodeServices = Configuration.CreateNodeServices(new NodeServicesOptions {
33+
HostingModel = NodeHostingModel.Http,
34+
ProjectPath = appEnv.ApplicationBasePath,
35+
WatchFileExtensions = new string[] {} // Don't watch anything
36+
});
37+
3838
// Get a filename matching the middleware Node script
3939
var script = EmbeddedResourceReader.Read(typeof (WebpackDevMiddleware), "/Content/Node/webpack-dev-middleware.js");
4040
var nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit

0 commit comments

Comments
 (0)