From 2e9a43d1dc31052caa123c187d3c136d9b95d0e1 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Tue, 9 Feb 2016 17:26:04 -0800 Subject: [PATCH] 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) --- .../AngularPrerenderTagHelper.cs | 5 +++- .../Configuration.cs | 30 ++++++++++++++----- .../Content/Node/entrypoint-http.js | 26 ++++++++++++++-- .../HostingModels/HttpNodeInstance.cs | 12 ++++++-- .../Prerendering/PrerenderTagHelper.cs | 5 +++- .../Webpack/WebpackDevMiddleware.cs | 22 +++++++------- 6 files changed, 76 insertions(+), 24 deletions(-) diff --git a/src/Microsoft.AspNet.AngularServices/AngularPrerenderTagHelper.cs b/src/Microsoft.AspNet.AngularServices/AngularPrerenderTagHelper.cs index b5771cc..fc7c447 100644 --- a/src/Microsoft.AspNet.AngularServices/AngularPrerenderTagHelper.cs +++ b/src/Microsoft.AspNet.AngularServices/AngularPrerenderTagHelper.cs @@ -34,7 +34,10 @@ namespace Microsoft.AspNet.AngularServices // in your startup file, but then again it might be confusing that you don't need to. if (this.nodeServices == null) { var appEnv = (IApplicationEnvironment)serviceProvider.GetService(typeof (IApplicationEnvironment)); - this.nodeServices = fallbackNodeServices = Configuration.CreateNodeServices(NodeHostingModel.Http, appEnv.ApplicationBasePath); + this.nodeServices = fallbackNodeServices = Configuration.CreateNodeServices(new NodeServicesOptions { + HostingModel = NodeHostingModel.Http, + ProjectPath = appEnv.ApplicationBasePath + }); } } diff --git a/src/Microsoft.AspNet.NodeServices/Configuration.cs b/src/Microsoft.AspNet.NodeServices/Configuration.cs index 043cc2d..3967f6c 100644 --- a/src/Microsoft.AspNet.NodeServices/Configuration.cs +++ b/src/Microsoft.AspNet.NodeServices/Configuration.cs @@ -3,24 +3,40 @@ using Microsoft.Extensions.PlatformAbstractions; namespace Microsoft.AspNet.NodeServices { public static class Configuration { - public static void AddNodeServices(this IServiceCollection serviceCollection, NodeHostingModel hostingModel = NodeHostingModel.Http) { + private static string[] defaultWatchFileExtensions = new[] { ".js", ".jsx", ".ts", ".tsx", ".json", ".html" }; + + public static void AddNodeServices(this IServiceCollection serviceCollection, NodeServicesOptions options) { serviceCollection.AddSingleton(typeof(INodeServices), (serviceProvider) => { var appEnv = serviceProvider.GetRequiredService(); - return CreateNodeServices(hostingModel, appEnv.ApplicationBasePath); + if (string.IsNullOrEmpty(options.ProjectPath)) { + options.ProjectPath = appEnv.ApplicationBasePath; + } + return CreateNodeServices(options); }); } - public static INodeServices CreateNodeServices(NodeHostingModel hostingModel, string projectPath) + public static INodeServices CreateNodeServices(NodeServicesOptions options) { - switch (hostingModel) + var watchFileExtensions = options.WatchFileExtensions ?? defaultWatchFileExtensions; + switch (options.HostingModel) { case NodeHostingModel.Http: - return new HttpNodeInstance(projectPath); + return new HttpNodeInstance(options.ProjectPath, /* port */ 0, watchFileExtensions); case NodeHostingModel.InputOutputStream: - return new InputOutputStreamNodeInstance(projectPath); + return new InputOutputStreamNodeInstance(options.ProjectPath); default: - throw new System.ArgumentException("Unknown hosting model: " + hostingModel.ToString()); + throw new System.ArgumentException("Unknown hosting model: " + options.HostingModel.ToString()); } } } + + public class NodeServicesOptions { + public NodeHostingModel HostingModel { get; set; } + public string ProjectPath { get; set; } + public string[] WatchFileExtensions { get; set; } + + public NodeServicesOptions() { + this.HostingModel = NodeHostingModel.Http; + } + } } diff --git a/src/Microsoft.AspNet.NodeServices/Content/Node/entrypoint-http.js b/src/Microsoft.AspNet.NodeServices/Content/Node/entrypoint-http.js index d833644..eeedf64 100644 --- a/src/Microsoft.AspNet.NodeServices/Content/Node/entrypoint-http.js +++ b/src/Microsoft.AspNet.NodeServices/Content/Node/entrypoint-http.js @@ -2,9 +2,12 @@ // but simplifies things for the consumer of this module. var http = require('http'); var path = require('path'); -var requestedPortOrZero = parseInt(process.argv[2]) || 0; // 0 means 'let the OS decide' +var parsedArgs = parseArgs(process.argv); +var requestedPortOrZero = parsedArgs.port || 0; // 0 means 'let the OS decide' -autoQuitOnFileChange(process.cwd(), ['.js', '.jsx', '.ts', '.tsx', '.json', '.html']); +if (parsedArgs.watch) { + autoQuitOnFileChange(process.cwd(), parsedArgs.watch.split(',')); +} var server = http.createServer(function(req, res) { readRequestBodyAsJson(req, function(bodyJson) { @@ -75,3 +78,22 @@ function autoQuitOnFileChange(rootDir, extensions) { } }); } + +function parseArgs(args) { + // Very simplistic parsing which is sufficient for the cases needed. We don't want to bring in any external + // dependencies (such as an args-parsing library) to this file. + var result = {}; + var currentKey = null; + args.forEach(function(arg) { + if (arg.indexOf('--') === 0) { + var argName = arg.substring(2); + result[argName] = undefined; + currentKey = argName; + } else if (currentKey) { + result[currentKey] = arg; + currentKey = null; + } + }); + + return result; +} diff --git a/src/Microsoft.AspNet.NodeServices/HostingModels/HttpNodeInstance.cs b/src/Microsoft.AspNet.NodeServices/HostingModels/HttpNodeInstance.cs index e19d74a..1c52f13 100644 --- a/src/Microsoft.AspNet.NodeServices/HostingModels/HttpNodeInstance.cs +++ b/src/Microsoft.AspNet.NodeServices/HostingModels/HttpNodeInstance.cs @@ -17,11 +17,19 @@ namespace Microsoft.AspNet.NodeServices { private int _portNumber; - public HttpNodeInstance(string projectPath, int port = 0) - : base(EmbeddedResourceReader.Read(typeof(HttpNodeInstance), "/Content/Node/entrypoint-http.js"), projectPath, port.ToString()) + public HttpNodeInstance(string projectPath, int port = 0, string[] watchFileExtensions = null) + : base(EmbeddedResourceReader.Read(typeof(HttpNodeInstance), "/Content/Node/entrypoint-http.js"), projectPath, MakeCommandLineOptions(port, watchFileExtensions)) { } + private static string MakeCommandLineOptions(int port, string[] watchFileExtensions) { + var result = "--port " + port.ToString(); + if (watchFileExtensions != null && watchFileExtensions.Length > 0) { + result += " --watch " + string.Join(",", watchFileExtensions); + } + return result; + } + public override async Task Invoke(NodeInvocationInfo invocationInfo) { await this.EnsureReady(); diff --git a/src/Microsoft.AspNet.SpaServices/Prerendering/PrerenderTagHelper.cs b/src/Microsoft.AspNet.SpaServices/Prerendering/PrerenderTagHelper.cs index 5f3cc8b..a81c415 100644 --- a/src/Microsoft.AspNet.SpaServices/Prerendering/PrerenderTagHelper.cs +++ b/src/Microsoft.AspNet.SpaServices/Prerendering/PrerenderTagHelper.cs @@ -36,7 +36,10 @@ namespace Microsoft.AspNet.SpaServices.Prerendering // in your startup file, but then again it might be confusing that you don't need to. if (this.nodeServices == null) { var appEnv = (IApplicationEnvironment)serviceProvider.GetService(typeof(IApplicationEnvironment)); - this.nodeServices = fallbackNodeServices = Configuration.CreateNodeServices(NodeHostingModel.Http, appEnv.ApplicationBasePath); + this.nodeServices = fallbackNodeServices = Configuration.CreateNodeServices(new NodeServicesOptions { + HostingModel = NodeHostingModel.Http, + ProjectPath = appEnv.ApplicationBasePath + }); } } diff --git a/src/Microsoft.AspNet.SpaServices/Webpack/WebpackDevMiddleware.cs b/src/Microsoft.AspNet.SpaServices/Webpack/WebpackDevMiddleware.cs index 6c71800..4ff28c3 100644 --- a/src/Microsoft.AspNet.SpaServices/Webpack/WebpackDevMiddleware.cs +++ b/src/Microsoft.AspNet.SpaServices/Webpack/WebpackDevMiddleware.cs @@ -15,8 +15,6 @@ namespace Microsoft.AspNet.Builder const string WebpackDevMiddlewareHostname = "localhost"; const string WebpackHotMiddlewareEndpoint = "/__webpack_hmr"; - static INodeServices fallbackNodeServices; // Used only if no INodeServices was registered with DI - public static void UseWebpackDevMiddleware(this IApplicationBuilder appBuilder, WebpackDevMiddlewareOptions options = null) { // Validate options if (options != null) { @@ -24,17 +22,19 @@ namespace Microsoft.AspNet.Builder throw new ArgumentException("To enable ReactHotModuleReplacement, you must also enable HotModuleReplacement."); } } - - // Get the NodeServices instance from DI - var nodeServices = (INodeServices)appBuilder.ApplicationServices.GetService(typeof (INodeServices)) ?? fallbackNodeServices; - // Consider removing the following. Having it means you can get away with not putting app.AddNodeServices() - // in your startup file, but then again it might be confusing that you don't need to. + // Unlike other consumers of NodeServices, WebpackDevMiddleware dosen't share Node instances, nor does it + // use your DI configuration. It's important for WebpackDevMiddleware to have its own private Node instance + // because it must *not* restart when files change (if it did, you'd lose all the benefits of Webpack + // middleware). And since this is a dev-time-only feature, it doesn't matter if the default transport isn't + // as fast as some theoretical future alternative. var appEnv = (IApplicationEnvironment)appBuilder.ApplicationServices.GetService(typeof(IApplicationEnvironment)); - if (nodeServices == null) { - nodeServices = fallbackNodeServices = Configuration.CreateNodeServices(NodeHostingModel.Http, appEnv.ApplicationBasePath); - } - + var nodeServices = Configuration.CreateNodeServices(new NodeServicesOptions { + HostingModel = NodeHostingModel.Http, + ProjectPath = appEnv.ApplicationBasePath, + WatchFileExtensions = new string[] {} // Don't watch anything + }); + // Get a filename matching the middleware Node script var script = EmbeddedResourceReader.Read(typeof (WebpackDevMiddleware), "/Content/Node/webpack-dev-middleware.js"); var nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit