From 377401b5e6b8668f6782a210bc81cc7a766b167e Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Wed, 7 Sep 2016 14:00:09 +0100 Subject: [PATCH 001/748] Update README to clarify that you need .NET Core 1.0 RTM --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 93224181..287a5fe1 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ This repo contains: * A Yeoman generator that creates preconfigured app starting points ([guide](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/)) * Samples and docs -Everything here is cross-platform, and works with .NET Core 1.0 RC2 or later on Windows, Linux, or OS X. +Everything here is cross-platform, and works with .NET Core 1.0 (RTM) or later on Windows, Linux, or OS X. ## Creating new applications From 465d0c8d15820c95fd8fc927eacf59df9c338f1a Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Wed, 7 Sep 2016 17:11:57 +0100 Subject: [PATCH 002/748] Design review: Explicitly disable TypeNameHandling in all Json.NET usage --- .../HostingModels/HttpNodeInstance.cs | 9 +++++---- .../HostingModels/SocketNodeInstance.cs | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs index 9fd72a4d..0ab27825 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs @@ -25,9 +25,10 @@ internal class HttpNodeInstance : OutOfProcessNodeInstance private static readonly Regex PortMessageRegex = new Regex(@"^\[Microsoft.AspNetCore.NodeServices.HttpNodeHost:Listening on port (\d+)\]$"); - private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings + private static readonly JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings { - ContractResolver = new CamelCasePropertyNamesContractResolver() + ContractResolver = new CamelCasePropertyNamesContractResolver(), + TypeNameHandling = TypeNameHandling.None }; private readonly HttpClient _client; @@ -58,7 +59,7 @@ private static string MakeCommandLineOptions(int port) protected override async Task InvokeExportAsync(NodeInvocationInfo invocationInfo) { - var payloadJson = JsonConvert.SerializeObject(invocationInfo, JsonSerializerSettings); + var payloadJson = JsonConvert.SerializeObject(invocationInfo, jsonSerializerSettings); var payload = new StringContent(payloadJson, Encoding.UTF8, "application/json"); var response = await _client.PostAsync("http://localhost:" + _portNumber, payload); @@ -85,7 +86,7 @@ protected override async Task InvokeExportAsync(NodeInvocationInfo invocat case "application/json": var responseJson = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(responseJson); + return JsonConvert.DeserializeObject(responseJson, jsonSerializerSettings); case "application/octet-stream": // Streamed responses have to be received as System.IO.Stream instances diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs index 1a1c74e3..2acfb783 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs @@ -29,7 +29,8 @@ internal class SocketNodeInstance : OutOfProcessNodeInstance { private readonly static JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings { - ContractResolver = new CamelCasePropertyNamesContractResolver() + ContractResolver = new CamelCasePropertyNamesContractResolver(), + TypeNameHandling = TypeNameHandling.None }; private readonly SemaphoreSlim _connectionCreationSemaphore = new SemaphoreSlim(1); From f358d8e2b2b5913b01aade75b40a2b2e05418fd2 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Wed, 7 Sep 2016 17:59:13 +0100 Subject: [PATCH 003/748] In HttpNodeInstance, correctly report response serialisation errors back to .NET (previously, it just timed out) --- .../Content/Node/entrypoint-http.js | 23 ++++++++++++------- .../TypeScript/HttpNodeInstanceEntryPoint.ts | 23 ++++++++++++------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js index 76a00ef7..9d231f3b 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js +++ b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js @@ -69,18 +69,21 @@ if (!hasSentResult) { hasSentResult = true; if (errorValue) { - res.statusCode = 500; - if (errorValue.stack) { - res.end(errorValue.stack); - } - else { - res.end(errorValue.toString()); - } + respondWithError(res, errorValue); } else if (typeof successValue !== 'string') { // Arbitrary object/number/etc - JSON-serialize it + var successValueJson = void 0; + try { + successValueJson = JSON.stringify(successValue); + } + catch (ex) { + // JSON serialization error - pass it back to .NET + respondWithError(res, ex); + return; + } res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(successValue)); + res.end(JSON.stringify(successValueJson)); } else { // String - can bypass JSON-serialization altogether @@ -129,6 +132,10 @@ .on('data', function (chunk) { requestBodyAsString += chunk; }) .on('end', function () { callback(JSON.parse(requestBodyAsString)); }); } + function respondWithError(res, errorValue) { + res.statusCode = 500; + res.end(errorValue.stack || errorValue.toString()); + } /***/ }, diff --git a/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts b/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts index 431810e6..5bb8623d 100644 --- a/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts +++ b/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts @@ -17,17 +17,19 @@ const server = http.createServer((req, res) => { if (!hasSentResult) { hasSentResult = true; if (errorValue) { - res.statusCode = 500; - - if (errorValue.stack) { - res.end(errorValue.stack); - } else { - res.end(errorValue.toString()); - } + respondWithError(res, errorValue); } else if (typeof successValue !== 'string') { // Arbitrary object/number/etc - JSON-serialize it + let successValueJson: string; + try { + successValueJson = JSON.stringify(successValue); + } catch (ex) { + // JSON serialization error - pass it back to .NET + respondWithError(res, ex); + return; + } res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(successValue)); + res.end(JSON.stringify(successValueJson)); } else { // String - can bypass JSON-serialization altogether res.setHeader('Content-Type', 'text/plain'); @@ -82,3 +84,8 @@ function readRequestBodyAsJson(request, callback) { .on('data', chunk => { requestBodyAsString += chunk; }) .on('end', () => { callback(JSON.parse(requestBodyAsString)); }); } + +function respondWithError(res: http.ServerResponse, errorValue: any) { + res.statusCode = 500; + res.end(errorValue.stack || errorValue.toString()); +} From 279986129659faae8a110ca62cb4cf3965e0604f Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 8 Sep 2016 10:56:50 +0100 Subject: [PATCH 004/748] Support cancellation of NodeServices invocations --- .../HostingModels/HttpNodeInstance.cs | 15 ++++++---- .../HostingModels/INodeInstance.cs | 3 +- .../HostingModels/OutOfProcessNodeInstance.cs | 16 ++++++---- .../HostingModels/SocketNodeInstance.cs | 25 +++++++++------- .../INodeServices.cs | 4 +++ .../NodeServicesImpl.cs | 19 +++++++++--- .../Util/TaskExtensions.cs | 30 +++++++++++++++++++ 7 files changed, 86 insertions(+), 26 deletions(-) create mode 100644 src/Microsoft.AspNetCore.NodeServices/Util/TaskExtensions.cs diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs index 0ab27825..0c113718 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs @@ -4,6 +4,7 @@ using System.Net.Http; using System.Text; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -57,15 +58,17 @@ private static string MakeCommandLineOptions(int port) return $"--port {port}"; } - protected override async Task InvokeExportAsync(NodeInvocationInfo invocationInfo) + protected override async Task InvokeExportAsync( + NodeInvocationInfo invocationInfo, CancellationToken cancellationToken) { var payloadJson = JsonConvert.SerializeObject(invocationInfo, jsonSerializerSettings); var payload = new StringContent(payloadJson, Encoding.UTF8, "application/json"); - var response = await _client.PostAsync("http://localhost:" + _portNumber, payload); + var response = await _client.PostAsync("http://localhost:" + _portNumber, payload, cancellationToken); if (!response.IsSuccessStatusCode) { - var responseErrorString = await response.Content.ReadAsStringAsync(); + // Unfortunately there's no true way to cancel ReadAsStringAsync calls, hence AbandonIfCancelled + var responseErrorString = await response.Content.ReadAsStringAsync().OrThrowOnCancellation(cancellationToken); throw new Exception("Call to Node module failed with error: " + responseErrorString); } @@ -81,11 +84,11 @@ protected override async Task InvokeExportAsync(NodeInvocationInfo invocat typeof(T).FullName); } - var responseString = await response.Content.ReadAsStringAsync(); + var responseString = await response.Content.ReadAsStringAsync().OrThrowOnCancellation(cancellationToken); return (T)(object)responseString; case "application/json": - var responseJson = await response.Content.ReadAsStringAsync(); + var responseJson = await response.Content.ReadAsStringAsync().OrThrowOnCancellation(cancellationToken); return JsonConvert.DeserializeObject(responseJson, jsonSerializerSettings); case "application/octet-stream": @@ -97,7 +100,7 @@ protected override async Task InvokeExportAsync(NodeInvocationInfo invocat typeof(T).FullName + ". Instead you must use the generic type System.IO.Stream."); } - return (T)(object)(await response.Content.ReadAsStreamAsync()); + return (T)(object)(await response.Content.ReadAsStreamAsync().OrThrowOnCancellation(cancellationToken)); default: throw new InvalidOperationException("Unexpected response content type: " + responseContentType.MediaType); diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/INodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/INodeInstance.cs index cac69f2a..68a43195 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/INodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/INodeInstance.cs @@ -1,10 +1,11 @@ using System; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.AspNetCore.NodeServices.HostingModels { public interface INodeInstance : IDisposable { - Task InvokeExportAsync(string moduleName, string exportNameOrNull, params object[] args); + Task InvokeExportAsync(CancellationToken cancellationToken, string moduleName, string exportNameOrNull, params object[] args); } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs index 641c098d..9a583d3a 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -67,7 +68,8 @@ public OutOfProcessNodeInstance( ConnectToInputOutputStreams(); } - public async Task InvokeExportAsync(string moduleName, string exportNameOrNull, params object[] args) + public async Task InvokeExportAsync( + CancellationToken cancellationToken, string moduleName, string exportNameOrNull, params object[] args) { if (_nodeProcess.HasExited || _nodeProcessNeedsRestart) { @@ -79,15 +81,17 @@ public async Task InvokeExportAsync(string moduleName, string exportNameOr throw new NodeInvocationException(message, null, nodeInstanceUnavailable: true); } - // Wait until the connection is established. This will throw if the connection fails to initialize. - await _connectionIsReadySource.Task; + // Wait until the connection is established. This will throw if the connection fails to initialize, + // or if cancellation is requested first. Note that we can't really cancel the "establishing connection" + // task because that's shared with all callers, but we can stop waiting for it if this call is cancelled. + await _connectionIsReadySource.Task.OrThrowOnCancellation(cancellationToken); return await InvokeExportAsync(new NodeInvocationInfo { ModuleName = moduleName, ExportedFunctionName = exportNameOrNull, Args = args - }); + }, cancellationToken); } public void Dispose() @@ -96,7 +100,9 @@ public void Dispose() GC.SuppressFinalize(this); } - protected abstract Task InvokeExportAsync(NodeInvocationInfo invocationInfo); + protected abstract Task InvokeExportAsync( + NodeInvocationInfo invocationInfo, + CancellationToken cancellationToken); // This method is virtual, as it provides a way to override the NODE_PATH or the path to node.exe protected virtual ProcessStartInfo PrepareNodeProcessStartInfo( diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs index 2acfb783..2acfc0ed 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs @@ -57,7 +57,7 @@ public SocketNodeInstance(string projectPath, string[] watchFileExtensions, stri _socketAddress = socketAddress; } - protected override async Task InvokeExportAsync(NodeInvocationInfo invocationInfo) + protected override async Task InvokeExportAsync(NodeInvocationInfo invocationInfo, CancellationToken cancellationToken) { if (_connectionHasFailed) { @@ -70,7 +70,12 @@ protected override async Task InvokeExportAsync(NodeInvocationInfo invocat if (_virtualConnectionClient == null) { - await EnsureVirtualConnectionClientCreated(); + // Although we could pass the cancellationToken into EnsureVirtualConnectionClientCreated and + // have it signal cancellations upstream, that would be a bad thing to do, because all callers + // wait for the same connection task. There's no reason why the first caller should have the + // special ability to cancel the connection process in a way that would affect subsequent + // callers. So, each caller just independently stops awaiting connection if that call is cancelled. + await EnsureVirtualConnectionClientCreated().OrThrowOnCancellation(cancellationToken); } // For each invocation, we open a new virtual connection. This gives an API equivalent to opening a new @@ -83,7 +88,7 @@ protected override async Task InvokeExportAsync(NodeInvocationInfo invocat virtualConnection = _virtualConnectionClient.OpenVirtualConnection(); // Send request - await WriteJsonLineAsync(virtualConnection, invocationInfo); + await WriteJsonLineAsync(virtualConnection, invocationInfo, cancellationToken); // Determine what kind of response format is expected if (typeof(T) == typeof(Stream)) @@ -96,7 +101,7 @@ protected override async Task InvokeExportAsync(NodeInvocationInfo invocat else { // Parse and return non-streamed JSON response - var response = await ReadJsonAsync>(virtualConnection); + var response = await ReadJsonAsync>(virtualConnection, cancellationToken); if (response.ErrorMessage != null) { throw new NodeInvocationException(response.ErrorMessage, response.ErrorDetails); @@ -163,27 +168,27 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - private static async Task WriteJsonLineAsync(Stream stream, object serializableObject) + private static async Task WriteJsonLineAsync(Stream stream, object serializableObject, CancellationToken cancellationToken) { var json = JsonConvert.SerializeObject(serializableObject, jsonSerializerSettings); var bytes = Encoding.UTF8.GetBytes(json + '\n'); - await stream.WriteAsync(bytes, 0, bytes.Length); + await stream.WriteAsync(bytes, 0, bytes.Length, cancellationToken); } - private static async Task ReadJsonAsync(Stream stream) + private static async Task ReadJsonAsync(Stream stream, CancellationToken cancellationToken) { - var json = Encoding.UTF8.GetString(await ReadAllBytesAsync(stream)); + var json = Encoding.UTF8.GetString(await ReadAllBytesAsync(stream, cancellationToken)); return JsonConvert.DeserializeObject(json, jsonSerializerSettings); } - private static async Task ReadAllBytesAsync(Stream input) + private static async Task ReadAllBytesAsync(Stream input, CancellationToken cancellationToken) { byte[] buffer = new byte[16 * 1024]; using (var ms = new MemoryStream()) { int read; - while ((read = await input.ReadAsync(buffer, 0, buffer.Length)) > 0) + while ((read = await input.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0) { ms.Write(buffer, 0, read); } diff --git a/src/Microsoft.AspNetCore.NodeServices/INodeServices.cs b/src/Microsoft.AspNetCore.NodeServices/INodeServices.cs index 3aa09e3b..fbc32d04 100644 --- a/src/Microsoft.AspNetCore.NodeServices/INodeServices.cs +++ b/src/Microsoft.AspNetCore.NodeServices/INodeServices.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.AspNetCore.NodeServices @@ -6,8 +7,11 @@ namespace Microsoft.AspNetCore.NodeServices public interface INodeServices : IDisposable { Task InvokeAsync(string moduleName, params object[] args); + Task InvokeAsync(CancellationToken cancellationToken, string moduleName, params object[] args); Task InvokeExportAsync(string moduleName, string exportedFunctionName, params object[] args); + Task InvokeExportAsync(CancellationToken cancellationToken, string moduleName, string exportedFunctionName, params object[] args); + [Obsolete("Use InvokeAsync instead")] Task Invoke(string moduleName, params object[] args); diff --git a/src/Microsoft.AspNetCore.NodeServices/NodeServicesImpl.cs b/src/Microsoft.AspNetCore.NodeServices/NodeServicesImpl.cs index 037cc96d..38d30558 100644 --- a/src/Microsoft.AspNetCore.NodeServices/NodeServicesImpl.cs +++ b/src/Microsoft.AspNetCore.NodeServices/NodeServicesImpl.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.NodeServices.HostingModels; @@ -34,19 +35,29 @@ public Task InvokeAsync(string moduleName, params object[] args) return InvokeExportAsync(moduleName, null, args); } + public Task InvokeAsync(CancellationToken cancellationToken, string moduleName, params object[] args) + { + return InvokeExportAsync(cancellationToken, moduleName, null, args); + } + public Task InvokeExportAsync(string moduleName, string exportedFunctionName, params object[] args) { - return InvokeExportWithPossibleRetryAsync(moduleName, exportedFunctionName, args, allowRetry: true); + return InvokeExportWithPossibleRetryAsync(moduleName, exportedFunctionName, args, /* allowRetry */ true, CancellationToken.None); + } + + public Task InvokeExportAsync(CancellationToken cancellationToken, string moduleName, string exportedFunctionName, params object[] args) + { + return InvokeExportWithPossibleRetryAsync(moduleName, exportedFunctionName, args, /* allowRetry */ true, cancellationToken); } - public async Task InvokeExportWithPossibleRetryAsync(string moduleName, string exportedFunctionName, object[] args, bool allowRetry) + public async Task InvokeExportWithPossibleRetryAsync(string moduleName, string exportedFunctionName, object[] args, bool allowRetry, CancellationToken cancellationToken) { ThrowAnyOutstandingDelayedDisposalException(); var nodeInstance = GetOrCreateCurrentNodeInstance(); try { - return await nodeInstance.InvokeExportAsync(moduleName, exportedFunctionName, args); + return await nodeInstance.InvokeExportAsync(cancellationToken, moduleName, exportedFunctionName, args); } catch (NodeInvocationException ex) { @@ -69,7 +80,7 @@ public async Task InvokeExportWithPossibleRetryAsync(string moduleName, st // One the next call, don't allow retries, because we could get into an infinite retry loop, or a long retry // loop that masks an underlying problem. A newly-created Node instance should be able to accept invocations, // or something more serious must be wrong. - return await InvokeExportWithPossibleRetryAsync(moduleName, exportedFunctionName, args, allowRetry: false); + return await InvokeExportWithPossibleRetryAsync(moduleName, exportedFunctionName, args, /* allowRetry */ false, cancellationToken); } else { diff --git a/src/Microsoft.AspNetCore.NodeServices/Util/TaskExtensions.cs b/src/Microsoft.AspNetCore.NodeServices/Util/TaskExtensions.cs new file mode 100644 index 00000000..75cfdb16 --- /dev/null +++ b/src/Microsoft.AspNetCore.NodeServices/Util/TaskExtensions.cs @@ -0,0 +1,30 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.NodeServices +{ + internal static class TaskExtensions + { + public static Task OrThrowOnCancellation(this Task task, CancellationToken cancellationToken) + { + return task.IsCompleted + ? task // If the task is already completed, no need to wrap it in a further layer of task + : task.ContinueWith( + _ => {}, // If the task completes, allow execution to continue + cancellationToken, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + } + + public static Task OrThrowOnCancellation(this Task task, CancellationToken cancellationToken) + { + return task.IsCompleted + ? task // If the task is already completed, no need to wrap it in a further layer of task + : task.ContinueWith( + t => t.Result, // If the task completes, pass through its result + cancellationToken, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + } + } +} \ No newline at end of file From 041d173f56692a0b8e624a1db34c4560fe14e6df Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 8 Sep 2016 12:08:42 +0100 Subject: [PATCH 005/748] All NodeServices invocations now have a default timeout, plus a descriptive exception if that happens --- .../Configuration/NodeServicesFactory.cs | 4 +- .../Configuration/NodeServicesOptions.cs | 4 + .../HostingModels/HttpNodeInstance.cs | 4 +- .../HostingModels/OutOfProcessNodeInstance.cs | 80 ++++++++++++++++--- .../HostingModels/SocketNodeInstance.cs | 3 +- 5 files changed, 81 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesFactory.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesFactory.cs index fc038f2c..d6660c0d 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesFactory.cs +++ b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesFactory.cs @@ -29,11 +29,11 @@ private static INodeInstance CreateNodeInstance(NodeServicesOptions options) { case NodeHostingModel.Http: return new HttpNodeInstance(options.ProjectPath, options.WatchFileExtensions, options.NodeInstanceOutputLogger, - options.EnvironmentVariables, options.LaunchWithDebugging, options.DebuggingPort, /* port */ 0); + options.EnvironmentVariables, options.InvocationTimeoutMilliseconds, options.LaunchWithDebugging, options.DebuggingPort, /* port */ 0); case NodeHostingModel.Socket: var pipeName = "pni-" + Guid.NewGuid().ToString("D"); // Arbitrary non-clashing string return new SocketNodeInstance(options.ProjectPath, options.WatchFileExtensions, pipeName, options.NodeInstanceOutputLogger, - options.EnvironmentVariables, options.LaunchWithDebugging, options.DebuggingPort); + options.EnvironmentVariables, options.InvocationTimeoutMilliseconds, options.LaunchWithDebugging, options.DebuggingPort); default: throw new ArgumentException("Unknown hosting model: " + options.HostingModel); } diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs index edf9ae2c..dbf1ce60 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs +++ b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs @@ -11,6 +11,8 @@ namespace Microsoft.AspNetCore.NodeServices public class NodeServicesOptions { public const NodeHostingModel DefaultNodeHostingModel = NodeHostingModel.Http; + internal const string TimeoutConfigPropertyName = nameof(InvocationTimeoutMilliseconds); + private const int DefaultInvocationTimeoutMilliseconds = 60 * 1000; private const string LogCategoryName = "Microsoft.AspNetCore.NodeServices"; private static readonly string[] DefaultWatchFileExtensions = { ".js", ".jsx", ".ts", ".tsx", ".json", ".html" }; @@ -22,6 +24,7 @@ public NodeServicesOptions(IServiceProvider serviceProvider) } EnvironmentVariables = new Dictionary(); + InvocationTimeoutMilliseconds = DefaultInvocationTimeoutMilliseconds; HostingModel = DefaultNodeHostingModel; WatchFileExtensions = (string[])DefaultWatchFileExtensions.Clone(); @@ -49,5 +52,6 @@ public NodeServicesOptions(IServiceProvider serviceProvider) public bool LaunchWithDebugging { get; set; } public IDictionary EnvironmentVariables { get; set; } public int DebuggingPort { get; set; } + public int InvocationTimeoutMilliseconds { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs index 0c113718..1ab4c137 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs @@ -37,7 +37,8 @@ internal class HttpNodeInstance : OutOfProcessNodeInstance private int _portNumber; public HttpNodeInstance(string projectPath, string[] watchFileExtensions, ILogger nodeInstanceOutputLogger, - IDictionary environmentVars, bool launchWithDebugging, int debuggingPort, int port = 0) + IDictionary environmentVars, int invocationTimeoutMilliseconds, bool launchWithDebugging, + int debuggingPort, int port = 0) : base( EmbeddedResourceReader.Read( typeof(HttpNodeInstance), @@ -47,6 +48,7 @@ public HttpNodeInstance(string projectPath, string[] watchFileExtensions, ILogge MakeCommandLineOptions(port), nodeInstanceOutputLogger, environmentVars, + invocationTimeoutMilliseconds, launchWithDebugging, debuggingPort) { diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs index 9a583d3a..3957cb08 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs @@ -37,6 +37,7 @@ npm install -g node-inspector private bool _disposed; private readonly StringAsTempFile _entryPointScript; private FileSystemWatcher _fileSystemWatcher; + private int _invocationTimeoutMilliseconds; private readonly Process _nodeProcess; private int? _nodeDebuggingPort; private bool _nodeProcessNeedsRestart; @@ -49,6 +50,7 @@ public OutOfProcessNodeInstance( string commandLineArguments, ILogger nodeOutputLogger, IDictionary environmentVars, + int invocationTimeoutMilliseconds, bool launchWithDebugging, int debuggingPort) { @@ -59,6 +61,7 @@ public OutOfProcessNodeInstance( OutputLogger = nodeOutputLogger; _entryPointScript = new StringAsTempFile(entryPointScript); + _invocationTimeoutMilliseconds = invocationTimeoutMilliseconds; var startInfo = PrepareNodeProcessStartInfo(_entryPointScript.FileName, projectPath, commandLineArguments, environmentVars, launchWithDebugging, debuggingPort); @@ -81,17 +84,74 @@ public async Task InvokeExportAsync( throw new NodeInvocationException(message, null, nodeInstanceUnavailable: true); } - // Wait until the connection is established. This will throw if the connection fails to initialize, - // or if cancellation is requested first. Note that we can't really cancel the "establishing connection" - // task because that's shared with all callers, but we can stop waiting for it if this call is cancelled. - await _connectionIsReadySource.Task.OrThrowOnCancellation(cancellationToken); - - return await InvokeExportAsync(new NodeInvocationInfo + // Construct a new cancellation token that combines the supplied token with the configured invocation + // timeout. Technically we could avoid wrapping the cancellationToken if no timeout is configured, + // but that's not really a major use case, since timeouts are enabled by default. + using (var timeoutSource = new CancellationTokenSource()) + using (var combinedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutSource.Token)) { - ModuleName = moduleName, - ExportedFunctionName = exportNameOrNull, - Args = args - }, cancellationToken); + if (_invocationTimeoutMilliseconds > 0) + { + timeoutSource.CancelAfter(_invocationTimeoutMilliseconds); + } + + // By overwriting the supplied cancellation token, we ensure that it isn't accidentally used + // below. We only want to pass through the token that respects timeouts. + cancellationToken = combinedCancellationTokenSource.Token; + var connectionDidSucceed = false; + + try + { + // Wait until the connection is established. This will throw if the connection fails to initialize, + // or if cancellation is requested first. Note that we can't really cancel the "establishing connection" + // task because that's shared with all callers, but we can stop waiting for it if this call is cancelled. + await _connectionIsReadySource.Task.OrThrowOnCancellation(cancellationToken); + connectionDidSucceed = true; + + return await InvokeExportAsync(new NodeInvocationInfo + { + ModuleName = moduleName, + ExportedFunctionName = exportNameOrNull, + Args = args + }, cancellationToken); + } + catch (TaskCanceledException) + { + if (timeoutSource.IsCancellationRequested) + { + // It was very common for developers to report 'TaskCanceledException' when encountering almost any + // trouble when using NodeServices. Now we have a default invocation timeout, and attempt to give + // a more descriptive exception message if it happens. + if (!connectionDidSucceed) + { + // This is very unlikely, but for debugging, it's still useful to differentiate it from the + // case below. + throw new NodeInvocationException( + $"Attempt to connect to Node timed out after {_invocationTimeoutMilliseconds}ms.", + string.Empty); + } + else + { + // Developers encounter this fairly often (if their Node code fails without invoking the callback, + // all that the .NET side knows is that the invocation eventually times out). Previously, this surfaced + // as a TaskCanceledException, but this led to a lot of issue reports. Now we throw the following + // descriptive error. + throw new NodeInvocationException( + $"The Node invocation timed out after {_invocationTimeoutMilliseconds}ms.", + $"You can change the timeout duration by setting the {NodeServicesOptions.TimeoutConfigPropertyName} " + + $"property on {nameof(NodeServicesOptions)}.\n\n" + + "The first debugging step is to ensure that your Node.js function always invokes the supplied " + + "callback (or throws an exception synchronously), even if it encounters an error. Otherwise, " + + "the .NET code has no way to know that it is finished or has failed." + ); + } + } + else + { + throw; + } + } + } } public void Dispose() diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs index 2acfc0ed..2ee2aa4e 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs @@ -41,7 +41,7 @@ internal class SocketNodeInstance : OutOfProcessNodeInstance public SocketNodeInstance(string projectPath, string[] watchFileExtensions, string socketAddress, ILogger nodeInstanceOutputLogger, IDictionary environmentVars, - bool launchWithDebugging, int debuggingPort) + int invocationTimeoutMilliseconds, bool launchWithDebugging, int debuggingPort) : base( EmbeddedResourceReader.Read( typeof(SocketNodeInstance), @@ -51,6 +51,7 @@ public SocketNodeInstance(string projectPath, string[] watchFileExtensions, stri MakeNewCommandLineOptions(socketAddress), nodeInstanceOutputLogger, environmentVars, + invocationTimeoutMilliseconds, launchWithDebugging, debuggingPort) { From 411100478a31bb9640afb339343575bed8184fc6 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 8 Sep 2016 12:14:45 +0100 Subject: [PATCH 006/748] Fix double-encoding typo --- .../Content/Node/entrypoint-http.js | 2 +- .../TypeScript/HttpNodeInstanceEntryPoint.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js index 9d231f3b..cfa15bcf 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js +++ b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js @@ -83,7 +83,7 @@ return; } res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(successValueJson)); + res.end(successValueJson); } else { // String - can bypass JSON-serialization altogether diff --git a/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts b/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts index 5bb8623d..f8e619d2 100644 --- a/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts +++ b/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts @@ -29,7 +29,7 @@ const server = http.createServer((req, res) => { return; } res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(successValueJson)); + res.end(successValueJson); } else { // String - can bypass JSON-serialization altogether res.setHeader('Content-Type', 'text/plain'); From 4ca1669db130970b7ce9f05d8e71de4e28e5a4e0 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 8 Sep 2016 12:56:05 +0100 Subject: [PATCH 007/748] Prerendering imposes its own (overridable) timeout with descriptive error --- .../Prerendering/PrerenderTagHelper.cs | 7 +++- .../Prerendering/Prerenderer.cs | 6 ++- .../aspnet-prerendering/src/Prerendering.ts | 39 ++++++++++++++++++- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs b/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs index b91c2a6a..de02c90f 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs @@ -18,6 +18,7 @@ public class PrerenderTagHelper : TagHelper private const string PrerenderExportAttributeName = "asp-prerender-export"; private const string PrerenderWebpackConfigAttributeName = "asp-prerender-webpack-config"; private const string PrerenderDataAttributeName = "asp-prerender-data"; + private const string PrerenderTimeoutAttributeName = "asp-prerender-timeout"; private static INodeServices _fallbackNodeServices; // Used only if no INodeServices was registered with DI private readonly string _applicationBasePath; @@ -50,6 +51,9 @@ public PrerenderTagHelper(IServiceProvider serviceProvider) [HtmlAttributeName(PrerenderDataAttributeName)] public object CustomDataParameter { get; set; } + [HtmlAttributeName(PrerenderTimeoutAttributeName)] + public int TimeoutMillisecondsParameter { get; set; } + [HtmlAttributeNotBound] [ViewContext] public ViewContext ViewContext { get; set; } @@ -79,7 +83,8 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu }, unencodedAbsoluteUrl, unencodedPathAndQuery, - CustomDataParameter); + CustomDataParameter, + TimeoutMillisecondsParameter); output.Content.SetHtmlContent(result.Html); // Also attach any specified globals to the 'window' object. This is useful for transferring diff --git a/src/Microsoft.AspNetCore.SpaServices/Prerendering/Prerenderer.cs b/src/Microsoft.AspNetCore.SpaServices/Prerendering/Prerenderer.cs index 40286d14..9d8724e4 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Prerendering/Prerenderer.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Prerendering/Prerenderer.cs @@ -23,7 +23,8 @@ public static Task RenderToString( JavaScriptModuleExport bootModule, string requestAbsoluteUrl, string requestPathAndQuery, - object customDataParameter) + object customDataParameter, + int timeoutMilliseconds) { return nodeServices.InvokeExportAsync( NodeScript.Value.FileName, @@ -32,7 +33,8 @@ public static Task RenderToString( bootModule, requestAbsoluteUrl, requestPathAndQuery, - customDataParameter); + customDataParameter, + timeoutMilliseconds); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts index 4d9269b2..3a14f94f 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts @@ -5,6 +5,8 @@ import * as domain from 'domain'; import { run as domainTaskRun } from 'domain-task/main'; import { baseUrl } from 'domain-task/fetch'; +const defaultTimeoutMilliseconds = 30 * 1000; + export interface RenderToStringCallback { (error: any, result: RenderToStringResult): void; } @@ -33,7 +35,7 @@ export interface BootModuleInfo { webpackConfig?: string; } -export function renderToString(callback: RenderToStringCallback, applicationBasePath: string, bootModule: BootModuleInfo, absoluteRequestUrl: string, requestPathAndQuery: string, customDataParameter: any) { +export function renderToString(callback: RenderToStringCallback, applicationBasePath: string, bootModule: BootModuleInfo, absoluteRequestUrl: string, requestPathAndQuery: string, customDataParameter: any, overrideTimeoutMilliseconds: number) { findBootFunc(applicationBasePath, bootModule, (findBootFuncError, bootFunc) => { if (findBootFuncError) { callback(findBootFuncError, null); @@ -66,8 +68,22 @@ export function renderToString(callback: RenderToStringCallback, applicationBase // Make the base URL available to the 'domain-tasks/fetch' helper within this execution context baseUrl(absoluteRequestUrl); + // Begin rendering, and apply a timeout + const bootFuncPromise = bootFunc(params); + if (!bootFuncPromise || typeof bootFuncPromise.then !== 'function') { + callback(`Prerendering failed because the boot function in ${bootModule.moduleName} did not return a promise.`, null); + return; + } + const timeoutMilliseconds = overrideTimeoutMilliseconds || defaultTimeoutMilliseconds; // e.g., pass -1 to override as 'never time out' + const bootFuncPromiseWithTimeout = timeoutMilliseconds > 0 + ? wrapWithTimeout(bootFuncPromise, timeoutMilliseconds, + `Prerendering timed out after ${timeoutMilliseconds}ms because the boot function in '${bootModule.moduleName}' ` + + 'returned a promise that did not resolve or reject. Make sure that your boot function always resolves or ' + + 'rejects its promise. You can change the timeout value using the \'asp-prerender-timeout\' tag helper.') + : bootFuncPromise; + // Actually perform the rendering - bootFunc(params).then(successResult => { + bootFuncPromiseWithTimeout.then(successResult => { callback(null, { html: successResult.html, globals: successResult.globals }); }, error => { callback(error, null); @@ -84,6 +100,25 @@ export function renderToString(callback: RenderToStringCallback, applicationBase }); } +function wrapWithTimeout(promise: Promise, timeoutMilliseconds: number, timeoutRejectionValue: any): Promise { + return new Promise((resolve, reject) => { + const timeoutTimer = setTimeout(() => { + reject(timeoutRejectionValue); + }, timeoutMilliseconds); + + promise.then( + resolvedValue => { + clearTimeout(timeoutTimer); + resolve(resolvedValue); + }, + rejectedValue => { + clearTimeout(timeoutTimer); + reject(rejectedValue); + } + ) + }); +} + function findBootModule(applicationBasePath: string, bootModule: BootModuleInfo, callback: (error: any, foundModule: T) => void) { const bootModuleNameFullPath = path.resolve(applicationBasePath, bootModule.moduleName); if (bootModule.webpackConfig) { From 1f2168949d54ea3cdb7573fecff7a7dc0932dbfc Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 8 Sep 2016 12:56:46 +0100 Subject: [PATCH 008/748] Publish updated aspnet-prerendering NPM package --- .../npm/aspnet-prerendering/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json index 156a6f1e..4fc59a56 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json @@ -1,6 +1,6 @@ { "name": "aspnet-prerendering", - "version": "1.0.4", + "version": "1.0.5", "description": "Helpers for server-side rendering of JavaScript applications in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "main": "index.js", "scripts": { From 5fcce843bad20262d91bf9be31544382e17eb920 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 8 Sep 2016 14:32:25 +0100 Subject: [PATCH 009/748] Workaround to fix #235 (add placeholder inside node_modules) --- .gitignore | 9 ++++++++- templates/Angular2Spa/node_modules/_placeholder.txt | 7 +++++++ templates/Angular2Spa/template_gitignore | 5 ++++- templates/KnockoutSpa/node_modules/_placeholder.txt | 7 +++++++ templates/KnockoutSpa/template_gitignore | 5 ++++- templates/ReactReduxSpa/node_modules/_placeholder.txt | 7 +++++++ templates/ReactReduxSpa/template_gitignore | 5 ++++- templates/ReactSpa/node_modules/_placeholder.txt | 7 +++++++ templates/ReactSpa/template_gitignore | 5 ++++- .../WebApplicationBasic/node_modules/_placeholder.txt | 7 +++++++ templates/WebApplicationBasic/template_gitignore | 5 ++++- 11 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 templates/Angular2Spa/node_modules/_placeholder.txt create mode 100644 templates/KnockoutSpa/node_modules/_placeholder.txt create mode 100644 templates/ReactReduxSpa/node_modules/_placeholder.txt create mode 100644 templates/ReactSpa/node_modules/_placeholder.txt create mode 100644 templates/WebApplicationBasic/node_modules/_placeholder.txt diff --git a/.gitignore b/.gitignore index 06cc11a3..6df4b42c 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,13 @@ npm-debug.log # files with that name (https://github.com/npm/npm/issues/1862). So, each template instead has a template_gitignore # file which gets renamed after the files are copied. And so any files that need to be excluded in the source # repo have to be excluded here. -/templates/*/node_modules/ + +# Note that we need to exclude node_modules/** (i.e., subdirs, not the whole of node_modules) because we do need to +# include the _placeholder.txt files, and can't do that using gitignore exclusion because developers aren't promoted to +# commit files included that way. This is all a workaround for Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 +/templates/*/node_modules/** /templates/*/wwwroot/dist/ .vscode/ + +# Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 +!/templates/*/node_modules/_placeholder.txt diff --git a/templates/Angular2Spa/node_modules/_placeholder.txt b/templates/Angular2Spa/node_modules/_placeholder.txt new file mode 100644 index 00000000..09ae8c0c --- /dev/null +++ b/templates/Angular2Spa/node_modules/_placeholder.txt @@ -0,0 +1,7 @@ +This file exists as a workaround for https://github.com/dotnet/cli/issues/1396 +('dotnet publish' does not publish any directories that didn't exist or were +empty before the publish script started, which means it's not enough just to +run 'npm install' during publishing: you need to ensure node_modules already +existed at least). + +Hopefully, this can be removed after the move to the new MSBuild. diff --git a/templates/Angular2Spa/template_gitignore b/templates/Angular2Spa/template_gitignore index d410b8bb..2ee8e59c 100644 --- a/templates/Angular2Spa/template_gitignore +++ b/templates/Angular2Spa/template_gitignore @@ -184,9 +184,12 @@ ClientBin/ *.dbproj.schemaview *.pfx *.publishsettings -node_modules/ orleans.codegen.cs +# Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 +/node_modules/** +!/node_modules/_placeholder.txt + # RIA/Silverlight projects Generated_Code/ diff --git a/templates/KnockoutSpa/node_modules/_placeholder.txt b/templates/KnockoutSpa/node_modules/_placeholder.txt new file mode 100644 index 00000000..09ae8c0c --- /dev/null +++ b/templates/KnockoutSpa/node_modules/_placeholder.txt @@ -0,0 +1,7 @@ +This file exists as a workaround for https://github.com/dotnet/cli/issues/1396 +('dotnet publish' does not publish any directories that didn't exist or were +empty before the publish script started, which means it's not enough just to +run 'npm install' during publishing: you need to ensure node_modules already +existed at least). + +Hopefully, this can be removed after the move to the new MSBuild. diff --git a/templates/KnockoutSpa/template_gitignore b/templates/KnockoutSpa/template_gitignore index d410b8bb..2ee8e59c 100644 --- a/templates/KnockoutSpa/template_gitignore +++ b/templates/KnockoutSpa/template_gitignore @@ -184,9 +184,12 @@ ClientBin/ *.dbproj.schemaview *.pfx *.publishsettings -node_modules/ orleans.codegen.cs +# Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 +/node_modules/** +!/node_modules/_placeholder.txt + # RIA/Silverlight projects Generated_Code/ diff --git a/templates/ReactReduxSpa/node_modules/_placeholder.txt b/templates/ReactReduxSpa/node_modules/_placeholder.txt new file mode 100644 index 00000000..09ae8c0c --- /dev/null +++ b/templates/ReactReduxSpa/node_modules/_placeholder.txt @@ -0,0 +1,7 @@ +This file exists as a workaround for https://github.com/dotnet/cli/issues/1396 +('dotnet publish' does not publish any directories that didn't exist or were +empty before the publish script started, which means it's not enough just to +run 'npm install' during publishing: you need to ensure node_modules already +existed at least). + +Hopefully, this can be removed after the move to the new MSBuild. diff --git a/templates/ReactReduxSpa/template_gitignore b/templates/ReactReduxSpa/template_gitignore index d410b8bb..2ee8e59c 100644 --- a/templates/ReactReduxSpa/template_gitignore +++ b/templates/ReactReduxSpa/template_gitignore @@ -184,9 +184,12 @@ ClientBin/ *.dbproj.schemaview *.pfx *.publishsettings -node_modules/ orleans.codegen.cs +# Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 +/node_modules/** +!/node_modules/_placeholder.txt + # RIA/Silverlight projects Generated_Code/ diff --git a/templates/ReactSpa/node_modules/_placeholder.txt b/templates/ReactSpa/node_modules/_placeholder.txt new file mode 100644 index 00000000..09ae8c0c --- /dev/null +++ b/templates/ReactSpa/node_modules/_placeholder.txt @@ -0,0 +1,7 @@ +This file exists as a workaround for https://github.com/dotnet/cli/issues/1396 +('dotnet publish' does not publish any directories that didn't exist or were +empty before the publish script started, which means it's not enough just to +run 'npm install' during publishing: you need to ensure node_modules already +existed at least). + +Hopefully, this can be removed after the move to the new MSBuild. diff --git a/templates/ReactSpa/template_gitignore b/templates/ReactSpa/template_gitignore index d410b8bb..2ee8e59c 100644 --- a/templates/ReactSpa/template_gitignore +++ b/templates/ReactSpa/template_gitignore @@ -184,9 +184,12 @@ ClientBin/ *.dbproj.schemaview *.pfx *.publishsettings -node_modules/ orleans.codegen.cs +# Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 +/node_modules/** +!/node_modules/_placeholder.txt + # RIA/Silverlight projects Generated_Code/ diff --git a/templates/WebApplicationBasic/node_modules/_placeholder.txt b/templates/WebApplicationBasic/node_modules/_placeholder.txt new file mode 100644 index 00000000..09ae8c0c --- /dev/null +++ b/templates/WebApplicationBasic/node_modules/_placeholder.txt @@ -0,0 +1,7 @@ +This file exists as a workaround for https://github.com/dotnet/cli/issues/1396 +('dotnet publish' does not publish any directories that didn't exist or were +empty before the publish script started, which means it's not enough just to +run 'npm install' during publishing: you need to ensure node_modules already +existed at least). + +Hopefully, this can be removed after the move to the new MSBuild. diff --git a/templates/WebApplicationBasic/template_gitignore b/templates/WebApplicationBasic/template_gitignore index d410b8bb..2ee8e59c 100644 --- a/templates/WebApplicationBasic/template_gitignore +++ b/templates/WebApplicationBasic/template_gitignore @@ -184,9 +184,12 @@ ClientBin/ *.dbproj.schemaview *.pfx *.publishsettings -node_modules/ orleans.codegen.cs +# Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 +/node_modules/** +!/node_modules/_placeholder.txt + # RIA/Silverlight projects Generated_Code/ From 5750c4aab70749882e0c8fb99fa315835315e00a Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 8 Sep 2016 14:44:37 +0100 Subject: [PATCH 010/748] Publish updated generator-aspnetcore-spa package (0.2.5) --- templates/package-builder/src/yeoman/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/package-builder/src/yeoman/package.json b/templates/package-builder/src/yeoman/package.json index 82025189..f68b9aeb 100644 --- a/templates/package-builder/src/yeoman/package.json +++ b/templates/package-builder/src/yeoman/package.json @@ -1,6 +1,6 @@ { "name": "generator-aspnetcore-spa", - "version": "0.2.4", + "version": "0.2.5", "description": "Single-Page App templates for ASP.NET Core", "author": "Microsoft", "license": "Apache-2.0", From 0bcf4b0700c7bf4cb1de6b4124f0ee87427351b5 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 8 Sep 2016 15:58:47 +0100 Subject: [PATCH 011/748] Further work on fix for #235 (solving additional problem that 'npm publish' is hardcoded to exclude node_modules dirs) --- .gitignore | 9 +-------- ...lder.txt => template_nodemodules_placeholder.txt} | 0 ...lder.txt => template_nodemodules_placeholder.txt} | 0 ...lder.txt => template_nodemodules_placeholder.txt} | 0 ...lder.txt => template_nodemodules_placeholder.txt} | 0 ...lder.txt => template_nodemodules_placeholder.txt} | 0 templates/package-builder/src/build/build.ts | 6 +++++- templates/package-builder/src/yeoman/app/index.ts | 12 ++++++++++++ templates/package-builder/src/yeoman/package.json | 2 +- 9 files changed, 19 insertions(+), 10 deletions(-) rename templates/Angular2Spa/{node_modules/_placeholder.txt => template_nodemodules_placeholder.txt} (100%) rename templates/KnockoutSpa/{node_modules/_placeholder.txt => template_nodemodules_placeholder.txt} (100%) rename templates/ReactReduxSpa/{node_modules/_placeholder.txt => template_nodemodules_placeholder.txt} (100%) rename templates/ReactSpa/{node_modules/_placeholder.txt => template_nodemodules_placeholder.txt} (100%) rename templates/WebApplicationBasic/{node_modules/_placeholder.txt => template_nodemodules_placeholder.txt} (100%) diff --git a/.gitignore b/.gitignore index 6df4b42c..06cc11a3 100644 --- a/.gitignore +++ b/.gitignore @@ -34,13 +34,6 @@ npm-debug.log # files with that name (https://github.com/npm/npm/issues/1862). So, each template instead has a template_gitignore # file which gets renamed after the files are copied. And so any files that need to be excluded in the source # repo have to be excluded here. - -# Note that we need to exclude node_modules/** (i.e., subdirs, not the whole of node_modules) because we do need to -# include the _placeholder.txt files, and can't do that using gitignore exclusion because developers aren't promoted to -# commit files included that way. This is all a workaround for Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 -/templates/*/node_modules/** +/templates/*/node_modules/ /templates/*/wwwroot/dist/ .vscode/ - -# Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 -!/templates/*/node_modules/_placeholder.txt diff --git a/templates/Angular2Spa/node_modules/_placeholder.txt b/templates/Angular2Spa/template_nodemodules_placeholder.txt similarity index 100% rename from templates/Angular2Spa/node_modules/_placeholder.txt rename to templates/Angular2Spa/template_nodemodules_placeholder.txt diff --git a/templates/KnockoutSpa/node_modules/_placeholder.txt b/templates/KnockoutSpa/template_nodemodules_placeholder.txt similarity index 100% rename from templates/KnockoutSpa/node_modules/_placeholder.txt rename to templates/KnockoutSpa/template_nodemodules_placeholder.txt diff --git a/templates/ReactReduxSpa/node_modules/_placeholder.txt b/templates/ReactReduxSpa/template_nodemodules_placeholder.txt similarity index 100% rename from templates/ReactReduxSpa/node_modules/_placeholder.txt rename to templates/ReactReduxSpa/template_nodemodules_placeholder.txt diff --git a/templates/ReactSpa/node_modules/_placeholder.txt b/templates/ReactSpa/template_nodemodules_placeholder.txt similarity index 100% rename from templates/ReactSpa/node_modules/_placeholder.txt rename to templates/ReactSpa/template_nodemodules_placeholder.txt diff --git a/templates/WebApplicationBasic/node_modules/_placeholder.txt b/templates/WebApplicationBasic/template_nodemodules_placeholder.txt similarity index 100% rename from templates/WebApplicationBasic/node_modules/_placeholder.txt rename to templates/WebApplicationBasic/template_nodemodules_placeholder.txt diff --git a/templates/package-builder/src/build/build.ts b/templates/package-builder/src/build/build.ts index 546ff891..a0433bd4 100644 --- a/templates/package-builder/src/build/build.ts +++ b/templates/package-builder/src/build/build.ts @@ -110,7 +110,11 @@ function buildDotNetNewNuGetPackage() { const projectGuid = '00000000-0000-0000-0000-000000000000'; const filenameReplacements = [ { from: /.*\.xproj$/, to: `${sourceProjectName}.xproj` }, - { from: /\btemplate_gitignore$/, to: '.gitignore' } + { from: /\btemplate_gitignore$/, to: '.gitignore' }, + + // Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 + // For details, see the comment in ../yeoman/app/index.ts + { from: /\btemplate_nodemodules_placeholder.txt$/, to: 'node_modules/_placeholder.txt' } ]; const contentReplacements = [ { from: /[0-9a-f\-]{36}<\/ProjectGuid>/g, to: `${projectGuid}` }, diff --git a/templates/package-builder/src/yeoman/app/index.ts b/templates/package-builder/src/yeoman/app/index.ts index ecb53667..04687a08 100644 --- a/templates/package-builder/src/yeoman/app/index.ts +++ b/templates/package-builder/src/yeoman/app/index.ts @@ -52,6 +52,18 @@ class MyGenerator extends yeoman.Base { outputFn = path.join(path.dirname(fn), '.gitignore'); } + // Likewise, output template_nodemodules_placeholder.txt as node_modules/_placeholder.txt + // This is a workaround for https://github.com/aspnet/JavaScriptServices/issues/235. We need the new project + // to have a nonempty node_modules dir as far as *source control* is concerned. So, there's a gitignore + // rule that explicitly causes node_modules/_placeholder.txt to be tracked in source control. But how + // does that file get there in the first place? It's not enough for such a file to exist when the + // generator-aspnetcore-spa NPM package is published, because NPM doesn't allow any directories called + // node_modules to exist in the package. So we have a file with at a different location, and move it + // to node_modules as part of executing the template. + if (path.basename(fn) === 'template_nodemodules_placeholder.txt') { + outputFn = path.join(path.dirname(fn), 'node_modules', '_placeholder.txt'); + } + this.fs.copyTpl( path.join(templateRoot, fn), this.destinationPath(outputFn), diff --git a/templates/package-builder/src/yeoman/package.json b/templates/package-builder/src/yeoman/package.json index f68b9aeb..49302f90 100644 --- a/templates/package-builder/src/yeoman/package.json +++ b/templates/package-builder/src/yeoman/package.json @@ -1,6 +1,6 @@ { "name": "generator-aspnetcore-spa", - "version": "0.2.5", + "version": "0.2.6", "description": "Single-Page App templates for ASP.NET Core", "author": "Microsoft", "license": "Apache-2.0", From 874575ba92ee4438a9510141c989b298324c6ec3 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Fri, 9 Sep 2016 09:44:16 +0100 Subject: [PATCH 012/748] Fix instructions for running samples. Fixes #301 --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 287a5fe1..09881eb6 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,9 @@ Also in this repo, [the `samples` directory](https://github.com/aspnet/JavaScrip **To run the samples:** * Clone this repo - * Change directory to the same you want to run (e.g., `cd samples/angular/MusicStore`) - * Restore dependencies (run `dotnet restore` and `npm install`). + * At the repo's root directory (the one containing `src`, `samples`, etc.), run `dotnet restore` + * Change directory to the sample you want to run (e.g., `cd samples/angular/MusicStore`) + * Restore Node dependencies by running `npm install` * If you're trying to run the Angular 2 "Music Store" sample, then also run `gulp` (which you need to have installed globally). None of the other samples require this. * Run the application (`dotnet run`) * Browse to [http://localhost:5000](http://localhost:5000) From c2c45b04df4ecff721c407ff6fc57d082fe92712 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 9 Sep 2016 11:06:36 +0100 Subject: [PATCH 013/748] In preparation for supporting redirections, aspnet-prerendering now passes through all boot func resolution props to .NET code --- .../npm/aspnet-prerendering/package.json | 2 +- .../npm/aspnet-prerendering/src/Prerendering.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json index 4fc59a56..5c188ebd 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json @@ -1,6 +1,6 @@ { "name": "aspnet-prerendering", - "version": "1.0.5", + "version": "1.0.6", "description": "Helpers for server-side rendering of JavaScript applications in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "main": "index.js", "scripts": { diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts index 3a14f94f..6740cc93 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts @@ -84,7 +84,7 @@ export function renderToString(callback: RenderToStringCallback, applicationBase // Actually perform the rendering bootFuncPromiseWithTimeout.then(successResult => { - callback(null, { html: successResult.html, globals: successResult.globals }); + callback(null, successResult); }, error => { callback(error, null); }); From 1be9102aea7f0746a9b1219191c2abad70804f49 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 9 Sep 2016 11:09:44 +0100 Subject: [PATCH 014/748] Prerendering server-side code can now issue redirections. Fixes #280 --- .../Prerendering/PrerenderTagHelper.cs | 9 +++++++++ .../Prerendering/RenderToStringResult.cs | 1 + 2 files changed, 10 insertions(+) diff --git a/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs b/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs index de02c90f..7bce95c6 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs @@ -85,6 +85,15 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu unencodedPathAndQuery, CustomDataParameter, TimeoutMillisecondsParameter); + + if (!string.IsNullOrEmpty(result.RedirectUrl)) + { + // It's a redirection + ViewContext.HttpContext.Response.Redirect(result.RedirectUrl); + return; + } + + // It's some HTML to inject output.Content.SetHtmlContent(result.Html); // Also attach any specified globals to the 'window' object. This is useful for transferring diff --git a/src/Microsoft.AspNetCore.SpaServices/Prerendering/RenderToStringResult.cs b/src/Microsoft.AspNetCore.SpaServices/Prerendering/RenderToStringResult.cs index 1d5b482e..cae8d631 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Prerendering/RenderToStringResult.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Prerendering/RenderToStringResult.cs @@ -6,5 +6,6 @@ public class RenderToStringResult { public JObject Globals { get; set; } public string Html { get; set; } + public string RedirectUrl { get; set; } } } \ No newline at end of file From 28550784edb6c9c453a8983285dc0d6a9aaa12a9 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 9 Sep 2016 11:12:59 +0100 Subject: [PATCH 015/748] ReactReduxSpa's boot-server now supports redirections issued by react-router --- templates/ReactReduxSpa/ClientApp/boot-server.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/templates/ReactReduxSpa/ClientApp/boot-server.tsx b/templates/ReactReduxSpa/ClientApp/boot-server.tsx index fa3289ea..62aefcfa 100644 --- a/templates/ReactReduxSpa/ClientApp/boot-server.tsx +++ b/templates/ReactReduxSpa/ClientApp/boot-server.tsx @@ -5,15 +5,22 @@ import { match, RouterContext } from 'react-router'; import createMemoryHistory from 'history/lib/createMemoryHistory'; import routes from './routes'; import configureStore from './configureStore'; +type BootResult = { html?: string, globals?: { [key: string]: any }, redirectUrl?: string}; export default function (params: any): Promise<{ html: string }> { - return new Promise<{ html: string, globals: { [key: string]: any } }>((resolve, reject) => { + return new Promise((resolve, reject) => { // Match the incoming request against the list of client-side routes match({ routes, location: params.location }, (error, redirectLocation, renderProps: any) => { if (error) { throw error; } + // If there's a redirection, just send this information back to the host application + if (redirectLocation) { + resolve({ redirectUrl: redirectLocation.pathname }); + return; + } + // If it didn't match any route, renderProps will be undefined if (!renderProps) { throw new Error(`The location '${ params.url }' doesn't match any route configured in react-router.`); From b4bec30b0f1610f759bb5dd369a24bd26484ba86 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 9 Sep 2016 11:31:36 +0100 Subject: [PATCH 016/748] Clean up dependencies vs devDependencies in templates --- templates/Angular2Spa/package.json | 32 +++++++++++++--------------- templates/KnockoutSpa/package.json | 4 +--- templates/ReactReduxSpa/package.json | 24 ++++++++++----------- templates/ReactSpa/package.json | 12 +++++------ 4 files changed, 32 insertions(+), 40 deletions(-) diff --git a/templates/Angular2Spa/package.json b/templates/Angular2Spa/package.json index 37970041..04fab270 100644 --- a/templates/Angular2Spa/package.json +++ b/templates/Angular2Spa/package.json @@ -1,23 +1,6 @@ { "name": "WebApplicationBasic", "version": "0.0.0", - "devDependencies": { - "aspnet-webpack": "^1.0.6", - "bootstrap": "^3.3.6", - "css-loader": "^0.23.1", - "expose-loader": "^0.7.1", - "extendify": "^1.0.0", - "extract-text-webpack-plugin": "^1.0.1", - "file-loader": "^0.8.5", - "jquery": "^2.2.1", - "raw-loader": "^0.5.1", - "style-loader": "^0.13.0", - "ts-loader": "^0.8.1", - "typescript": "^1.8.2", - "url-loader": "^0.5.7", - "webpack": "^1.12.14", - "webpack-hot-middleware": "^2.10.0" - }, "dependencies": { "@angular/common": "2.0.0-rc.4", "@angular/compiler": "2.0.0-rc.4", @@ -29,12 +12,27 @@ "@angular/router": "3.0.0-beta.2", "angular2-universal": "^0.104.5", "aspnet-prerendering": "^1.0.2", + "aspnet-webpack": "^1.0.6", + "bootstrap": "^3.3.6", "css": "^2.2.1", + "css-loader": "^0.23.1", "es6-shim": "^0.35.1", + "expose-loader": "^0.7.1", + "extendify": "^1.0.0", + "extract-text-webpack-plugin": "^1.0.1", + "file-loader": "^0.8.5", "isomorphic-fetch": "^2.2.1", + "jquery": "^2.2.1", "preboot": "^2.0.10", + "raw-loader": "^0.5.1", "rxjs": "5.0.0-beta.6", + "style-loader": "^0.13.0", + "ts-loader": "^0.8.1", + "typescript": "^1.8.2", + "url-loader": "^0.5.7", + "webpack": "^1.12.14", "webpack-externals-plugin": "^1.0.0", + "webpack-hot-middleware": "^2.10.0", "zone.js": "^0.6.12" } } diff --git a/templates/KnockoutSpa/package.json b/templates/KnockoutSpa/package.json index 96a49bd7..bff49e01 100644 --- a/templates/KnockoutSpa/package.json +++ b/templates/KnockoutSpa/package.json @@ -19,9 +19,7 @@ "ts-loader": "^0.8.1", "typescript": "^1.8.2", "url-loader": "^0.5.7", - "webpack": "^1.12.14" - }, - "dependencies": { + "webpack": "^1.12.14", "webpack-hot-middleware": "^2.10.0" } } diff --git a/templates/ReactReduxSpa/package.json b/templates/ReactReduxSpa/package.json index 82fb4918..6ac0feeb 100644 --- a/templates/ReactReduxSpa/package.json +++ b/templates/ReactReduxSpa/package.json @@ -1,29 +1,21 @@ { "name": "WebApplicationBasic", "version": "0.0.0", - "devDependencies": { + "dependencies": { + "aspnet-prerendering": "^1.0.2", "aspnet-webpack": "^1.0.6", "aspnet-webpack-react": "^1.0.2", + "babel-core": "^6.5.2", "babel-loader": "^6.2.3", "babel-preset-es2015": "^6.5.0", "babel-preset-react": "^6.5.0", "bootstrap": "^3.3.6", "css-loader": "^0.23.1", + "domain-task": "^2.0.0", "extendify": "^1.0.0", "extract-text-webpack-plugin": "^1.0.1", "file-loader": "^0.8.5", "jquery": "^2.2.1", - "style-loader": "^0.13.0", - "ts-loader": "^0.8.1", - "typescript": "^1.8.2", - "url-loader": "^0.5.7", - "webpack": "^1.12.14", - "webpack-hot-middleware": "^2.10.0" - }, - "dependencies": { - "aspnet-prerendering": "^1.0.2", - "babel-core": "^6.5.2", - "domain-task": "^2.0.0", "react": "^15.0.1", "react-dom": "^15.0.1", "react-redux": "^4.4.4", @@ -32,6 +24,12 @@ "redux": "^3.4.0", "redux-thunk": "^2.0.1", "redux-typed": "^1.0.0", - "webpack-externals-plugin": "^1.0.0" + "style-loader": "^0.13.0", + "ts-loader": "^0.8.1", + "typescript": "^1.8.2", + "url-loader": "^0.5.7", + "webpack-externals-plugin": "^1.0.0", + "webpack": "^1.12.14", + "webpack-hot-middleware": "^2.10.0" } } diff --git a/templates/ReactSpa/package.json b/templates/ReactSpa/package.json index 9e07d1b7..5d2c44b9 100644 --- a/templates/ReactSpa/package.json +++ b/templates/ReactSpa/package.json @@ -4,6 +4,7 @@ "devDependencies": { "aspnet-webpack": "^1.0.6", "aspnet-webpack-react": "^1.0.2", + "babel-core": "^6.5.2", "babel-loader": "^6.2.3", "babel-preset-es2015": "^6.5.0", "babel-preset-react": "^6.5.0", @@ -12,19 +13,16 @@ "extendify": "^1.0.0", "extract-text-webpack-plugin": "^1.0.1", "file-loader": "^0.8.5", + "isomorphic-fetch": "^2.2.1", "jquery": "^2.2.1", + "react": "^15.0.1", + "react-dom": "^15.0.1", + "react-router": "^2.1.1", "style-loader": "^0.13.0", "ts-loader": "^0.8.1", "typescript": "^1.8.2", "url-loader": "^0.5.7", "webpack": "^1.12.14", "webpack-hot-middleware": "^2.10.0" - }, - "dependencies": { - "babel-core": "^6.5.2", - "isomorphic-fetch": "^2.2.1", - "react": "^15.0.1", - "react-dom": "^15.0.1", - "react-router": "^2.1.1" } } From da662c55fadacc4ec75167d9d56c9c8dcd756be0 Mon Sep 17 00:00:00 2001 From: Erik Medina Date: Sat, 27 Aug 2016 03:42:37 -0700 Subject: [PATCH 017/748] Make webpack dev dependency a peer dependency in aspnet-webpack. Moving webpack from a dev dependency to a peer dependency makes the dependency soft and allows the webpack-dev-middleware to pickup the version of webpack being used by the consumer of the package. --- .../npm/aspnet-webpack/package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json index 327d65d5..1f9a584d 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json @@ -14,12 +14,14 @@ "connect": "^3.4.1", "memory-fs": "^0.3.0", "require-from-string": "^1.1.0", - "webpack": "^1.12.14", - "webpack-dev-middleware": "^1.5.1", + "webpack-dev-middleware": "^1.6.1", "webpack-externals-plugin": "^1.0.0" }, "devDependencies": { "rimraf": "^2.5.4", "typescript": "^1.8.10" + }, + "peerDependencies": { + "webpack": "^1.13.2" } } From 67f7e7450f868b25f138f15c209bba00ea09796e Mon Sep 17 00:00:00 2001 From: Erik Medina Date: Sat, 27 Aug 2016 04:03:23 -0700 Subject: [PATCH 018/748] Adding tsd to dev dependencies in aspnet-webpack. Adding tsd to aspnet-webpack's dev dependencies to allow the package's npm prepublish script to succeed without a global tsd install. --- .../npm/aspnet-webpack/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json index 1f9a584d..5fa571a5 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json @@ -18,6 +18,7 @@ "webpack-externals-plugin": "^1.0.0" }, "devDependencies": { + "tsd": "0.6.5", "rimraf": "^2.5.4", "typescript": "^1.8.10" }, From 605090e909e53e655ab1ad66be34cc75f2afffed Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 9 Sep 2016 11:37:57 +0100 Subject: [PATCH 019/748] Publish updated version of aspnet-webpack as 1.0.10 --- .../npm/aspnet-webpack/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json index 5fa571a5..e58c2b46 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json @@ -1,6 +1,6 @@ { "name": "aspnet-webpack", - "version": "1.0.9", + "version": "1.0.10", "description": "Helpers for using Webpack in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "main": "index.js", "scripts": { From bc2de2ad598d90c29e19f1b585afbf074384a93c Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 9 Sep 2016 13:21:31 +0100 Subject: [PATCH 020/748] In aspnet-webpack HMR, don't rely on assumption that entry point is called 'main'. Fixes #289. --- .../npm/aspnet-webpack/package.json | 2 +- .../src/WebpackDevMiddleware.ts | 24 +++++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json index e58c2b46..077e6ff0 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json @@ -1,6 +1,6 @@ { "name": "aspnet-webpack", - "version": "1.0.10", + "version": "1.0.11", "description": "Helpers for using Webpack in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "main": "index.js", "scripts": { diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts index f34eefd8..ff47855d 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts @@ -43,12 +43,26 @@ export function createWebpackDevServer(callback: CreateDevServerCallback, option const listener = app.listen(suggestedHMRPortOrZero, () => { // Build the final Webpack config based on supplied options if (enableHotModuleReplacement) { - // TODO: Stop assuming there's an entry point called 'main' - if (typeof webpackConfig.entry['main'] === 'string') { - webpackConfig.entry['main'] = ['webpack-hot-middleware/client', webpackConfig.entry['main']]; - } else { - webpackConfig.entry['main'].unshift('webpack-hot-middleware/client'); + // For this, we only support the key/value config format, not string or string[], since + // those ones don't clearly indicate what the resulting bundle name will be + const entryPoints = webpackConfig.entry; + const isObjectStyleConfig = entryPoints + && typeof entryPoints === 'object' + && !(entryPoints instanceof Array); + if (!isObjectStyleConfig) { + 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); + return; } + + // Augment all entry points so they support HMR + Object.getOwnPropertyNames(entryPoints).forEach(entryPointName => { + if (typeof entryPoints[entryPointName] === 'string') { + entryPoints[entryPointName] = ['webpack-hot-middleware/client', entryPoints[entryPointName]]; + } else { + entryPoints[entryPointName].unshift('webpack-hot-middleware/client'); + } + }); + webpackConfig.plugins.push( new webpack.HotModuleReplacementPlugin() ); From f071590fcefa137919d3d500c0180bf385c93248 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 9 Sep 2016 16:31:15 +0100 Subject: [PATCH 021/748] Webpack HMR EventSource requests are now proxied (rather than redirected) to the local HMR server. Fixes #271. --- .../Webpack/ConditionalProxyMiddleware.cs | 1 + .../ConditionalProxyMiddlewareOptions.cs | 6 +++- .../Webpack/WebpackDevMiddleware.cs | 36 +++++++++---------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs index 72b66c30..5a56c81b 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs @@ -30,6 +30,7 @@ public ConditionalProxyMiddleware( _pathPrefix = pathPrefix; _options = options; _httpClient = new HttpClient(new HttpClientHandler()); + _httpClient.Timeout = _options.RequestTimeout; } public async Task Invoke(HttpContext context) diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs index 56540075..2c3311aa 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs @@ -1,16 +1,20 @@ +using System; + namespace Microsoft.AspNetCore.SpaServices.Webpack { internal class ConditionalProxyMiddlewareOptions { - public ConditionalProxyMiddlewareOptions(string scheme, string host, string port) + public ConditionalProxyMiddlewareOptions(string scheme, string host, string port, TimeSpan requestTimeout) { Scheme = scheme; Host = host; Port = port; + RequestTimeout = requestTimeout; } public string Scheme { get; } public string Host { get; } public string Port { get; } + public TimeSpan RequestTimeout { get; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs index dcc5d4fc..d046c284 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.PlatformAbstractions; using Newtonsoft.Json; +using System.Threading; // Putting in this namespace so it's always available whenever MapRoute is @@ -14,8 +15,6 @@ namespace Microsoft.AspNetCore.Builder { public static class WebpackDevMiddleware { - private const string WebpackDevMiddlewareScheme = "http"; - private const string WebpackHotMiddlewareEndpoint = "/__webpack_hmr"; private const string DefaultConfigFile = "webpack.config.js"; public static void UseWebpackDevMiddleware( @@ -62,30 +61,27 @@ public static void UseWebpackDevMiddleware( JsonConvert.SerializeObject(devServerOptions)).Result; // Proxy the corresponding requests through ASP.NET and into the Node listener + // Anything under / (e.g., /dist) is proxied as a normal HTTP request with a typical timeout (100s is the default from HttpClient), + // plus /__webpack_hmr is proxied with infinite timeout, because it's an EventSource (long-lived request). + appBuilder.UseProxyToLocalWebpackDevMiddleware(devServerInfo.PublicPath, devServerInfo.Port, TimeSpan.FromSeconds(100)); + appBuilder.UseProxyToLocalWebpackDevMiddleware("/__webpack_hmr", devServerInfo.Port, Timeout.InfiniteTimeSpan); + } + + private static void UseProxyToLocalWebpackDevMiddleware(this IApplicationBuilder appBuilder, string publicPath, int proxyToPort, TimeSpan requestTimeout) + { // Note that this is hardcoded to make requests to "localhost" regardless of the hostname of the // server as far as the client is concerned. This is because ConditionalProxyMiddlewareOptions is // the one making the internal HTTP requests, and it's going to be to some port on this machine // because aspnet-webpack hosts the dev server there. We can't use the hostname that the client // sees, because that could be anything (e.g., some upstream load balancer) and we might not be // able to make outbound requests to it from here. - var proxyOptions = new ConditionalProxyMiddlewareOptions(WebpackDevMiddlewareScheme, - "localhost", devServerInfo.Port.ToString()); - appBuilder.UseMiddleware(devServerInfo.PublicPath, proxyOptions); - - // While it would be nice to proxy the /__webpack_hmr requests too, these return an EventStream, - // and the Microsoft.AspNetCore.Proxy code doesn't handle that entirely - it throws an exception after - // a while. So, just serve a 302 for those. But note that we must use the hostname that the client - // sees, not "localhost", so that it works even when you're not running on localhost (e.g., Docker). - appBuilder.Map(WebpackHotMiddlewareEndpoint, builder => - { - builder.Use(next => ctx => - { - var hostname = ctx.Request.Host.Host; - ctx.Response.Redirect( - $"{WebpackDevMiddlewareScheme}://{hostname}:{devServerInfo.Port.ToString()}{WebpackHotMiddlewareEndpoint}"); - return Task.FromResult(0); - }); - }); + // Also note that the webpack HMR service always uses HTTP, even if your app server uses HTTPS, + // because the HMR service has no need for HTTPS (the client doesn't see it directly - all traffic + // to it is proxied), and the HMR service couldn't use HTTPS anyway (in general it wouldn't have + // the necessary certificate). + var proxyOptions = new ConditionalProxyMiddlewareOptions( + "http", "localhost", proxyToPort.ToString(), requestTimeout); + appBuilder.UseMiddleware(publicPath, proxyOptions); } #pragma warning disable CS0649 From 80f740a9ed33d5fe2589af1ee5d620cb45d0dcd6 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Tue, 13 Sep 2016 12:51:23 +0100 Subject: [PATCH 022/748] Revert "Webpack HMR EventSource requests are now proxied (rather than redirected) to the local HMR server" because of 'ECANCELED'/'EPIPE broken pipe' issues. Awaiting feedback from Kestrel team. --- .../Webpack/ConditionalProxyMiddleware.cs | 1 - .../ConditionalProxyMiddlewareOptions.cs | 6 +--- .../Webpack/WebpackDevMiddleware.cs | 36 ++++++++++--------- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs index 5a56c81b..72b66c30 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs @@ -30,7 +30,6 @@ public ConditionalProxyMiddleware( _pathPrefix = pathPrefix; _options = options; _httpClient = new HttpClient(new HttpClientHandler()); - _httpClient.Timeout = _options.RequestTimeout; } public async Task Invoke(HttpContext context) diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs index 2c3311aa..56540075 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs @@ -1,20 +1,16 @@ -using System; - namespace Microsoft.AspNetCore.SpaServices.Webpack { internal class ConditionalProxyMiddlewareOptions { - public ConditionalProxyMiddlewareOptions(string scheme, string host, string port, TimeSpan requestTimeout) + public ConditionalProxyMiddlewareOptions(string scheme, string host, string port) { Scheme = scheme; Host = host; Port = port; - RequestTimeout = requestTimeout; } public string Scheme { get; } public string Host { get; } public string Port { get; } - public TimeSpan RequestTimeout { get; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs index d046c284..dcc5d4fc 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.PlatformAbstractions; using Newtonsoft.Json; -using System.Threading; // Putting in this namespace so it's always available whenever MapRoute is @@ -15,6 +14,8 @@ namespace Microsoft.AspNetCore.Builder { public static class WebpackDevMiddleware { + private const string WebpackDevMiddlewareScheme = "http"; + private const string WebpackHotMiddlewareEndpoint = "/__webpack_hmr"; private const string DefaultConfigFile = "webpack.config.js"; public static void UseWebpackDevMiddleware( @@ -61,27 +62,30 @@ public static void UseWebpackDevMiddleware( JsonConvert.SerializeObject(devServerOptions)).Result; // Proxy the corresponding requests through ASP.NET and into the Node listener - // Anything under / (e.g., /dist) is proxied as a normal HTTP request with a typical timeout (100s is the default from HttpClient), - // plus /__webpack_hmr is proxied with infinite timeout, because it's an EventSource (long-lived request). - appBuilder.UseProxyToLocalWebpackDevMiddleware(devServerInfo.PublicPath, devServerInfo.Port, TimeSpan.FromSeconds(100)); - appBuilder.UseProxyToLocalWebpackDevMiddleware("/__webpack_hmr", devServerInfo.Port, Timeout.InfiniteTimeSpan); - } - - private static void UseProxyToLocalWebpackDevMiddleware(this IApplicationBuilder appBuilder, string publicPath, int proxyToPort, TimeSpan requestTimeout) - { // Note that this is hardcoded to make requests to "localhost" regardless of the hostname of the // server as far as the client is concerned. This is because ConditionalProxyMiddlewareOptions is // the one making the internal HTTP requests, and it's going to be to some port on this machine // because aspnet-webpack hosts the dev server there. We can't use the hostname that the client // sees, because that could be anything (e.g., some upstream load balancer) and we might not be // able to make outbound requests to it from here. - // Also note that the webpack HMR service always uses HTTP, even if your app server uses HTTPS, - // because the HMR service has no need for HTTPS (the client doesn't see it directly - all traffic - // to it is proxied), and the HMR service couldn't use HTTPS anyway (in general it wouldn't have - // the necessary certificate). - var proxyOptions = new ConditionalProxyMiddlewareOptions( - "http", "localhost", proxyToPort.ToString(), requestTimeout); - appBuilder.UseMiddleware(publicPath, proxyOptions); + var proxyOptions = new ConditionalProxyMiddlewareOptions(WebpackDevMiddlewareScheme, + "localhost", devServerInfo.Port.ToString()); + appBuilder.UseMiddleware(devServerInfo.PublicPath, proxyOptions); + + // While it would be nice to proxy the /__webpack_hmr requests too, these return an EventStream, + // and the Microsoft.AspNetCore.Proxy code doesn't handle that entirely - it throws an exception after + // a while. So, just serve a 302 for those. But note that we must use the hostname that the client + // sees, not "localhost", so that it works even when you're not running on localhost (e.g., Docker). + appBuilder.Map(WebpackHotMiddlewareEndpoint, builder => + { + builder.Use(next => ctx => + { + var hostname = ctx.Request.Host.Host; + ctx.Response.Redirect( + $"{WebpackDevMiddlewareScheme}://{hostname}:{devServerInfo.Port.ToString()}{WebpackHotMiddlewareEndpoint}"); + return Task.FromResult(0); + }); + }); } #pragma warning disable CS0649 From 7f841ff8404f886385659a203c75a57baca010b3 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Tue, 13 Sep 2016 13:44:51 +0100 Subject: [PATCH 023/748] In Yeoman generator, support passing args from command line (e.g., --framework=angular-2) --- templates/package-builder/src/yeoman/app/index.ts | 7 ++++++- templates/package-builder/src/yeoman/package.json | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/templates/package-builder/src/yeoman/app/index.ts b/templates/package-builder/src/yeoman/app/index.ts index 04687a08..0d6af2d9 100644 --- a/templates/package-builder/src/yeoman/app/index.ts +++ b/templates/package-builder/src/yeoman/app/index.ts @@ -5,6 +5,9 @@ import * as glob from 'glob'; const yosay = require('yosay'); const toPascalCase = require('to-pascal-case'); +type YeomanPrompt = (opt: yeoman.IPromptOptions | yeoman.IPromptOptions[], callback: (answers: any) => void) => void; +const optionOrPrompt: YeomanPrompt = require('yeoman-option-or-prompt'); + const templates = [ { value: 'angular-2', name: 'Angular 2' }, { value: 'knockout', name: 'Knockout' }, @@ -14,16 +17,18 @@ const templates = [ class MyGenerator extends yeoman.Base { private _answers: any; + private _optionOrPrompt: YeomanPrompt; constructor(args: string | string[], options: any) { super(args, options); + this._optionOrPrompt = optionOrPrompt; this.log(yosay('Welcome to the ASP.NET Core Single-Page App generator!')); } prompting() { const done = this.async(); - this.prompt([{ + this._optionOrPrompt([{ type: 'list', name: 'framework', message: 'Framework', diff --git a/templates/package-builder/src/yeoman/package.json b/templates/package-builder/src/yeoman/package.json index 49302f90..2b1305c5 100644 --- a/templates/package-builder/src/yeoman/package.json +++ b/templates/package-builder/src/yeoman/package.json @@ -1,6 +1,6 @@ { "name": "generator-aspnetcore-spa", - "version": "0.2.6", + "version": "0.2.7", "description": "Single-Page App templates for ASP.NET Core", "author": "Microsoft", "license": "Apache-2.0", @@ -15,6 +15,7 @@ "node-uuid": "^1.4.7", "to-pascal-case": "^1.0.0", "yeoman-generator": "^0.20.2", + "yeoman-option-or-prompt": "^1.0.2", "yosay": "^1.1.1" } } From b72435c5cc30a4fedd207e2f6092777f4ea84b12 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Tue, 13 Sep 2016 13:57:23 +0100 Subject: [PATCH 024/748] Yeoman generator support for optional --projectguid=... CLI argument --- templates/package-builder/src/yeoman/app/index.ts | 3 ++- templates/package-builder/src/yeoman/package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/templates/package-builder/src/yeoman/app/index.ts b/templates/package-builder/src/yeoman/app/index.ts index 0d6af2d9..c10038b2 100644 --- a/templates/package-builder/src/yeoman/app/index.ts +++ b/templates/package-builder/src/yeoman/app/index.ts @@ -28,6 +28,7 @@ class MyGenerator extends yeoman.Base { prompting() { const done = this.async(); + this.option('projectguid'); this._optionOrPrompt([{ type: 'list', name: 'framework', @@ -41,7 +42,7 @@ class MyGenerator extends yeoman.Base { }], answers => { this._answers = answers; this._answers.namePascalCase = toPascalCase(answers.name); - this._answers.projectGuid = uuid.v4(); + this._answers.projectGuid = this.options['projectguid'] || uuid.v4(); done(); }); } diff --git a/templates/package-builder/src/yeoman/package.json b/templates/package-builder/src/yeoman/package.json index 2b1305c5..c26fd162 100644 --- a/templates/package-builder/src/yeoman/package.json +++ b/templates/package-builder/src/yeoman/package.json @@ -1,6 +1,6 @@ { "name": "generator-aspnetcore-spa", - "version": "0.2.7", + "version": "0.2.8", "description": "Single-Page App templates for ASP.NET Core", "author": "Microsoft", "license": "Apache-2.0", From 7c316d5c744c5ec1a378ececd89f7eb46a1ba3c7 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Wed, 14 Sep 2016 11:36:53 +0100 Subject: [PATCH 025/748] Update to ASP.NET Core 1.0.1. Fixes #309 --- src/Microsoft.AspNetCore.AngularServices/project.json | 2 +- src/Microsoft.AspNetCore.ReactServices/project.json | 2 +- src/Microsoft.AspNetCore.SpaServices/project.json | 2 +- templates/Angular2Spa/project.json | 6 +++--- templates/KnockoutSpa/project.json | 6 +++--- templates/ReactReduxSpa/project.json | 6 +++--- templates/ReactSpa/project.json | 6 +++--- templates/WebApplicationBasic/project.json | 6 +++--- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.AspNetCore.AngularServices/project.json b/src/Microsoft.AspNetCore.AngularServices/project.json index 8b64b73e..99aa0697 100644 --- a/src/Microsoft.AspNetCore.AngularServices/project.json +++ b/src/Microsoft.AspNetCore.AngularServices/project.json @@ -9,7 +9,7 @@ "defaultNamespace": "Microsoft.AspNetCore.AngularServices" }, "dependencies": { - "Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.0", + "Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.1", "Microsoft.AspNetCore.SpaServices": "1.0.0-*" }, "frameworks": { diff --git a/src/Microsoft.AspNetCore.ReactServices/project.json b/src/Microsoft.AspNetCore.ReactServices/project.json index ca1ae6ff..1da09985 100644 --- a/src/Microsoft.AspNetCore.ReactServices/project.json +++ b/src/Microsoft.AspNetCore.ReactServices/project.json @@ -9,7 +9,7 @@ "defaultNamespace": "Microsoft.AspNetCore.ReactServices" }, "dependencies": { - "Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.0", + "Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.1", "Microsoft.AspNetCore.SpaServices": "1.0.0-*" }, "frameworks": { diff --git a/src/Microsoft.AspNetCore.SpaServices/project.json b/src/Microsoft.AspNetCore.SpaServices/project.json index 1c55cd23..38ababb7 100644 --- a/src/Microsoft.AspNetCore.SpaServices/project.json +++ b/src/Microsoft.AspNetCore.SpaServices/project.json @@ -8,7 +8,7 @@ "Microsoft" ], "dependencies": { - "Microsoft.AspNetCore.Mvc": "1.0.0", + "Microsoft.AspNetCore.Mvc": "1.0.1", "Microsoft.AspNetCore.NodeServices": "1.0.0-*" }, "frameworks": { diff --git a/templates/Angular2Spa/project.json b/templates/Angular2Spa/project.json index 845fe680..c9066fc5 100755 --- a/templates/Angular2Spa/project.json +++ b/templates/Angular2Spa/project.json @@ -1,18 +1,18 @@ { "dependencies": { "Microsoft.NETCore.App": { - "version": "1.0.0", + "version": "1.0.1", "type": "platform" }, "Microsoft.AspNetCore.AngularServices": "1.0.0-*", "Microsoft.AspNetCore.Diagnostics": "1.0.0", - "Microsoft.AspNetCore.Mvc": "1.0.0", + "Microsoft.AspNetCore.Mvc": "1.0.1", "Microsoft.AspNetCore.Razor.Tools": { "version": "1.0.0-preview2-final", "type": "build" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", - "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.0.1", "Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.0.0", diff --git a/templates/KnockoutSpa/project.json b/templates/KnockoutSpa/project.json index d3e2777a..f76bf2a7 100755 --- a/templates/KnockoutSpa/project.json +++ b/templates/KnockoutSpa/project.json @@ -1,18 +1,18 @@ { "dependencies": { "Microsoft.NETCore.App": { - "version": "1.0.0", + "version": "1.0.1", "type": "platform" }, "Microsoft.AspNetCore.SpaServices": "1.0.0-*", "Microsoft.AspNetCore.Diagnostics": "1.0.0", - "Microsoft.AspNetCore.Mvc": "1.0.0", + "Microsoft.AspNetCore.Mvc": "1.0.1", "Microsoft.AspNetCore.Razor.Tools": { "version": "1.0.0-preview2-final", "type": "build" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", - "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.0.1", "Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.0.0", diff --git a/templates/ReactReduxSpa/project.json b/templates/ReactReduxSpa/project.json index 0794a38c..4b1ebfa3 100755 --- a/templates/ReactReduxSpa/project.json +++ b/templates/ReactReduxSpa/project.json @@ -1,18 +1,18 @@ { "dependencies": { "Microsoft.NETCore.App": { - "version": "1.0.0", + "version": "1.0.1", "type": "platform" }, "Microsoft.AspNetCore.ReactServices": "1.0.0-*", "Microsoft.AspNetCore.Diagnostics": "1.0.0", - "Microsoft.AspNetCore.Mvc": "1.0.0", + "Microsoft.AspNetCore.Mvc": "1.0.1", "Microsoft.AspNetCore.Razor.Tools": { "version": "1.0.0-preview2-final", "type": "build" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", - "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.0.1", "Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.0.0", diff --git a/templates/ReactSpa/project.json b/templates/ReactSpa/project.json index 0794a38c..4b1ebfa3 100755 --- a/templates/ReactSpa/project.json +++ b/templates/ReactSpa/project.json @@ -1,18 +1,18 @@ { "dependencies": { "Microsoft.NETCore.App": { - "version": "1.0.0", + "version": "1.0.1", "type": "platform" }, "Microsoft.AspNetCore.ReactServices": "1.0.0-*", "Microsoft.AspNetCore.Diagnostics": "1.0.0", - "Microsoft.AspNetCore.Mvc": "1.0.0", + "Microsoft.AspNetCore.Mvc": "1.0.1", "Microsoft.AspNetCore.Razor.Tools": { "version": "1.0.0-preview2-final", "type": "build" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", - "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.0.1", "Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.0.0", diff --git a/templates/WebApplicationBasic/project.json b/templates/WebApplicationBasic/project.json index 4464a43f..106ade4d 100755 --- a/templates/WebApplicationBasic/project.json +++ b/templates/WebApplicationBasic/project.json @@ -1,17 +1,17 @@ { "dependencies": { "Microsoft.NETCore.App": { - "version": "1.0.0", + "version": "1.0.1", "type": "platform" }, "Microsoft.AspNetCore.Diagnostics": "1.0.0", - "Microsoft.AspNetCore.Mvc": "1.0.0", + "Microsoft.AspNetCore.Mvc": "1.0.1", "Microsoft.AspNetCore.Razor.Tools": { "version": "1.0.0-preview2-final", "type": "build" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", - "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.0.1", "Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.0.0", From d76b013a561594ad0f6b258fd35c524f2db594d7 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Wed, 14 Sep 2016 12:04:15 +0100 Subject: [PATCH 026/748] WebpackDevMiddleware now uses ProjectPath option consistently. Fixes #307 --- .../Webpack/WebpackDevMiddleware.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs index dcc5d4fc..e6a65cbf 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs @@ -42,6 +42,11 @@ public static void UseWebpackDevMiddleware( // as fast as some theoretical future alternative. var nodeServicesOptions = new NodeServicesOptions(appBuilder.ApplicationServices); nodeServicesOptions.WatchFileExtensions = new string[] {}; // Don't watch anything + if (!string.IsNullOrEmpty(options.ProjectPath)) + { + nodeServicesOptions.ProjectPath = options.ProjectPath; + } + var nodeServices = NodeServicesFactory.CreateNodeServices(nodeServicesOptions); // Get a filename matching the middleware Node script @@ -50,11 +55,9 @@ public static void UseWebpackDevMiddleware( var nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit // Tell Node to start the server hosting webpack-dev-middleware - var hostEnv = (IHostingEnvironment)appBuilder.ApplicationServices.GetService(typeof(IHostingEnvironment)); - var projectPath = options.ProjectPath ?? hostEnv.ContentRootPath; var devServerOptions = new { - webpackConfigPath = Path.Combine(projectPath, options.ConfigFile ?? DefaultConfigFile), + webpackConfigPath = Path.Combine(nodeServicesOptions.ProjectPath, options.ConfigFile ?? DefaultConfigFile), suppliedOptions = options }; var devServerInfo = From 7a80d905b8af277972201ff7a4fb249733a86e2d Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 15 Sep 2016 12:07:49 +0100 Subject: [PATCH 027/748] In Angular 2 template, include reflect-metadata and zone.js in vendor bundle --- templates/Angular2Spa/webpack.config.vendor.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/Angular2Spa/webpack.config.vendor.js b/templates/Angular2Spa/webpack.config.vendor.js index cd2bbe00..9636722f 100644 --- a/templates/Angular2Spa/webpack.config.vendor.js +++ b/templates/Angular2Spa/webpack.config.vendor.js @@ -29,6 +29,8 @@ module.exports = { '@angular/platform-browser-dynamic', '@angular/router', '@angular/platform-server', + 'reflect-metadata', + 'zone.js', ] }, output: { From 06ad36f8309c08aeed9d78cc12076f516855ffa3 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 15 Sep 2016 12:32:01 +0100 Subject: [PATCH 028/748] In Angular 2 template, include prebuilt wwwroot/dist/* files to support VS and "dotnet new" templates (which can't run post-project-creation actions) --- templates/package-builder/src/build/build.ts | 34 ++++++++++++++------ 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/templates/package-builder/src/build/build.ts b/templates/package-builder/src/build/build.ts index a0433bd4..9c48a763 100644 --- a/templates/package-builder/src/build/build.ts +++ b/templates/package-builder/src/build/build.ts @@ -11,8 +11,8 @@ const isWindows = /^win/.test(process.platform); const textFileExtensions = ['.gitignore', 'template_gitignore', '.config', '.cs', '.cshtml', 'Dockerfile', '.html', '.js', '.json', '.jsx', '.md', '.nuspec', '.ts', '.tsx', '.xproj']; const yeomanGeneratorSource = './src/yeoman'; -const templates: { [key: string]: { dir: string, dotNetNewId: string, displayName: string } } = { - 'angular-2': { dir: '../../templates/Angular2Spa/', dotNetNewId: 'Angular', displayName: 'Angular 2' }, +const templates: { [key: string]: { dir: string, dotNetNewId: string, displayName: string, forceInclusion?: RegExp } } = { + 'angular-2': { dir: '../../templates/Angular2Spa/', dotNetNewId: 'Angular', displayName: 'Angular 2', forceInclusion: /^wwwroot\/dist\// }, 'knockout': { dir: '../../templates/KnockoutSpa/', dotNetNewId: 'Knockout', displayName: 'Knockout.js' }, 'react-redux': { dir: '../../templates/ReactReduxSpa/', dotNetNewId: 'ReactRedux', displayName: 'React.js and Redux' }, 'react': { dir: '../../templates/ReactSpa/', dotNetNewId: 'React', displayName: 'React.js' } @@ -28,7 +28,7 @@ function writeFileEnsuringDirExists(root: string, filename: string, contents: st fs.writeFileSync(fullPath, contents); } -function listFilesExcludingGitignored(root: string): string[] { +function listFilesExcludingGitignored(root: string, forceInclusion: RegExp): string[] { // Note that the gitignore files, prior to be written by the generator, are called 'template_gitignore' // instead of '.gitignore'. This is a workaround for Yeoman doing strange stuff with .gitignore files // (it renames them to .npmignore, which is not helpful). @@ -37,11 +37,11 @@ function listFilesExcludingGitignored(root: string): string[] { ? gitignore.compile(fs.readFileSync(gitIgnorePath, 'utf8')) : { accepts: () => true }; return glob.sync('**/*', { cwd: root, dot: true, nodir: true }) - .filter(fn => gitignoreEvaluator.accepts(fn)); + .filter(fn => gitignoreEvaluator.accepts(fn) || (forceInclusion && forceInclusion.test(fn))); } -function writeTemplate(sourceRoot: string, destRoot: string, contentReplacements: { from: RegExp, to: string }[], filenameReplacements: { from: RegExp, to: string }[]) { - listFilesExcludingGitignored(sourceRoot).forEach(fn => { +function writeTemplate(sourceRoot: string, destRoot: string, contentReplacements: { from: RegExp, to: string }[], filenameReplacements: { from: RegExp, to: string }[], forceInclusion: RegExp) { + listFilesExcludingGitignored(sourceRoot, forceInclusion).forEach(fn => { let sourceContent = fs.readFileSync(path.join(sourceRoot, fn)); // For text files, replace hardcoded values with template tags @@ -89,7 +89,7 @@ function buildYeomanNpmPackage() { ]; _.forEach(templates, (templateConfig, templateName) => { const outputDir = path.join(outputTemplatesRoot, templateName); - writeTemplate(templateConfig.dir, outputDir, contentReplacements, filenameReplacements); + writeTemplate(templateConfig.dir, outputDir, contentReplacements, filenameReplacements, templateConfig.forceInclusion); }); // Also copy the generator files (that's the compiled .js files, plus all other non-.ts files) @@ -125,7 +125,7 @@ function buildDotNetNewNuGetPackage() { _.forEach(templates, (templateConfig, templateName) => { const templateOutputDir = path.join(outputRoot, 'templates', templateName); const templateOutputProjectDir = path.join(templateOutputDir, sourceProjectName); - writeTemplate(templateConfig.dir, templateOutputProjectDir, contentReplacements, filenameReplacements); + writeTemplate(templateConfig.dir, templateOutputProjectDir, contentReplacements, filenameReplacements, templateConfig.forceInclusion); // Add a .netnew.json file fs.writeFileSync(path.join(templateOutputDir, '.netnew.json'), JSON.stringify({ @@ -145,7 +145,7 @@ function buildDotNetNewNuGetPackage() { const yeomanPackageVersion = JSON.parse(fs.readFileSync(path.join(yeomanGeneratorSource, 'package.json'), 'utf8')).version; writeTemplate('./src/dotnetnew', outputRoot, [ { from: /\{version\}/g, to: yeomanPackageVersion }, - ], []); + ], [], null); const nugetExe = path.join(process.cwd(), './bin/NuGet.exe'); const nugetStartInfo = { cwd: outputRoot, stdio: 'inherit' }; if (isWindows) { @@ -160,5 +160,21 @@ function buildDotNetNewNuGetPackage() { rimraf.sync('./tmp'); } +// TODO: Instead of just showing this warning, improve build script so it actually does build them +// in the correct format. Can do this once we've moved away from using ASPNETCORE_ENVIRONMENT to +// control the build output mode. The templates we warn about here are the ones where we ship some +// files that wouldn't normally be under source control (e.g., /wwwroot/dist/*). +const templatesWithForceIncludes = Object.getOwnPropertyNames(templates) + .filter(templateName => !!templates[templateName].forceInclusion); +if (templatesWithForceIncludes.length > 0) { + console.warn(` +--- +WARNING: Ensure that the following templates are already built in the configuration desired for publishing. +For example, build the dist files in debug mode. +TEMPLATES: ${templatesWithForceIncludes.join(', ')} +--- +`); +} + buildYeomanNpmPackage(); buildDotNetNewNuGetPackage(); From 591d548de750e002334e131965f8abbc42aca616 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 15 Sep 2016 12:34:24 +0100 Subject: [PATCH 029/748] Publish new Yeoman templates (0.2.9) --- templates/package-builder/src/yeoman/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/package-builder/src/yeoman/package.json b/templates/package-builder/src/yeoman/package.json index c26fd162..164fa30a 100644 --- a/templates/package-builder/src/yeoman/package.json +++ b/templates/package-builder/src/yeoman/package.json @@ -1,6 +1,6 @@ { "name": "generator-aspnetcore-spa", - "version": "0.2.8", + "version": "0.2.9", "description": "Single-Page App templates for ASP.NET Core", "author": "Microsoft", "license": "Apache-2.0", From 2ee0078cfd8e2936a51dd6ecfb813d83b03a6ce5 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 15 Sep 2016 14:15:03 +0100 Subject: [PATCH 030/748] Fix HttpNodeInstanceEntryPoint to match latest NPM modules --- .../Content/Node/entrypoint-http.js | 5 ++--- .../TypeScript/HttpNodeInstanceEntryPoint.ts | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js index cfa15bcf..b085a36e 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js +++ b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js @@ -128,9 +128,8 @@ ExitWhenParentExits_1.exitWhenParentExits(parseInt(parsedArgs.parentPid)); function readRequestBodyAsJson(request, callback) { var requestBodyAsString = ''; - request - .on('data', function (chunk) { requestBodyAsString += chunk; }) - .on('end', function () { callback(JSON.parse(requestBodyAsString)); }); + request.on('data', function (chunk) { requestBodyAsString += chunk; }); + request.on('end', function () { callback(JSON.parse(requestBodyAsString)); }); } function respondWithError(res, errorValue) { res.statusCode = 500; diff --git a/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts b/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts index f8e619d2..eb7fc28a 100644 --- a/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts +++ b/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts @@ -80,9 +80,8 @@ exitWhenParentExits(parseInt(parsedArgs.parentPid)); function readRequestBodyAsJson(request, callback) { let requestBodyAsString = ''; - request - .on('data', chunk => { requestBodyAsString += chunk; }) - .on('end', () => { callback(JSON.parse(requestBodyAsString)); }); + request.on('data', chunk => { requestBodyAsString += chunk; }); + request.on('end', () => { callback(JSON.parse(requestBodyAsString)); }); } function respondWithError(res: http.ServerResponse, errorValue: any) { From b71d139eb5f04d0a04494bcf8cfd7384513d3835 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 15 Sep 2016 14:36:42 +0100 Subject: [PATCH 031/748] Update xproj files to reference dotnet build tooling --- templates/Angular2Spa/Angular2Spa.xproj | 8 ++++---- templates/KnockoutSpa/KnockoutSpa.xproj | 12 ++++++------ templates/ReactReduxSpa/ReactReduxSpa.xproj | 8 ++++---- templates/ReactSpa/ReactSpa.xproj | 8 ++++---- .../WebApplicationBasic/WebApplicationBasic.xproj | 8 ++++---- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/templates/Angular2Spa/Angular2Spa.xproj b/templates/Angular2Spa/Angular2Spa.xproj index d4c5f075..3f02ad9d 100644 --- a/templates/Angular2Spa/Angular2Spa.xproj +++ b/templates/Angular2Spa/Angular2Spa.xproj @@ -5,16 +5,16 @@ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) true - + 8f5cb8a9-3086-4b49-a1c2-32a9f89bca11 Angular2Spa - ..\..\..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName) + .\obj .\bin\ + v4.5.2 2.0 - 2018 - + \ No newline at end of file diff --git a/templates/KnockoutSpa/KnockoutSpa.xproj b/templates/KnockoutSpa/KnockoutSpa.xproj index 7ce39c5c..9a6e512c 100644 --- a/templates/KnockoutSpa/KnockoutSpa.xproj +++ b/templates/KnockoutSpa/KnockoutSpa.xproj @@ -5,16 +5,16 @@ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) true - + 85231b41-6998-49ae-abd2-5124c83dbef2 KnockoutSpa - ..\..\..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName) - ..\..\..\JavaScriptServices.sln\artifacts\bin\$(MSBuildProjectName)\ + .\obj + .\bin\ + v4.5.2 2.0 - 2018 - - + + \ No newline at end of file diff --git a/templates/ReactReduxSpa/ReactReduxSpa.xproj b/templates/ReactReduxSpa/ReactReduxSpa.xproj index 0996bb60..f3701ba6 100644 --- a/templates/ReactReduxSpa/ReactReduxSpa.xproj +++ b/templates/ReactReduxSpa/ReactReduxSpa.xproj @@ -5,16 +5,16 @@ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) true - + dbfc6db0-a6d1-4694-a108-1c604b988da3 ReactReduxSpa - ..\..\..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName) + .\obj .\bin\ + v4.5.2 2.0 - 2018 - + \ No newline at end of file diff --git a/templates/ReactSpa/ReactSpa.xproj b/templates/ReactSpa/ReactSpa.xproj index abe5a589..4a9c25be 100644 --- a/templates/ReactSpa/ReactSpa.xproj +++ b/templates/ReactSpa/ReactSpa.xproj @@ -5,16 +5,16 @@ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) true - + e9d1a695-f0e6-46f2-b5e3-72f4af805387 ReactSpa - ..\..\..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName) + .\obj .\bin\ + v4.5.2 2.0 - 2018 - + \ No newline at end of file diff --git a/templates/WebApplicationBasic/WebApplicationBasic.xproj b/templates/WebApplicationBasic/WebApplicationBasic.xproj index b0c23a00..40c6b3ab 100644 --- a/templates/WebApplicationBasic/WebApplicationBasic.xproj +++ b/templates/WebApplicationBasic/WebApplicationBasic.xproj @@ -5,16 +5,16 @@ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) true - + cb4398d6-b7f1-449a-ae02-828769679232 WebApplicationBasic - ..\..\..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName) + .\obj .\bin\ + v4.5.2 2.0 - 2018 - + \ No newline at end of file From ce0d2089d2487e52e288ebc956a9a2277346545a Mon Sep 17 00:00:00 2001 From: Mark Pieszak Date: Mon, 19 Sep 2016 09:12:03 +0100 Subject: [PATCH 032/748] ng2 2.0, Universal 2.0, TS 2.0, Preboot 4.* --- .../Angular2Spa/ClientApp/boot-client.ts | 33 +- .../Angular2Spa/ClientApp/boot-server.ts | 98 ++- .../ClientApp/components/app/app.ts | 8 +- .../ClientApp/components/counter/counter.ts | 4 +- .../components/fetch-data/fetch-data.ts | 4 +- .../ClientApp/components/home/home.ts | 4 +- .../Angular2Spa/ClientApp/components/index.ts | 8 + .../ClientApp/components/nav-menu/nav-menu.ts | 8 +- .../Angular2Spa/ClientApp/main.browser.ts | 55 ++ templates/Angular2Spa/ClientApp/main.node.ts | 55 ++ templates/Angular2Spa/ClientApp/routes.ts | 9 +- templates/Angular2Spa/package.json | 55 +- templates/Angular2Spa/tsconfig.json | 17 +- templates/Angular2Spa/tsd.json | 15 - ...rl-workaround.d.ts => custom-typings.d.ts} | 0 .../typings/es6-shim/es6-shim.d.ts | 668 ------------------ .../typings/requirejs/require.d.ts | 397 ----------- templates/Angular2Spa/typings/tsd.d.ts | 3 - 18 files changed, 274 insertions(+), 1167 deletions(-) create mode 100644 templates/Angular2Spa/ClientApp/components/index.ts create mode 100644 templates/Angular2Spa/ClientApp/main.browser.ts create mode 100644 templates/Angular2Spa/ClientApp/main.node.ts delete mode 100644 templates/Angular2Spa/tsd.json rename templates/Angular2Spa/typings/{url-workaround.d.ts => custom-typings.d.ts} (100%) delete mode 100644 templates/Angular2Spa/typings/es6-shim/es6-shim.d.ts delete mode 100644 templates/Angular2Spa/typings/requirejs/require.d.ts delete mode 100644 templates/Angular2Spa/typings/tsd.d.ts diff --git a/templates/Angular2Spa/ClientApp/boot-client.ts b/templates/Angular2Spa/ClientApp/boot-client.ts index d19ff24e..0889ed5c 100644 --- a/templates/Angular2Spa/ClientApp/boot-client.ts +++ b/templates/Angular2Spa/ClientApp/boot-client.ts @@ -1,21 +1,30 @@ +// the polyfills must be the first thing imported +import 'angular2-universal-polyfills'; + import 'es6-shim'; require('zone.js'); import 'bootstrap'; import 'reflect-metadata'; import './styles/site.css'; -import { bootstrap } from '@angular/platform-browser-dynamic'; -import { FormBuilder } from '@angular/common'; -import { provideRouter } from '@angular/router'; -import { HTTP_PROVIDERS } from '@angular/http'; -import { App } from './components/app/app'; -import { routes } from './routes'; - -bootstrap(App, [ - ...HTTP_PROVIDERS, - FormBuilder, - provideRouter(routes) -]); +// Angular 2 +import { enableProdMode} from '@angular/core'; +import { platformUniversalDynamic } from 'angular2-universal'; + +// enable prod for faster renders +enableProdMode(); + +import { MainModule } from './main.browser'; + +const platformRef = platformUniversalDynamic(); + +// on document ready bootstrap Angular 2 +document.addEventListener('DOMContentLoaded', () => { + + platformRef.bootstrapModule(MainModule); + +}); + // Basic hot reloading support. Automatically reloads and restarts the Angular 2 app each time // you modify source files. This will not preserve any application state other than the URL. diff --git a/templates/Angular2Spa/ClientApp/boot-server.ts b/templates/Angular2Spa/ClientApp/boot-server.ts index 3549263b..de006549 100644 --- a/templates/Angular2Spa/ClientApp/boot-server.ts +++ b/templates/Angular2Spa/ClientApp/boot-server.ts @@ -1,36 +1,76 @@ -import 'angular2-universal/polyfills'; -import * as ngCore from '@angular/core'; -import { APP_BASE_HREF } from '@angular/common'; -import { provideRouter } from '@angular/router'; -import * as ngUniversal from 'angular2-universal'; -import { BASE_URL, ORIGIN_URL, REQUEST_URL } from 'angular2-universal/common'; -import { App } from './components/app/app'; +// the polyfills must be the first thing imported in node.js +import 'angular2-universal-polyfills'; + +// Angular 2 +import { enableProdMode } from '@angular/core'; +// Angular2 Universal +import { platformNodeDynamic } from 'angular2-universal'; + +// Application imports +import { MainModule } from './main.node'; +import { App } from './components'; import { routes } from './routes'; -const bootloader = ngUniversal.bootloader({ - async: true, - preboot: false, - platformProviders: [ - ngCore.provide(APP_BASE_HREF, { useValue: '/' }), - ] -}); - -export default function (params: any): Promise<{ html: string, globals?: any }> { - const config: ngUniversal.AppConfig = { - directives: [App], - providers: [ - ngCore.provide(ORIGIN_URL, { useValue: params.origin }), - ngCore.provide(REQUEST_URL, { useValue: params.url }), - ...ngUniversal.NODE_HTTP_PROVIDERS, - provideRouter(routes), - ...ngUniversal.NODE_LOCATION_PROVIDERS, - ], - // TODO: Render just the component instead of wrapping it inside an extra HTML document - // Waiting on https://github.com/angular/universal/issues/347 - template: '\n' +// enable prod for faster renders +enableProdMode(); + +declare var Zone: any; + +export default function (params: any) : Promise<{ html: string, globals?: any }> { + + const doc = ` + \n + + + + + + + `; + + // hold platform reference + var platformRef = platformNodeDynamic(); + + var platformConfig = { + ngModule: MainModule, + document: doc, + preboot: false, + baseUrl: '/', + requestUrl: params.url, + originUrl: params.origin }; - return bootloader.serializeApplication(config).then(html => { + // defaults + var cancel = false; + + const _config = Object.assign({ + get cancel() { return cancel; }, + cancelHandler() { return Zone.current.get('cancel') } + }, platformConfig); + + // for each user + const zone = Zone.current.fork({ + name: 'UNIVERSAL request', + properties: _config + }); + + + return Promise.resolve( + zone.run(() => { + return platformRef.serializeModule(Zone.current.get('ngModule')) + }) + ).then(html => { + + if (typeof html !== 'string' ) { + return { html : doc }; + } return { html }; + + }).catch(err => { + + console.log(err); + return { html : doc }; + }); + } diff --git a/templates/Angular2Spa/ClientApp/components/app/app.ts b/templates/Angular2Spa/ClientApp/components/app/app.ts index d6fc8b96..4ef528ee 100644 --- a/templates/Angular2Spa/ClientApp/components/app/app.ts +++ b/templates/Angular2Spa/ClientApp/components/app/app.ts @@ -1,11 +1,9 @@ -import * as ng from '@angular/core'; -import { ROUTER_DIRECTIVES } from '@angular/router'; +import { Component } from '@angular/core'; import { NavMenu } from '../nav-menu/nav-menu'; -@ng.Component({ +@Component({ selector: 'app', - template: require('./app.html'), - directives: [...ROUTER_DIRECTIVES, NavMenu] + template: require('./app.html') }) export class App { } diff --git a/templates/Angular2Spa/ClientApp/components/counter/counter.ts b/templates/Angular2Spa/ClientApp/components/counter/counter.ts index 00b4b952..4687dd2d 100644 --- a/templates/Angular2Spa/ClientApp/components/counter/counter.ts +++ b/templates/Angular2Spa/ClientApp/components/counter/counter.ts @@ -1,6 +1,6 @@ -import * as ng from '@angular/core'; +import { Component } from '@angular/core'; -@ng.Component({ +@Component({ selector: 'counter', template: require('./counter.html') }) diff --git a/templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.ts b/templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.ts index cede7ebc..ada8d432 100644 --- a/templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.ts +++ b/templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.ts @@ -1,7 +1,7 @@ -import * as ng from '@angular/core'; +import { Component } from '@angular/core'; import { Http } from '@angular/http'; -@ng.Component({ +@Component({ selector: 'fetch-data', template: require('./fetch-data.html') }) diff --git a/templates/Angular2Spa/ClientApp/components/home/home.ts b/templates/Angular2Spa/ClientApp/components/home/home.ts index 656f7573..1d41bfd4 100644 --- a/templates/Angular2Spa/ClientApp/components/home/home.ts +++ b/templates/Angular2Spa/ClientApp/components/home/home.ts @@ -1,6 +1,6 @@ -import * as ng from '@angular/core'; +import { Component }from '@angular/core'; -@ng.Component({ +@Component({ selector: 'home', template: require('./home.html') }) diff --git a/templates/Angular2Spa/ClientApp/components/index.ts b/templates/Angular2Spa/ClientApp/components/index.ts new file mode 100644 index 00000000..08cee111 --- /dev/null +++ b/templates/Angular2Spa/ClientApp/components/index.ts @@ -0,0 +1,8 @@ +// Here we can create "Barrels" so that it's easier to import everything +// within /components + +export * from './app/app'; +export * from './counter/counter'; +export * from './fetch-data/fetch-data'; +export * from './home/home'; +export * from './nav-menu/nav-menu'; \ No newline at end of file diff --git a/templates/Angular2Spa/ClientApp/components/nav-menu/nav-menu.ts b/templates/Angular2Spa/ClientApp/components/nav-menu/nav-menu.ts index 9c525ec5..d57ed47c 100644 --- a/templates/Angular2Spa/ClientApp/components/nav-menu/nav-menu.ts +++ b/templates/Angular2Spa/ClientApp/components/nav-menu/nav-menu.ts @@ -1,10 +1,8 @@ -import * as ng from '@angular/core'; -import { ROUTER_DIRECTIVES } from '@angular/router'; +import { Component } from '@angular/core'; -@ng.Component({ +@Component({ selector: 'nav-menu', - template: require('./nav-menu.html'), - directives: [...ROUTER_DIRECTIVES] + template: require('./nav-menu.html') }) export class NavMenu { } diff --git a/templates/Angular2Spa/ClientApp/main.browser.ts b/templates/Angular2Spa/ClientApp/main.browser.ts new file mode 100644 index 00000000..bf2f53ec --- /dev/null +++ b/templates/Angular2Spa/ClientApp/main.browser.ts @@ -0,0 +1,55 @@ +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; +import { UniversalModule } from 'angular2-universal'; + +import { + App, + Counter, + FetchData, + Home, + NavMenu +} from './components'; + +import { routes } from './routes'; + +/* NOTE : + + This file and `main.node.ts` are identical, at the moment(!) + By splitting these, you're able to create logic, imports, etc + that are "Platform" specific. + + If you want your code to be completely Universal and don't need that + You can also just have 1 file, that is imported into both + * boot-client + * boot-server + +*/ + +// ** Top-level NgModule "container" ** +@NgModule({ + + // Root App Component + bootstrap: [ App ], + + // Our Components + declarations: [ + App, Counter, FetchData, Home, NavMenu + ], + + imports: [ + + // * NOTE: Needs to be your first import (!) + UniversalModule, + // * ^ BrowserModule, HttpModule, and JsonpModule are included here + + // Your other imports can go here : + FormsModule, + + // App Routing + RouterModule.forRoot(routes) + ] +}) +export class MainModule { + +} diff --git a/templates/Angular2Spa/ClientApp/main.node.ts b/templates/Angular2Spa/ClientApp/main.node.ts new file mode 100644 index 00000000..8a5edbe4 --- /dev/null +++ b/templates/Angular2Spa/ClientApp/main.node.ts @@ -0,0 +1,55 @@ +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; +import { UniversalModule } from 'angular2-universal'; + +import { + App, + Counter, + FetchData, + Home, + NavMenu +} from './components'; + +import { routes } from './routes'; + +/* NOTE : + + This file and `main.browser.ts` are identical, at the moment(!) + By splitting these, you're able to create logic, imports, etc + that are "Platform" specific. + + If you want your code to be completely Universal and don't need that + You can also just have 1 file, that is imported into both + * boot-client + * boot-server + +*/ + +// ** Top-level NgModule "container" ** +@NgModule({ + + // Root App Component + bootstrap: [ App ], + + // Our Components + declarations: [ + App, Counter, FetchData, Home, NavMenu + ], + + imports: [ + + // * NOTE: Needs to be your first import (!) + UniversalModule, + // ^ NodeModule, NodeHttpModule, NodeJsonpModule are included for server + + // Your other imports can go here: + FormsModule, + + // App Routing + RouterModule.forRoot(routes) + ] +}) +export class MainModule { + +} diff --git a/templates/Angular2Spa/ClientApp/routes.ts b/templates/Angular2Spa/ClientApp/routes.ts index c0a45b73..160ae4f7 100644 --- a/templates/Angular2Spa/ClientApp/routes.ts +++ b/templates/Angular2Spa/ClientApp/routes.ts @@ -1,9 +1,8 @@ -import { RouterConfig } from '@angular/router'; -import { Home } from './components/home/home'; -import { FetchData } from './components/fetch-data/fetch-data'; -import { Counter } from './components/counter/counter'; +import { Routes } from '@angular/router'; -export const routes: RouterConfig = [ +import { Home, FetchData, Counter } from './components'; + +export const routes: Routes = [ { path: '', redirectTo: 'home', pathMatch: 'full' }, { path: 'home', component: Home }, { path: 'counter', component: Counter }, diff --git a/templates/Angular2Spa/package.json b/templates/Angular2Spa/package.json index 04fab270..966d07a8 100644 --- a/templates/Angular2Spa/package.json +++ b/templates/Angular2Spa/package.json @@ -1,38 +1,53 @@ { - "name": "WebApplicationBasic", + "name": "Angular2Spa", "version": "0.0.0", + "devDependencies": { + "@types/body-parser": "0.0.29", + "@types/compression": "0.0.29", + "@types/cookie-parser": "^1.3.29", + "@types/express": "^4.0.32", + "@types/express-serve-static-core": "^4.0.33", + "@types/hammerjs": "^2.0.32", + "@types/mime": "0.0.28", + "@types/node": "^6.0.38", + "@types/serve-static": "^1.7.27" + }, "dependencies": { - "@angular/common": "2.0.0-rc.4", - "@angular/compiler": "2.0.0-rc.4", - "@angular/core": "2.0.0-rc.4", - "@angular/http": "2.0.0-rc.4", - "@angular/platform-browser": "2.0.0-rc.4", - "@angular/platform-browser-dynamic": "2.0.0-rc.4", - "@angular/platform-server": "2.0.0-rc.4", - "@angular/router": "3.0.0-beta.2", - "angular2-universal": "^0.104.5", - "aspnet-prerendering": "^1.0.2", - "aspnet-webpack": "^1.0.6", - "bootstrap": "^3.3.6", + "@angular/common": "2.0.0", + "@angular/compiler": "2.0.0", + "@angular/core": "2.0.0", + "@angular/forms": "2.0.0", + "@angular/http": "2.0.0", + "@angular/platform-browser": "2.0.0", + "@angular/platform-browser-dynamic": "2.0.0", + "@angular/platform-server": "2.0.0", + "@angular/router": "3.0.0", + "angular2-platform-node": "~2.0.7", + "angular2-universal": "~2.0.7", + "angular2-universal-polyfills": "~2.0.7", + "angular2-express-engine": "~2.0.7", + "aspnet-prerendering": "^1.0.6", + "aspnet-webpack": "^1.0.11", + "bootstrap": "^3.3.7", "css": "^2.2.1", - "css-loader": "^0.23.1", + "css-loader": "^0.25.0", "es6-shim": "^0.35.1", "expose-loader": "^0.7.1", "extendify": "^1.0.0", "extract-text-webpack-plugin": "^1.0.1", - "file-loader": "^0.8.5", + "file-loader": "^0.9.0", "isomorphic-fetch": "^2.2.1", "jquery": "^2.2.1", - "preboot": "^2.0.10", + "preboot": "^4.5.2", "raw-loader": "^0.5.1", - "rxjs": "5.0.0-beta.6", + "rxjs": "5.0.0-beta.12", "style-loader": "^0.13.0", - "ts-loader": "^0.8.1", - "typescript": "^1.8.2", + "ts-loader": "^0.8.2", + "typescript": "^2.0.0", "url-loader": "^0.5.7", "webpack": "^1.12.14", "webpack-externals-plugin": "^1.0.0", "webpack-hot-middleware": "^2.10.0", - "zone.js": "^0.6.12" + "zone.js": "^0.6.21" } } diff --git a/templates/Angular2Spa/tsconfig.json b/templates/Angular2Spa/tsconfig.json index e141587c..0f72b020 100644 --- a/templates/Angular2Spa/tsconfig.json +++ b/templates/Angular2Spa/tsconfig.json @@ -5,10 +5,23 @@ "sourceMap": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, - "skipDefaultLibCheck": true + "skipDefaultLibCheck": true, + "lib": ["es6", "dom"], + "types": [ + "body-parser", + "compression", + "cookie-parser", + "express", + "express-serve-static-core", + "mime", + "node", + "serve-static", + "hammerjs" + ] }, "exclude": [ "bin", "node_modules" - ] + ], + "atom": { "rewriteTsconfig": false } } diff --git a/templates/Angular2Spa/tsd.json b/templates/Angular2Spa/tsd.json deleted file mode 100644 index b39437eb..00000000 --- a/templates/Angular2Spa/tsd.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "version": "v4", - "repo": "borisyankov/DefinitelyTyped", - "ref": "master", - "path": "typings", - "bundle": "typings/tsd.d.ts", - "installed": { - "requirejs/require.d.ts": { - "commit": "dade4414712ce84e3c63393f1aae407e9e7e6af7" - }, - "es6-shim/es6-shim.d.ts": { - "commit": "c0d876601e0f8236fd6b57626eb62c4e485f1563" - } - } -} diff --git a/templates/Angular2Spa/typings/url-workaround.d.ts b/templates/Angular2Spa/typings/custom-typings.d.ts similarity index 100% rename from templates/Angular2Spa/typings/url-workaround.d.ts rename to templates/Angular2Spa/typings/custom-typings.d.ts diff --git a/templates/Angular2Spa/typings/es6-shim/es6-shim.d.ts b/templates/Angular2Spa/typings/es6-shim/es6-shim.d.ts deleted file mode 100644 index 41f22997..00000000 --- a/templates/Angular2Spa/typings/es6-shim/es6-shim.d.ts +++ /dev/null @@ -1,668 +0,0 @@ -// Type definitions for es6-shim v0.31.2 -// Project: https://github.com/paulmillr/es6-shim -// Definitions by: Ron Buckton -// Definitions: https://github.com/borisyankov/DefinitelyTyped - -declare type PropertyKey = string | number | symbol; - -interface IteratorResult { - done: boolean; - value?: T; -} - -interface IterableShim { - /** - * Shim for an ES6 iterable. Not intended for direct use by user code. - */ - "_es6-shim iterator_"(): Iterator; -} - -interface Iterator { - next(value?: any): IteratorResult; - return?(value?: any): IteratorResult; - throw?(e?: any): IteratorResult; -} - -interface IterableIteratorShim extends IterableShim, Iterator { - /** - * Shim for an ES6 iterable iterator. Not intended for direct use by user code. - */ - "_es6-shim iterator_"(): IterableIteratorShim; -} - -interface StringConstructor { - /** - * Return the String value whose elements are, in order, the elements in the List elements. - * If length is 0, the empty string is returned. - */ - fromCodePoint(...codePoints: number[]): string; - - /** - * String.raw is intended for use as a tag function of a Tagged Template String. When called - * as such the first argument will be a well formed template call site object and the rest - * parameter will contain the substitution values. - * @param template A well-formed template string call site representation. - * @param substitutions A set of substitution values. - */ - raw(template: TemplateStringsArray, ...substitutions: any[]): string; -} - -interface String { - /** - * Returns a nonnegative integer Number less than 1114112 (0x110000) that is the code point - * value of the UTF-16 encoded code point starting at the string element at position pos in - * the String resulting from converting this object to a String. - * If there is no element at that position, the result is undefined. - * If a valid UTF-16 surrogate pair does not begin at pos, the result is the code unit at pos. - */ - codePointAt(pos: number): number; - - /** - * Returns true if searchString appears as a substring of the result of converting this - * object to a String, at one or more positions that are - * greater than or equal to position; otherwise, returns false. - * @param searchString search string - * @param position If position is undefined, 0 is assumed, so as to search all of the String. - */ - includes(searchString: string, position?: number): boolean; - - /** - * Returns true if the sequence of elements of searchString converted to a String is the - * same as the corresponding elements of this object (converted to a String) starting at - * endPosition – length(this). Otherwise returns false. - */ - endsWith(searchString: string, endPosition?: number): boolean; - - /** - * Returns a String value that is made from count copies appended together. If count is 0, - * T is the empty String is returned. - * @param count number of copies to append - */ - repeat(count: number): string; - - /** - * Returns true if the sequence of elements of searchString converted to a String is the - * same as the corresponding elements of this object (converted to a String) starting at - * position. Otherwise returns false. - */ - startsWith(searchString: string, position?: number): boolean; - - /** - * Returns an HTML anchor element and sets the name attribute to the text value - * @param name - */ - anchor(name: string): string; - - /** Returns a HTML element */ - big(): string; - - /** Returns a HTML element */ - blink(): string; - - /** Returns a HTML element */ - bold(): string; - - /** Returns a HTML element */ - fixed(): string - - /** Returns a HTML element and sets the color attribute value */ - fontcolor(color: string): string - - /** Returns a HTML element and sets the size attribute value */ - fontsize(size: number): string; - - /** Returns a HTML element and sets the size attribute value */ - fontsize(size: string): string; - - /** Returns an HTML element */ - italics(): string; - - /** Returns an HTML element and sets the href attribute value */ - link(url: string): string; - - /** Returns a HTML element */ - small(): string; - - /** Returns a HTML element */ - strike(): string; - - /** Returns a HTML element */ - sub(): string; - - /** Returns a HTML element */ - sup(): string; - - /** - * Shim for an ES6 iterable. Not intended for direct use by user code. - */ - "_es6-shim iterator_"(): IterableIteratorShim; -} - -interface ArrayConstructor { - /** - * Creates an array from an array-like object. - * @param arrayLike An array-like object to convert to an array. - * @param mapfn A mapping function to call on every element of the array. - * @param thisArg Value of 'this' used to invoke the mapfn. - */ - from(arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): Array; - - /** - * Creates an array from an iterable object. - * @param iterable An iterable object to convert to an array. - * @param mapfn A mapping function to call on every element of the array. - * @param thisArg Value of 'this' used to invoke the mapfn. - */ - from(iterable: IterableShim, mapfn: (v: T, k: number) => U, thisArg?: any): Array; - - /** - * Creates an array from an array-like object. - * @param arrayLike An array-like object to convert to an array. - */ - from(arrayLike: ArrayLike): Array; - - /** - * Creates an array from an iterable object. - * @param iterable An iterable object to convert to an array. - */ - from(iterable: IterableShim): Array; - - /** - * Returns a new array from a set of elements. - * @param items A set of elements to include in the new array object. - */ - of(...items: T[]): Array; -} - -interface Array { - /** - * Returns the value of the first element in the array where predicate is true, and undefined - * otherwise. - * @param predicate find calls predicate once for each element of the array, in ascending - * order, until it finds one where predicate returns true. If such an element is found, find - * immediately returns that element value. Otherwise, find returns undefined. - * @param thisArg If provided, it will be used as the this value for each invocation of - * predicate. If it is not provided, undefined is used instead. - */ - find(predicate: (value: T, index: number, obj: Array) => boolean, thisArg?: any): T; - - /** - * Returns the index of the first element in the array where predicate is true, and undefined - * otherwise. - * @param predicate find calls predicate once for each element of the array, in ascending - * order, until it finds one where predicate returns true. If such an element is found, find - * immediately returns that element value. Otherwise, find returns undefined. - * @param thisArg If provided, it will be used as the this value for each invocation of - * predicate. If it is not provided, undefined is used instead. - */ - findIndex(predicate: (value: T) => boolean, thisArg?: any): number; - - /** - * Returns the this object after filling the section identified by start and end with value - * @param value value to fill array section with - * @param start index to start filling the array at. If start is negative, it is treated as - * length+start where length is the length of the array. - * @param end index to stop filling the array at. If end is negative, it is treated as - * length+end. - */ - fill(value: T, start?: number, end?: number): T[]; - - /** - * Returns the this object after copying a section of the array identified by start and end - * to the same array starting at position target - * @param target If target is negative, it is treated as length+target where length is the - * length of the array. - * @param start If start is negative, it is treated as length+start. If end is negative, it - * is treated as length+end. - * @param end If not specified, length of the this object is used as its default value. - */ - copyWithin(target: number, start: number, end?: number): T[]; - - /** - * Returns an array of key, value pairs for every entry in the array - */ - entries(): IterableIteratorShim<[number, T]>; - - /** - * Returns an list of keys in the array - */ - keys(): IterableIteratorShim; - - /** - * Returns an list of values in the array - */ - values(): IterableIteratorShim; - - /** - * Shim for an ES6 iterable. Not intended for direct use by user code. - */ - "_es6-shim iterator_"(): IterableIteratorShim; -} - -interface NumberConstructor { - /** - * The value of Number.EPSILON is the difference between 1 and the smallest value greater than 1 - * that is representable as a Number value, which is approximately: - * 2.2204460492503130808472633361816 x 10‍−‍16. - */ - EPSILON: number; - - /** - * Returns true if passed value is finite. - * Unlike the global isFininte, Number.isFinite doesn't forcibly convert the parameter to a - * number. Only finite values of the type number, result in true. - * @param number A numeric value. - */ - isFinite(number: number): boolean; - - /** - * Returns true if the value passed is an integer, false otherwise. - * @param number A numeric value. - */ - isInteger(number: number): boolean; - - /** - * Returns a Boolean value that indicates whether a value is the reserved value NaN (not a - * number). Unlike the global isNaN(), Number.isNaN() doesn't forcefully convert the parameter - * to a number. Only values of the type number, that are also NaN, result in true. - * @param number A numeric value. - */ - isNaN(number: number): boolean; - - /** - * Returns true if the value passed is a safe integer. - * @param number A numeric value. - */ - isSafeInteger(number: number): boolean; - - /** - * The value of the largest integer n such that n and n + 1 are both exactly representable as - * a Number value. - * The value of Number.MIN_SAFE_INTEGER is 9007199254740991 2^53 − 1. - */ - MAX_SAFE_INTEGER: number; - - /** - * The value of the smallest integer n such that n and n − 1 are both exactly representable as - * a Number value. - * The value of Number.MIN_SAFE_INTEGER is −9007199254740991 (−(2^53 − 1)). - */ - MIN_SAFE_INTEGER: number; - - /** - * Converts a string to a floating-point number. - * @param string A string that contains a floating-point number. - */ - parseFloat(string: string): number; - - /** - * Converts A string to an integer. - * @param s A string to convert into a number. - * @param radix A value between 2 and 36 that specifies the base of the number in numString. - * If this argument is not supplied, strings with a prefix of '0x' are considered hexadecimal. - * All other strings are considered decimal. - */ - parseInt(string: string, radix?: number): number; -} - -interface ObjectConstructor { - /** - * Copy the values of all of the enumerable own properties from one or more source objects to a - * target object. Returns the target object. - * @param target The target object to copy to. - * @param sources One or more source objects to copy properties from. - */ - assign(target: any, ...sources: any[]): any; - - /** - * Returns true if the values are the same value, false otherwise. - * @param value1 The first value. - * @param value2 The second value. - */ - is(value1: any, value2: any): boolean; - - /** - * Sets the prototype of a specified object o to object proto or null. Returns the object o. - * @param o The object to change its prototype. - * @param proto The value of the new prototype or null. - * @remarks Requires `__proto__` support. - */ - setPrototypeOf(o: any, proto: any): any; -} - -interface RegExp { - /** - * Returns a string indicating the flags of the regular expression in question. This field is read-only. - * The characters in this string are sequenced and concatenated in the following order: - * - * - "g" for global - * - "i" for ignoreCase - * - "m" for multiline - * - "u" for unicode - * - "y" for sticky - * - * If no flags are set, the value is the empty string. - */ - flags: string; -} - -interface Math { - /** - * Returns the number of leading zero bits in the 32-bit binary representation of a number. - * @param x A numeric expression. - */ - clz32(x: number): number; - - /** - * Returns the result of 32-bit multiplication of two numbers. - * @param x First number - * @param y Second number - */ - imul(x: number, y: number): number; - - /** - * Returns the sign of the x, indicating whether x is positive, negative or zero. - * @param x The numeric expression to test - */ - sign(x: number): number; - - /** - * Returns the base 10 logarithm of a number. - * @param x A numeric expression. - */ - log10(x: number): number; - - /** - * Returns the base 2 logarithm of a number. - * @param x A numeric expression. - */ - log2(x: number): number; - - /** - * Returns the natural logarithm of 1 + x. - * @param x A numeric expression. - */ - log1p(x: number): number; - - /** - * Returns the result of (e^x - 1) of x (e raised to the power of x, where e is the base of - * the natural logarithms). - * @param x A numeric expression. - */ - expm1(x: number): number; - - /** - * Returns the hyperbolic cosine of a number. - * @param x A numeric expression that contains an angle measured in radians. - */ - cosh(x: number): number; - - /** - * Returns the hyperbolic sine of a number. - * @param x A numeric expression that contains an angle measured in radians. - */ - sinh(x: number): number; - - /** - * Returns the hyperbolic tangent of a number. - * @param x A numeric expression that contains an angle measured in radians. - */ - tanh(x: number): number; - - /** - * Returns the inverse hyperbolic cosine of a number. - * @param x A numeric expression that contains an angle measured in radians. - */ - acosh(x: number): number; - - /** - * Returns the inverse hyperbolic sine of a number. - * @param x A numeric expression that contains an angle measured in radians. - */ - asinh(x: number): number; - - /** - * Returns the inverse hyperbolic tangent of a number. - * @param x A numeric expression that contains an angle measured in radians. - */ - atanh(x: number): number; - - /** - * Returns the square root of the sum of squares of its arguments. - * @param values Values to compute the square root for. - * If no arguments are passed, the result is +0. - * If there is only one argument, the result is the absolute value. - * If any argument is +Infinity or -Infinity, the result is +Infinity. - * If any argument is NaN, the result is NaN. - * If all arguments are either +0 or −0, the result is +0. - */ - hypot(...values: number[]): number; - - /** - * Returns the integral part of the a numeric expression, x, removing any fractional digits. - * If x is already an integer, the result is x. - * @param x A numeric expression. - */ - trunc(x: number): number; - - /** - * Returns the nearest single precision float representation of a number. - * @param x A numeric expression. - */ - fround(x: number): number; - - /** - * Returns an implementation-dependent approximation to the cube root of number. - * @param x A numeric expression. - */ - cbrt(x: number): number; -} - -interface PromiseLike { - /** - * Attaches callbacks for the resolution and/or rejection of the Promise. - * @param onfulfilled The callback to execute when the Promise is resolved. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of which ever callback is executed. - */ - then(onfulfilled?: (value: T) => TResult | PromiseLike, onrejected?: (reason: any) => TResult | PromiseLike): PromiseLike; - then(onfulfilled?: (value: T) => TResult | PromiseLike, onrejected?: (reason: any) => void): PromiseLike; -} - -/** - * Represents the completion of an asynchronous operation - */ -interface Promise { - /** - * Attaches callbacks for the resolution and/or rejection of the Promise. - * @param onfulfilled The callback to execute when the Promise is resolved. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of which ever callback is executed. - */ - then(onfulfilled?: (value: T) => TResult | PromiseLike, onrejected?: (reason: any) => TResult | PromiseLike): Promise; - then(onfulfilled?: (value: T) => TResult | PromiseLike, onrejected?: (reason: any) => void): Promise; - - /** - * Attaches a callback for only the rejection of the Promise. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of the callback. - */ - catch(onrejected?: (reason: any) => T | PromiseLike): Promise; - catch(onrejected?: (reason: any) => void): Promise; -} - -interface PromiseConstructor { - /** - * A reference to the prototype. - */ - prototype: Promise; - - /** - * Creates a new Promise. - * @param executor A callback used to initialize the promise. This callback is passed two arguments: - * a resolve callback used resolve the promise with a value or the result of another promise, - * and a reject callback used to reject the promise with a provided reason or error. - */ - new (executor: (resolve: (value?: T | PromiseLike) => void, reject: (reason?: any) => void) => void): Promise; - - /** - * Creates a Promise that is resolved with an array of results when all of the provided Promises - * resolve, or rejected when any Promise is rejected. - * @param values An array of Promises. - * @returns A new Promise. - */ - all(values: IterableShim>): Promise; - - /** - * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved - * or rejected. - * @param values An array of Promises. - * @returns A new Promise. - */ - race(values: IterableShim>): Promise; - - /** - * Creates a new rejected promise for the provided reason. - * @param reason The reason the promise was rejected. - * @returns A new rejected Promise. - */ - reject(reason: any): Promise; - - /** - * Creates a new rejected promise for the provided reason. - * @param reason The reason the promise was rejected. - * @returns A new rejected Promise. - */ - reject(reason: any): Promise; - - /** - * Creates a new resolved promise for the provided value. - * @param value A promise. - * @returns A promise whose internal state matches the provided promise. - */ - resolve(value: T | PromiseLike): Promise; - - /** - * Creates a new resolved promise . - * @returns A resolved promise. - */ - resolve(): Promise; -} - -declare var Promise: PromiseConstructor; - -interface Map { - clear(): void; - delete(key: K): boolean; - forEach(callbackfn: (value: V, index: K, map: Map) => void, thisArg?: any): void; - get(key: K): V; - has(key: K): boolean; - set(key: K, value?: V): Map; - size: number; - entries(): IterableIteratorShim<[K, V]>; - keys(): IterableIteratorShim; - values(): IterableIteratorShim; -} - -interface MapConstructor { - new (): Map; - new (iterable: IterableShim<[K, V]>): Map; - prototype: Map; -} - -declare var Map: MapConstructor; - -interface Set { - add(value: T): Set; - clear(): void; - delete(value: T): boolean; - forEach(callbackfn: (value: T, index: T, set: Set) => void, thisArg?: any): void; - has(value: T): boolean; - size: number; - entries(): IterableIteratorShim<[T, T]>; - keys(): IterableIteratorShim; - values(): IterableIteratorShim; -} - -interface SetConstructor { - new (): Set; - new (iterable: IterableShim): Set; - prototype: Set; -} - -declare var Set: SetConstructor; - -interface WeakMap { - delete(key: K): boolean; - get(key: K): V; - has(key: K): boolean; - set(key: K, value?: V): WeakMap; -} - -interface WeakMapConstructor { - new (): WeakMap; - new (iterable: IterableShim<[K, V]>): WeakMap; - prototype: WeakMap; -} - -declare var WeakMap: WeakMapConstructor; - -interface WeakSet { - add(value: T): WeakSet; - delete(value: T): boolean; - has(value: T): boolean; -} - -interface WeakSetConstructor { - new (): WeakSet; - new (iterable: IterableShim): WeakSet; - prototype: WeakSet; -} - -declare var WeakSet: WeakSetConstructor; - -declare module Reflect { - function apply(target: Function, thisArgument: any, argumentsList: ArrayLike): any; - function construct(target: Function, argumentsList: ArrayLike): any; - function defineProperty(target: any, propertyKey: PropertyKey, attributes: PropertyDescriptor): boolean; - function deleteProperty(target: any, propertyKey: PropertyKey): boolean; - function enumerate(target: any): IterableIteratorShim; - function get(target: any, propertyKey: PropertyKey, receiver?: any): any; - function getOwnPropertyDescriptor(target: any, propertyKey: PropertyKey): PropertyDescriptor; - function getPrototypeOf(target: any): any; - function has(target: any, propertyKey: PropertyKey): boolean; - function isExtensible(target: any): boolean; - function ownKeys(target: any): Array; - function preventExtensions(target: any): boolean; - function set(target: any, propertyKey: PropertyKey, value: any, receiver?: any): boolean; - function setPrototypeOf(target: any, proto: any): boolean; -} - -declare module "es6-shim" { - var String: StringConstructor; - var Array: ArrayConstructor; - var Number: NumberConstructor; - var Math: Math; - var Object: ObjectConstructor; - var Map: MapConstructor; - var Set: SetConstructor; - var WeakMap: WeakMapConstructor; - var WeakSet: WeakSetConstructor; - var Promise: PromiseConstructor; - module Reflect { - function apply(target: Function, thisArgument: any, argumentsList: ArrayLike): any; - function construct(target: Function, argumentsList: ArrayLike): any; - function defineProperty(target: any, propertyKey: PropertyKey, attributes: PropertyDescriptor): boolean; - function deleteProperty(target: any, propertyKey: PropertyKey): boolean; - function enumerate(target: any): Iterator; - function get(target: any, propertyKey: PropertyKey, receiver?: any): any; - function getOwnPropertyDescriptor(target: any, propertyKey: PropertyKey): PropertyDescriptor; - function getPrototypeOf(target: any): any; - function has(target: any, propertyKey: PropertyKey): boolean; - function isExtensible(target: any): boolean; - function ownKeys(target: any): Array; - function preventExtensions(target: any): boolean; - function set(target: any, propertyKey: PropertyKey, value: any, receiver?: any): boolean; - function setPrototypeOf(target: any, proto: any): boolean; - } -} diff --git a/templates/Angular2Spa/typings/requirejs/require.d.ts b/templates/Angular2Spa/typings/requirejs/require.d.ts deleted file mode 100644 index e9cdb3e8..00000000 --- a/templates/Angular2Spa/typings/requirejs/require.d.ts +++ /dev/null @@ -1,397 +0,0 @@ -// Type definitions for RequireJS 2.1.20 -// Project: http://requirejs.org/ -// Definitions by: Josh Baldwin -// Definitions: https://github.com/borisyankov/DefinitelyTyped - -/* -require-2.1.8.d.ts may be freely distributed under the MIT license. - -Copyright (c) 2013 Josh Baldwin https://github.com/jbaldwin/require.d.ts - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. -*/ - -declare module 'module' { - var mod: { - config: () => any; - id: string; - uri: string; - } - export = mod; -} - -interface RequireError extends Error { - - /** - * The error ID that maps to an ID on a web page. - **/ - requireType: string; - - /** - * Required modules. - **/ - requireModules: string[]; - - /** - * The original error, if there is one (might be null). - **/ - originalError: Error; -} - -interface RequireShim { - - /** - * List of dependencies. - **/ - deps?: string[]; - - /** - * Name the module will be exported as. - **/ - exports?: string; - - /** - * Initialize function with all dependcies passed in, - * if the function returns a value then that value is used - * as the module export value instead of the object - * found via the 'exports' string. - * @param dependencies - * @return - **/ - init?: (...dependencies: any[]) => any; -} - -interface RequireConfig { - - // The root path to use for all module lookups. - baseUrl?: string; - - // Path mappings for module names not found directly under - // baseUrl. - paths?: { [key: string]: any; }; - - - // Dictionary of Shim's. - // does not cover case of key->string[] - shim?: { [key: string]: RequireShim; }; - - /** - * For the given module prefix, instead of loading the - * module with the given ID, substitude a different - * module ID. - * - * @example - * requirejs.config({ - * map: { - * 'some/newmodule': { - * 'foo': 'foo1.2' - * }, - * 'some/oldmodule': { - * 'foo': 'foo1.0' - * } - * } - * }); - **/ - map?: { - [id: string]: { - [id: string]: string; - }; - }; - - /** - * Allows pointing multiple module IDs to a module ID that contains a bundle of modules. - * - * @example - * requirejs.config({ - * bundles: { - * 'primary': ['main', 'util', 'text', 'text!template.html'], - * 'secondary': ['text!secondary.html'] - * } - * }); - **/ - bundles?: { [key: string]: string[]; }; - - /** - * AMD configurations, use module.config() to access in - * define() functions - **/ - config?: { [id: string]: {}; }; - - /** - * Configures loading modules from CommonJS packages. - **/ - packages?: {}; - - /** - * The number of seconds to wait before giving up on loading - * a script. The default is 7 seconds. - **/ - waitSeconds?: number; - - /** - * A name to give to a loading context. This allows require.js - * to load multiple versions of modules in a page, as long as - * each top-level require call specifies a unique context string. - **/ - context?: string; - - /** - * An array of dependencies to load. - **/ - deps?: string[]; - - /** - * A function to pass to require that should be require after - * deps have been loaded. - * @param modules - **/ - callback?: (...modules: any[]) => void; - - /** - * If set to true, an error will be thrown if a script loads - * that does not call define() or have shim exports string - * value that can be checked. - **/ - enforceDefine?: boolean; - - /** - * If set to true, document.createElementNS() will be used - * to create script elements. - **/ - xhtml?: boolean; - - /** - * Extra query string arguments appended to URLs that RequireJS - * uses to fetch resources. Most useful to cache bust when - * the browser or server is not configured correctly. - * - * @example - * urlArgs: "bust= + (new Date()).getTime() - **/ - urlArgs?: string; - - /** - * Specify the value for the type="" attribute used for script - * tags inserted into the document by RequireJS. Default is - * "text/javascript". To use Firefox's JavasScript 1.8 - * features, use "text/javascript;version=1.8". - **/ - scriptType?: string; - - /** - * If set to true, skips the data-main attribute scanning done - * to start module loading. Useful if RequireJS is embedded in - * a utility library that may interact with other RequireJS - * library on the page, and the embedded version should not do - * data-main loading. - **/ - skipDataMain?: boolean; - - /** - * Allow extending requirejs to support Subresource Integrity - * (SRI). - **/ - onNodeCreated?: (node: HTMLScriptElement, config: RequireConfig, moduleName: string, url: string) => void; -} - -// todo: not sure what to do with this guy -interface RequireModule { - - /** - * - **/ - config(): {}; - -} - -/** -* -**/ -interface RequireMap { - - /** - * - **/ - prefix: string; - - /** - * - **/ - name: string; - - /** - * - **/ - parentMap: RequireMap; - - /** - * - **/ - url: string; - - /** - * - **/ - originalName: string; - - /** - * - **/ - fullName: string; -} - -interface Require { - - /** - * Configure require.js - **/ - config(config: RequireConfig): Require; - - /** - * CommonJS require call - * @param module Module to load - * @return The loaded module - */ - (module: string): any; - - /** - * Start the main app logic. - * Callback is optional. - * Can alternatively use deps and callback. - * @param modules Required modules to load. - **/ - (modules: string[]): void; - - /** - * @see Require() - * @param ready Called when required modules are ready. - **/ - (modules: string[], ready: Function): void; - - /** - * @see http://requirejs.org/docs/api.html#errbacks - * @param ready Called when required modules are ready. - **/ - (modules: string[], ready: Function, errback: Function): void; - - /** - * Generate URLs from require module - * @param module Module to URL - * @return URL string - **/ - toUrl(module: string): string; - - /** - * Returns true if the module has already been loaded and defined. - * @param module Module to check - **/ - defined(module: string): boolean; - - /** - * Returns true if the module has already been requested or is in the process of loading and should be available at some point. - * @param module Module to check - **/ - specified(module: string): boolean; - - /** - * On Error override - * @param err - **/ - onError(err: RequireError, errback?: (err: RequireError) => void): void; - - /** - * Undefine a module - * @param module Module to undefine. - **/ - undef(module: string): void; - - /** - * Semi-private function, overload in special instance of undef() - **/ - onResourceLoad(context: Object, map: RequireMap, depArray: RequireMap[]): void; -} - -interface RequireDefine { - - /** - * Define Simple Name/Value Pairs - * @param config Dictionary of Named/Value pairs for the config. - **/ - (config: { [key: string]: any; }): void; - - /** - * Define function. - * @param func: The function module. - **/ - (func: () => any): void; - - /** - * Define function with dependencies. - * @param deps List of dependencies module IDs. - * @param ready Callback function when the dependencies are loaded. - * callback param deps module dependencies - * callback return module definition - **/ - (deps: string[], ready: Function): void; - - /** - * Define module with simplified CommonJS wrapper. - * @param ready - * callback require requirejs instance - * callback exports exports object - * callback module module - * callback return module definition - **/ - (ready: (require: Require, exports: { [key: string]: any; }, module: RequireModule) => any): void; - - /** - * Define a module with a name and dependencies. - * @param name The name of the module. - * @param deps List of dependencies module IDs. - * @param ready Callback function when the dependencies are loaded. - * callback deps module dependencies - * callback return module definition - **/ - (name: string, deps: string[], ready: Function): void; - - /** - * Define a module with a name. - * @param name The name of the module. - * @param ready Callback function when the dependencies are loaded. - * callback return module definition - **/ - (name: string, ready: Function): void; - - /** - * Used to allow a clear indicator that a global define function (as needed for script src browser loading) conforms - * to the AMD API, any global define function SHOULD have a property called "amd" whose value is an object. - * This helps avoid conflict with any other existing JavaScript code that could have defined a define() function - * that does not conform to the AMD API. - * define.amd.jQuery is specific to jQuery and indicates that the loader is able to account for multiple version - * of jQuery being loaded simultaneously. - */ - amd: Object; -} - -// Ambient declarations for 'require' and 'define' -declare var requirejs: Require; -declare var require: Require; -declare var define: RequireDefine; diff --git a/templates/Angular2Spa/typings/tsd.d.ts b/templates/Angular2Spa/typings/tsd.d.ts deleted file mode 100644 index c5cfdc2b..00000000 --- a/templates/Angular2Spa/typings/tsd.d.ts +++ /dev/null @@ -1,3 +0,0 @@ - -/// -/// From 1e08548aa0f9654ac3698962116282b268ae9b98 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 10:33:02 +0100 Subject: [PATCH 033/748] Remove now-redundant 'typings' dir and custom-typings.d.ts --- templates/Angular2Spa/typings/custom-typings.d.ts | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 templates/Angular2Spa/typings/custom-typings.d.ts diff --git a/templates/Angular2Spa/typings/custom-typings.d.ts b/templates/Angular2Spa/typings/custom-typings.d.ts deleted file mode 100644 index 9d167602..00000000 --- a/templates/Angular2Spa/typings/custom-typings.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -// This file is a workaround for angular2-universal-preview version 0.84.2 relying on the declaration of -// Node's 'url' module. Ideally it would not declare dependencies on Node APIs except where it also supplies -// the definitions itself. - -declare module 'url' { - export interface Url {} -} From 243a9b4ef6dcf4cffe4780708ba89ea45e5bc271 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 11:05:44 +0100 Subject: [PATCH 034/748] Add @types/node to avoid intellisense errors for "require" statements --- templates/Angular2Spa/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/Angular2Spa/package.json b/templates/Angular2Spa/package.json index 966d07a8..4a899278 100644 --- a/templates/Angular2Spa/package.json +++ b/templates/Angular2Spa/package.json @@ -22,10 +22,11 @@ "@angular/platform-browser-dynamic": "2.0.0", "@angular/platform-server": "2.0.0", "@angular/router": "3.0.0", + "@types/node": "^6.0.38", + "angular2-express-engine": "~2.0.7", "angular2-platform-node": "~2.0.7", "angular2-universal": "~2.0.7", "angular2-universal-polyfills": "~2.0.7", - "angular2-express-engine": "~2.0.7", "aspnet-prerendering": "^1.0.6", "aspnet-webpack": "^1.0.11", "bootstrap": "^3.3.7", From 8f550c5706158dfb36a99c713b377acedbba8c47 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 12:44:25 +0100 Subject: [PATCH 035/748] Simplify Angular 2 template where possible --- .../Angular2Spa/ClientApp/app/app.module.ts | 31 +++++++ .../components/app/app.component.html} | 0 .../app/components/app/app.component.ts | 8 ++ .../counter/counter.component.html} | 0 .../components/counter/counter.component.ts} | 4 +- .../fetchdata/fetchdata.component.html} | 0 .../fetchdata/fetchdata.component.ts} | 6 +- .../components/home/home.component.html} | 0 .../app/components/home/home.component.ts | 8 ++ .../navmenu/navmenu.component.html} | 0 .../components/navmenu/navmenu.component.ts} | 4 +- .../Angular2Spa/ClientApp/boot-client.ts | 21 +---- .../Angular2Spa/ClientApp/boot-server.ts | 82 ++++--------------- .../ClientApp/components/app/app.ts | 9 -- .../ClientApp/components/home/home.ts | 8 -- .../Angular2Spa/ClientApp/components/index.ts | 8 -- .../Angular2Spa/ClientApp/main.browser.ts | 55 ------------- templates/Angular2Spa/ClientApp/main.node.ts | 55 ------------- templates/Angular2Spa/ClientApp/routes.ts | 11 --- templates/Angular2Spa/tsconfig.json | 19 +---- 20 files changed, 77 insertions(+), 252 deletions(-) create mode 100644 templates/Angular2Spa/ClientApp/app/app.module.ts rename templates/Angular2Spa/ClientApp/{components/app/app.html => app/components/app/app.component.html} (100%) create mode 100644 templates/Angular2Spa/ClientApp/app/components/app/app.component.ts rename templates/Angular2Spa/ClientApp/{components/counter/counter.html => app/components/counter/counter.component.html} (100%) rename templates/Angular2Spa/ClientApp/{components/counter/counter.ts => app/components/counter/counter.component.ts} (69%) rename templates/Angular2Spa/ClientApp/{components/fetch-data/fetch-data.html => app/components/fetchdata/fetchdata.component.html} (100%) rename templates/Angular2Spa/ClientApp/{components/fetch-data/fetch-data.ts => app/components/fetchdata/fetchdata.component.ts} (80%) rename templates/Angular2Spa/ClientApp/{components/home/home.html => app/components/home/home.component.html} (100%) create mode 100644 templates/Angular2Spa/ClientApp/app/components/home/home.component.ts rename templates/Angular2Spa/ClientApp/{components/nav-menu/nav-menu.html => app/components/navmenu/navmenu.component.html} (100%) rename templates/Angular2Spa/ClientApp/{components/nav-menu/nav-menu.ts => app/components/navmenu/navmenu.component.ts} (51%) delete mode 100644 templates/Angular2Spa/ClientApp/components/app/app.ts delete mode 100644 templates/Angular2Spa/ClientApp/components/home/home.ts delete mode 100644 templates/Angular2Spa/ClientApp/components/index.ts delete mode 100644 templates/Angular2Spa/ClientApp/main.browser.ts delete mode 100644 templates/Angular2Spa/ClientApp/main.node.ts delete mode 100644 templates/Angular2Spa/ClientApp/routes.ts diff --git a/templates/Angular2Spa/ClientApp/app/app.module.ts b/templates/Angular2Spa/ClientApp/app/app.module.ts new file mode 100644 index 00000000..23dcf581 --- /dev/null +++ b/templates/Angular2Spa/ClientApp/app/app.module.ts @@ -0,0 +1,31 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { UniversalModule } from 'angular2-universal'; +import { AppComponent } from './components/app/app.component' +import { NavMenuComponent } from './components/navmenu/navmenu.component'; +import { HomeComponent } from './components/home/home.component'; +import { FetchDataComponent } from './components/fetchdata/fetchdata.component'; +import { CounterComponent } from './components/counter/counter.component'; + +@NgModule({ + bootstrap: [ AppComponent ], + declarations: [ + AppComponent, + NavMenuComponent, + CounterComponent, + FetchDataComponent, + HomeComponent + ], + imports: [ + UniversalModule, + RouterModule.forRoot([ + { path: '', redirectTo: 'home', pathMatch: 'full' }, + { path: 'home', component: HomeComponent }, + { path: 'counter', component: CounterComponent }, + { path: 'fetch-data', component: FetchDataComponent }, + { path: '**', redirectTo: 'home' } + ]) + ] +}) +export class AppModule { +} diff --git a/templates/Angular2Spa/ClientApp/components/app/app.html b/templates/Angular2Spa/ClientApp/app/components/app/app.component.html similarity index 100% rename from templates/Angular2Spa/ClientApp/components/app/app.html rename to templates/Angular2Spa/ClientApp/app/components/app/app.component.html diff --git a/templates/Angular2Spa/ClientApp/app/components/app/app.component.ts b/templates/Angular2Spa/ClientApp/app/components/app/app.component.ts new file mode 100644 index 00000000..f1bd036c --- /dev/null +++ b/templates/Angular2Spa/ClientApp/app/components/app/app.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app', + template: require('./app.component.html') +}) +export class AppComponent { +} diff --git a/templates/Angular2Spa/ClientApp/components/counter/counter.html b/templates/Angular2Spa/ClientApp/app/components/counter/counter.component.html similarity index 100% rename from templates/Angular2Spa/ClientApp/components/counter/counter.html rename to templates/Angular2Spa/ClientApp/app/components/counter/counter.component.html diff --git a/templates/Angular2Spa/ClientApp/components/counter/counter.ts b/templates/Angular2Spa/ClientApp/app/components/counter/counter.component.ts similarity index 69% rename from templates/Angular2Spa/ClientApp/components/counter/counter.ts rename to templates/Angular2Spa/ClientApp/app/components/counter/counter.component.ts index 4687dd2d..03848360 100644 --- a/templates/Angular2Spa/ClientApp/components/counter/counter.ts +++ b/templates/Angular2Spa/ClientApp/app/components/counter/counter.component.ts @@ -2,9 +2,9 @@ import { Component } from '@angular/core'; @Component({ selector: 'counter', - template: require('./counter.html') + template: require('./counter.component.html') }) -export class Counter { +export class CounterComponent { public currentCount = 0; public incrementCounter() { diff --git a/templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.html b/templates/Angular2Spa/ClientApp/app/components/fetchdata/fetchdata.component.html similarity index 100% rename from templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.html rename to templates/Angular2Spa/ClientApp/app/components/fetchdata/fetchdata.component.html diff --git a/templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.ts b/templates/Angular2Spa/ClientApp/app/components/fetchdata/fetchdata.component.ts similarity index 80% rename from templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.ts rename to templates/Angular2Spa/ClientApp/app/components/fetchdata/fetchdata.component.ts index ada8d432..40de3d92 100644 --- a/templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.ts +++ b/templates/Angular2Spa/ClientApp/app/components/fetchdata/fetchdata.component.ts @@ -2,10 +2,10 @@ import { Component } from '@angular/core'; import { Http } from '@angular/http'; @Component({ - selector: 'fetch-data', - template: require('./fetch-data.html') + selector: 'fetchdata', + template: require('./fetchdata.component.html') }) -export class FetchData { +export class FetchDataComponent { public forecasts: WeatherForecast[]; constructor(http: Http) { diff --git a/templates/Angular2Spa/ClientApp/components/home/home.html b/templates/Angular2Spa/ClientApp/app/components/home/home.component.html similarity index 100% rename from templates/Angular2Spa/ClientApp/components/home/home.html rename to templates/Angular2Spa/ClientApp/app/components/home/home.component.html diff --git a/templates/Angular2Spa/ClientApp/app/components/home/home.component.ts b/templates/Angular2Spa/ClientApp/app/components/home/home.component.ts new file mode 100644 index 00000000..2152f0a5 --- /dev/null +++ b/templates/Angular2Spa/ClientApp/app/components/home/home.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'home', + template: require('./home.component.html') +}) +export class HomeComponent { +} diff --git a/templates/Angular2Spa/ClientApp/components/nav-menu/nav-menu.html b/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.html similarity index 100% rename from templates/Angular2Spa/ClientApp/components/nav-menu/nav-menu.html rename to templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.html diff --git a/templates/Angular2Spa/ClientApp/components/nav-menu/nav-menu.ts b/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.ts similarity index 51% rename from templates/Angular2Spa/ClientApp/components/nav-menu/nav-menu.ts rename to templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.ts index d57ed47c..3bcba94e 100644 --- a/templates/Angular2Spa/ClientApp/components/nav-menu/nav-menu.ts +++ b/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.ts @@ -2,7 +2,7 @@ import { Component } from '@angular/core'; @Component({ selector: 'nav-menu', - template: require('./nav-menu.html') + template: require('./navmenu.component.html') }) -export class NavMenu { +export class NavMenuComponent { } diff --git a/templates/Angular2Spa/ClientApp/boot-client.ts b/templates/Angular2Spa/ClientApp/boot-client.ts index 0889ed5c..ad0d29fa 100644 --- a/templates/Angular2Spa/ClientApp/boot-client.ts +++ b/templates/Angular2Spa/ClientApp/boot-client.ts @@ -1,30 +1,15 @@ -// the polyfills must be the first thing imported import 'angular2-universal-polyfills'; - import 'es6-shim'; -require('zone.js'); +import 'zone.js'; import 'bootstrap'; import 'reflect-metadata'; import './styles/site.css'; - -// Angular 2 import { enableProdMode} from '@angular/core'; import { platformUniversalDynamic } from 'angular2-universal'; +import { AppModule } from './app/app.module'; -// enable prod for faster renders enableProdMode(); - -import { MainModule } from './main.browser'; - -const platformRef = platformUniversalDynamic(); - -// on document ready bootstrap Angular 2 -document.addEventListener('DOMContentLoaded', () => { - - platformRef.bootstrapModule(MainModule); - -}); - +platformUniversalDynamic().bootstrapModule(AppModule); // Basic hot reloading support. Automatically reloads and restarts the Angular 2 app each time // you modify source files. This will not preserve any application state other than the URL. diff --git a/templates/Angular2Spa/ClientApp/boot-server.ts b/templates/Angular2Spa/ClientApp/boot-server.ts index de006549..0c4c3aa5 100644 --- a/templates/Angular2Spa/ClientApp/boot-server.ts +++ b/templates/Angular2Spa/ClientApp/boot-server.ts @@ -1,76 +1,28 @@ -// the polyfills must be the first thing imported in node.js import 'angular2-universal-polyfills'; - -// Angular 2 +import 'zone.js'; import { enableProdMode } from '@angular/core'; -// Angular2 Universal import { platformNodeDynamic } from 'angular2-universal'; +import { AppModule } from './app/app.module'; -// Application imports -import { MainModule } from './main.node'; -import { App } from './components'; -import { routes } from './routes'; - -// enable prod for faster renders enableProdMode(); - -declare var Zone: any; +const platform = platformNodeDynamic(); export default function (params: any) : Promise<{ html: string, globals?: any }> { - - const doc = ` - \n - - - - - - - `; - - // hold platform reference - var platformRef = platformNodeDynamic(); - - var platformConfig = { - ngModule: MainModule, - document: doc, - preboot: false, - baseUrl: '/', - requestUrl: params.url, - originUrl: params.origin - }; - - // defaults - var cancel = false; - - const _config = Object.assign({ - get cancel() { return cancel; }, - cancelHandler() { return Zone.current.get('cancel') } - }, platformConfig); - - // for each user - const zone = Zone.current.fork({ - name: 'UNIVERSAL request', - properties: _config - }); - - - return Promise.resolve( - zone.run(() => { - return platformRef.serializeModule(Zone.current.get('ngModule')) - }) - ).then(html => { - - if (typeof html !== 'string' ) { - return { html : doc }; + const requestZone = Zone.current.fork({ + name: 'angular-universal request', + properties: { + baseUrl: '/', + requestUrl: params.url, + originUrl: params.origin, + preboot: false, + // TODO: Render just the component instead of wrapping it inside an extra HTML document + // Waiting on https://github.com/angular/universal/issues/347 + document: '' } - return { html }; - - }).catch(err => { - - console.log(err); - return { html : doc }; - }); + return requestZone.run>(() => platform.serializeModule(AppModule)) + .then(html => { + return { html: html }; + }); } diff --git a/templates/Angular2Spa/ClientApp/components/app/app.ts b/templates/Angular2Spa/ClientApp/components/app/app.ts deleted file mode 100644 index 4ef528ee..00000000 --- a/templates/Angular2Spa/ClientApp/components/app/app.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component } from '@angular/core'; -import { NavMenu } from '../nav-menu/nav-menu'; - -@Component({ - selector: 'app', - template: require('./app.html') -}) -export class App { -} diff --git a/templates/Angular2Spa/ClientApp/components/home/home.ts b/templates/Angular2Spa/ClientApp/components/home/home.ts deleted file mode 100644 index 1d41bfd4..00000000 --- a/templates/Angular2Spa/ClientApp/components/home/home.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Component }from '@angular/core'; - -@Component({ - selector: 'home', - template: require('./home.html') -}) -export class Home { -} diff --git a/templates/Angular2Spa/ClientApp/components/index.ts b/templates/Angular2Spa/ClientApp/components/index.ts deleted file mode 100644 index 08cee111..00000000 --- a/templates/Angular2Spa/ClientApp/components/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Here we can create "Barrels" so that it's easier to import everything -// within /components - -export * from './app/app'; -export * from './counter/counter'; -export * from './fetch-data/fetch-data'; -export * from './home/home'; -export * from './nav-menu/nav-menu'; \ No newline at end of file diff --git a/templates/Angular2Spa/ClientApp/main.browser.ts b/templates/Angular2Spa/ClientApp/main.browser.ts deleted file mode 100644 index bf2f53ec..00000000 --- a/templates/Angular2Spa/ClientApp/main.browser.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { NgModule } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { RouterModule } from '@angular/router'; -import { UniversalModule } from 'angular2-universal'; - -import { - App, - Counter, - FetchData, - Home, - NavMenu -} from './components'; - -import { routes } from './routes'; - -/* NOTE : - - This file and `main.node.ts` are identical, at the moment(!) - By splitting these, you're able to create logic, imports, etc - that are "Platform" specific. - - If you want your code to be completely Universal and don't need that - You can also just have 1 file, that is imported into both - * boot-client - * boot-server - -*/ - -// ** Top-level NgModule "container" ** -@NgModule({ - - // Root App Component - bootstrap: [ App ], - - // Our Components - declarations: [ - App, Counter, FetchData, Home, NavMenu - ], - - imports: [ - - // * NOTE: Needs to be your first import (!) - UniversalModule, - // * ^ BrowserModule, HttpModule, and JsonpModule are included here - - // Your other imports can go here : - FormsModule, - - // App Routing - RouterModule.forRoot(routes) - ] -}) -export class MainModule { - -} diff --git a/templates/Angular2Spa/ClientApp/main.node.ts b/templates/Angular2Spa/ClientApp/main.node.ts deleted file mode 100644 index 8a5edbe4..00000000 --- a/templates/Angular2Spa/ClientApp/main.node.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { NgModule } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { RouterModule } from '@angular/router'; -import { UniversalModule } from 'angular2-universal'; - -import { - App, - Counter, - FetchData, - Home, - NavMenu -} from './components'; - -import { routes } from './routes'; - -/* NOTE : - - This file and `main.browser.ts` are identical, at the moment(!) - By splitting these, you're able to create logic, imports, etc - that are "Platform" specific. - - If you want your code to be completely Universal and don't need that - You can also just have 1 file, that is imported into both - * boot-client - * boot-server - -*/ - -// ** Top-level NgModule "container" ** -@NgModule({ - - // Root App Component - bootstrap: [ App ], - - // Our Components - declarations: [ - App, Counter, FetchData, Home, NavMenu - ], - - imports: [ - - // * NOTE: Needs to be your first import (!) - UniversalModule, - // ^ NodeModule, NodeHttpModule, NodeJsonpModule are included for server - - // Your other imports can go here: - FormsModule, - - // App Routing - RouterModule.forRoot(routes) - ] -}) -export class MainModule { - -} diff --git a/templates/Angular2Spa/ClientApp/routes.ts b/templates/Angular2Spa/ClientApp/routes.ts deleted file mode 100644 index 160ae4f7..00000000 --- a/templates/Angular2Spa/ClientApp/routes.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Routes } from '@angular/router'; - -import { Home, FetchData, Counter } from './components'; - -export const routes: Routes = [ - { path: '', redirectTo: 'home', pathMatch: 'full' }, - { path: 'home', component: Home }, - { path: 'counter', component: Counter }, - { path: 'fetch-data', component: FetchData }, - { path: '**', redirectTo: 'home' } -]; diff --git a/templates/Angular2Spa/tsconfig.json b/templates/Angular2Spa/tsconfig.json index 0f72b020..94b22fce 100644 --- a/templates/Angular2Spa/tsconfig.json +++ b/templates/Angular2Spa/tsconfig.json @@ -6,22 +6,9 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "skipDefaultLibCheck": true, - "lib": ["es6", "dom"], - "types": [ - "body-parser", - "compression", - "cookie-parser", - "express", - "express-serve-static-core", - "mime", - "node", - "serve-static", - "hammerjs" - ] + "lib": [ "es6", "dom" ], + "types": [ "node" ] }, - "exclude": [ - "bin", - "node_modules" - ], + "exclude": [ "bin", "node_modules" ], "atom": { "rewriteTsconfig": false } } From 297b4dbd92930aeff85e16d37b07117eb94d7660 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 13:09:12 +0100 Subject: [PATCH 036/748] Move more modules to vendor bundle. Remove explicit reflect-metadata reference (no longer needed). --- templates/Angular2Spa/ClientApp/boot-client.ts | 1 - templates/Angular2Spa/webpack.config.vendor.js | 14 ++++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/templates/Angular2Spa/ClientApp/boot-client.ts b/templates/Angular2Spa/ClientApp/boot-client.ts index ad0d29fa..778ba7d3 100644 --- a/templates/Angular2Spa/ClientApp/boot-client.ts +++ b/templates/Angular2Spa/ClientApp/boot-client.ts @@ -2,7 +2,6 @@ import 'angular2-universal-polyfills'; import 'es6-shim'; import 'zone.js'; import 'bootstrap'; -import 'reflect-metadata'; import './styles/site.css'; import { enableProdMode} from '@angular/core'; import { platformUniversalDynamic } from 'angular2-universal'; diff --git a/templates/Angular2Spa/webpack.config.vendor.js b/templates/Angular2Spa/webpack.config.vendor.js index 9636722f..1cffdfb7 100644 --- a/templates/Angular2Spa/webpack.config.vendor.js +++ b/templates/Angular2Spa/webpack.config.vendor.js @@ -16,11 +16,6 @@ module.exports = { }, entry: { vendor: [ - 'bootstrap', - 'bootstrap/dist/css/bootstrap.css', - 'es6-shim', - 'style-loader', - 'jquery', '@angular/common', '@angular/compiler', '@angular/core', @@ -29,7 +24,14 @@ module.exports = { '@angular/platform-browser-dynamic', '@angular/router', '@angular/platform-server', - 'reflect-metadata', + 'angular2-universal', + 'angular2-universal-polyfills', + 'bootstrap', + 'bootstrap/dist/css/bootstrap.css', + 'es6-shim', + 'es6-promise', + 'jquery', + 'style-loader', 'zone.js', ] }, From a1c1bdb1e629837d1c46bbf946d59be0700dfd1f Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 13:18:44 +0100 Subject: [PATCH 037/748] Simplify imports in boot-client.ts --- templates/Angular2Spa/ClientApp/boot-client.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/templates/Angular2Spa/ClientApp/boot-client.ts b/templates/Angular2Spa/ClientApp/boot-client.ts index 778ba7d3..6ac7dfb7 100644 --- a/templates/Angular2Spa/ClientApp/boot-client.ts +++ b/templates/Angular2Spa/ClientApp/boot-client.ts @@ -1,9 +1,7 @@ -import 'angular2-universal-polyfills'; -import 'es6-shim'; -import 'zone.js'; +import 'angular2-universal-polyfills/browser'; import 'bootstrap'; import './styles/site.css'; -import { enableProdMode} from '@angular/core'; +import { enableProdMode } from '@angular/core'; import { platformUniversalDynamic } from 'angular2-universal'; import { AppModule } from './app/app.module'; From a91b6a6b5cb071732009aa5aa043fbdd331a2061 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 13:50:26 +0100 Subject: [PATCH 038/748] Make HMR work again --- .../Angular2Spa/ClientApp/boot-client.ts | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/templates/Angular2Spa/ClientApp/boot-client.ts b/templates/Angular2Spa/ClientApp/boot-client.ts index 6ac7dfb7..75bd9a07 100644 --- a/templates/Angular2Spa/ClientApp/boot-client.ts +++ b/templates/Angular2Spa/ClientApp/boot-client.ts @@ -1,16 +1,21 @@ import 'angular2-universal-polyfills/browser'; -import 'bootstrap'; -import './styles/site.css'; import { enableProdMode } from '@angular/core'; import { platformUniversalDynamic } from 'angular2-universal'; import { AppModule } from './app/app.module'; -enableProdMode(); -platformUniversalDynamic().bootstrapModule(AppModule); +// Include styles in the bundle +import 'bootstrap'; +import './styles/site.css'; -// Basic hot reloading support. Automatically reloads and restarts the Angular 2 app each time -// you modify source files. This will not preserve any application state other than the URL. -declare var module: any; -if (module.hot) { - module.hot.accept(); +// Enable either Hot Module Reloading or production mode +const hotModuleReplacement = module['hot']; +if (hotModuleReplacement) { + hotModuleReplacement.accept(); + hotModuleReplacement.dispose(() => { platform.destroy(); }); +} else { + enableProdMode(); } + +// Boot the application +const platform = platformUniversalDynamic(); +platform.bootstrapModule(AppModule); From 85dfdd9b50cacc09813335a10575b32d1d9b2043 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 14:04:00 +0100 Subject: [PATCH 039/748] Move tsconfig into ClientApp dir, since it's not needed at root --- templates/Angular2Spa/{ => ClientApp}/tsconfig.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename templates/Angular2Spa/{ => ClientApp}/tsconfig.json (100%) diff --git a/templates/Angular2Spa/tsconfig.json b/templates/Angular2Spa/ClientApp/tsconfig.json similarity index 100% rename from templates/Angular2Spa/tsconfig.json rename to templates/Angular2Spa/ClientApp/tsconfig.json From 4ea7eb195e1f3c62567daf12ba0a1815d3d12fa6 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 14:13:20 +0100 Subject: [PATCH 040/748] Simplify webpack config. Eliminate dev/prod override files. --- templates/Angular2Spa/package.json | 1 - templates/Angular2Spa/webpack.config.dev.js | 3 --- templates/Angular2Spa/webpack.config.js | 25 +++++++++---------- templates/Angular2Spa/webpack.config.prod.js | 12 --------- .../Angular2Spa/webpack.config.vendor.js | 6 +---- 5 files changed, 13 insertions(+), 34 deletions(-) delete mode 100644 templates/Angular2Spa/webpack.config.dev.js delete mode 100644 templates/Angular2Spa/webpack.config.prod.js diff --git a/templates/Angular2Spa/package.json b/templates/Angular2Spa/package.json index 4a899278..65036e4a 100644 --- a/templates/Angular2Spa/package.json +++ b/templates/Angular2Spa/package.json @@ -34,7 +34,6 @@ "css-loader": "^0.25.0", "es6-shim": "^0.35.1", "expose-loader": "^0.7.1", - "extendify": "^1.0.0", "extract-text-webpack-plugin": "^1.0.1", "file-loader": "^0.9.0", "isomorphic-fetch": "^2.2.1", diff --git a/templates/Angular2Spa/webpack.config.dev.js b/templates/Angular2Spa/webpack.config.dev.js deleted file mode 100644 index 719de1fb..00000000 --- a/templates/Angular2Spa/webpack.config.dev.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - devtool: 'inline-source-map' -}; diff --git a/templates/Angular2Spa/webpack.config.js b/templates/Angular2Spa/webpack.config.js index 877b8530..5ed87302 100644 --- a/templates/Angular2Spa/webpack.config.js +++ b/templates/Angular2Spa/webpack.config.js @@ -1,16 +1,14 @@ var path = require('path'); var webpack = require('webpack'); -var merge = require('extendify')({ isDeep: true, arrays: 'concat' }); var ExtractTextPlugin = require('extract-text-webpack-plugin'); + +var isDevBuild = process.env.ASPNETCORE_ENVIRONMENT === 'Development'; var extractCSS = new ExtractTextPlugin('styles.css'); -var devConfig = require('./webpack.config.dev'); -var prodConfig = require('./webpack.config.prod'); -var isDevelopment = process.env.ASPNETCORE_ENVIRONMENT === 'Development'; -module.exports = merge({ - resolve: { - extensions: [ '', '.js', '.ts' ] - }, +module.exports = { + devtool: isDevBuild ? 'inline-source-map' : null, + resolve: { extensions: [ '', '.js', '.ts' ] }, + entry: { main: ['./ClientApp/boot-client.ts'] }, module: { loaders: [ { test: /\.ts$/, include: /ClientApp/, loader: 'ts-loader?silent=true' }, @@ -18,9 +16,6 @@ module.exports = merge({ { test: /\.css/, loader: extractCSS.extract(['css']) } ] }, - entry: { - main: ['./ClientApp/boot-client.ts'] - }, output: { path: path.join(__dirname, 'wwwroot', 'dist'), filename: '[name].js', @@ -32,5 +27,9 @@ module.exports = merge({ context: __dirname, manifest: require('./wwwroot/dist/vendor-manifest.json') }) - ] -}, isDevelopment ? devConfig : prodConfig); + ].concat(isDevBuild ? [] : [ + // Plugins that apply in production builds only + new webpack.optimize.OccurenceOrderPlugin(), + new webpack.optimize.UglifyJsPlugin() + ]) +}; diff --git a/templates/Angular2Spa/webpack.config.prod.js b/templates/Angular2Spa/webpack.config.prod.js deleted file mode 100644 index ddc6cf70..00000000 --- a/templates/Angular2Spa/webpack.config.prod.js +++ /dev/null @@ -1,12 +0,0 @@ -var webpack = require('webpack'); - -module.exports = { - plugins: [ - new webpack.optimize.OccurenceOrderPlugin(), - new webpack.optimize.UglifyJsPlugin({ - compress: { warnings: false }, - minimize: true, - mangle: false // Due to https://github.com/angular/angular/issues/6678 - }) - ] -}; diff --git a/templates/Angular2Spa/webpack.config.vendor.js b/templates/Angular2Spa/webpack.config.vendor.js index 1cffdfb7..9499e9c9 100644 --- a/templates/Angular2Spa/webpack.config.vendor.js +++ b/templates/Angular2Spa/webpack.config.vendor.js @@ -49,10 +49,6 @@ module.exports = { name: '[name]_[hash]' }) ].concat(isDevelopment ? [] : [ - new webpack.optimize.UglifyJsPlugin({ - compress: { warnings: false }, - minimize: true, - mangle: false // Due to https://github.com/angular/angular/issues/6678 - }) + new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }) ]) }; From 07a9c1685fd801df7864df9ce17eb846edadc484 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 14:15:13 +0100 Subject: [PATCH 041/748] Remove unnecessary NPM dependencies --- templates/Angular2Spa/package.json | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/templates/Angular2Spa/package.json b/templates/Angular2Spa/package.json index 65036e4a..24af3c99 100644 --- a/templates/Angular2Spa/package.json +++ b/templates/Angular2Spa/package.json @@ -1,17 +1,6 @@ { "name": "Angular2Spa", "version": "0.0.0", - "devDependencies": { - "@types/body-parser": "0.0.29", - "@types/compression": "0.0.29", - "@types/cookie-parser": "^1.3.29", - "@types/express": "^4.0.32", - "@types/express-serve-static-core": "^4.0.33", - "@types/hammerjs": "^2.0.32", - "@types/mime": "0.0.28", - "@types/node": "^6.0.38", - "@types/serve-static": "^1.7.27" - }, "dependencies": { "@angular/common": "2.0.0", "@angular/compiler": "2.0.0", From 358ee2261e660146e10aabab36865c6568ce54de Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 15:43:58 +0100 Subject: [PATCH 042/748] Make indentation consistent --- .../ClientApp/app/components/counter/counter.component.ts | 4 ++-- .../ClientApp/app/components/fetchdata/fetchdata.component.ts | 4 ++-- .../ClientApp/app/components/home/home.component.ts | 4 ++-- .../ClientApp/app/components/navmenu/navmenu.component.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/templates/Angular2Spa/ClientApp/app/components/counter/counter.component.ts b/templates/Angular2Spa/ClientApp/app/components/counter/counter.component.ts index 03848360..6100a3c9 100644 --- a/templates/Angular2Spa/ClientApp/app/components/counter/counter.component.ts +++ b/templates/Angular2Spa/ClientApp/app/components/counter/counter.component.ts @@ -1,8 +1,8 @@ import { Component } from '@angular/core'; @Component({ - selector: 'counter', - template: require('./counter.component.html') + selector: 'counter', + template: require('./counter.component.html') }) export class CounterComponent { public currentCount = 0; diff --git a/templates/Angular2Spa/ClientApp/app/components/fetchdata/fetchdata.component.ts b/templates/Angular2Spa/ClientApp/app/components/fetchdata/fetchdata.component.ts index 40de3d92..266149f0 100644 --- a/templates/Angular2Spa/ClientApp/app/components/fetchdata/fetchdata.component.ts +++ b/templates/Angular2Spa/ClientApp/app/components/fetchdata/fetchdata.component.ts @@ -2,8 +2,8 @@ import { Component } from '@angular/core'; import { Http } from '@angular/http'; @Component({ - selector: 'fetchdata', - template: require('./fetchdata.component.html') + selector: 'fetchdata', + template: require('./fetchdata.component.html') }) export class FetchDataComponent { public forecasts: WeatherForecast[]; diff --git a/templates/Angular2Spa/ClientApp/app/components/home/home.component.ts b/templates/Angular2Spa/ClientApp/app/components/home/home.component.ts index 2152f0a5..16b817c1 100644 --- a/templates/Angular2Spa/ClientApp/app/components/home/home.component.ts +++ b/templates/Angular2Spa/ClientApp/app/components/home/home.component.ts @@ -1,8 +1,8 @@ import { Component } from '@angular/core'; @Component({ - selector: 'home', - template: require('./home.component.html') + selector: 'home', + template: require('./home.component.html') }) export class HomeComponent { } diff --git a/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.ts b/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.ts index 3bcba94e..5941bf59 100644 --- a/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.ts +++ b/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.ts @@ -1,8 +1,8 @@ import { Component } from '@angular/core'; @Component({ - selector: 'nav-menu', - template: require('./navmenu.component.html') + selector: 'nav-menu', + template: require('./navmenu.component.html') }) export class NavMenuComponent { } From 49a853667983fa5ce566ef5a5fe866c71cdaaf2e Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 15:51:18 +0100 Subject: [PATCH 043/748] Update angular2-universal dependencies (cherry-pick 62dd13b3b) --- templates/Angular2Spa/package.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/templates/Angular2Spa/package.json b/templates/Angular2Spa/package.json index 24af3c99..e58de206 100644 --- a/templates/Angular2Spa/package.json +++ b/templates/Angular2Spa/package.json @@ -12,10 +12,9 @@ "@angular/platform-server": "2.0.0", "@angular/router": "3.0.0", "@types/node": "^6.0.38", - "angular2-express-engine": "~2.0.7", - "angular2-platform-node": "~2.0.7", - "angular2-universal": "~2.0.7", - "angular2-universal-polyfills": "~2.0.7", + "angular2-platform-node": "~2.0.10", + "angular2-universal": "~2.0.10", + "angular2-universal-polyfills": "~2.0.10", "aspnet-prerendering": "^1.0.6", "aspnet-webpack": "^1.0.11", "bootstrap": "^3.3.7", From 494c7b585cfd7450d896d529df44fdee02f6f1d9 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 16:05:22 +0100 Subject: [PATCH 044/748] Fix trailing whitespace --- templates/Angular2Spa/ClientApp/boot-client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/Angular2Spa/ClientApp/boot-client.ts b/templates/Angular2Spa/ClientApp/boot-client.ts index 75bd9a07..c46115f9 100644 --- a/templates/Angular2Spa/ClientApp/boot-client.ts +++ b/templates/Angular2Spa/ClientApp/boot-client.ts @@ -7,8 +7,8 @@ import { AppModule } from './app/app.module'; import 'bootstrap'; import './styles/site.css'; -// Enable either Hot Module Reloading or production mode -const hotModuleReplacement = module['hot']; +// Enable either Hot Module Reloading or production mode +const hotModuleReplacement = module['hot']; if (hotModuleReplacement) { hotModuleReplacement.accept(); hotModuleReplacement.dispose(() => { platform.destroy(); }); From 83cfb59c2da0f80c267f3db1ab9e461e29fd66f5 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 16:10:57 +0100 Subject: [PATCH 045/748] Add comment about UniversalModule --- templates/Angular2Spa/ClientApp/app/app.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/Angular2Spa/ClientApp/app/app.module.ts b/templates/Angular2Spa/ClientApp/app/app.module.ts index 23dcf581..6d912da0 100644 --- a/templates/Angular2Spa/ClientApp/app/app.module.ts +++ b/templates/Angular2Spa/ClientApp/app/app.module.ts @@ -17,7 +17,7 @@ import { CounterComponent } from './components/counter/counter.component'; HomeComponent ], imports: [ - UniversalModule, + UniversalModule, // Must be first import. This automatically imports BrowserModule, HttpModule, and JsonpModule too. RouterModule.forRoot([ { path: '', redirectTo: 'home', pathMatch: 'full' }, { path: 'home', component: HomeComponent }, From ca99a2304c07f11c40eb854340d0afaf61ad5b2a Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 16:53:20 +0100 Subject: [PATCH 046/748] Remove style-loader from Angular2Spa vendor bundle as it's not used at all. Is used by other templates though. --- templates/Angular2Spa/webpack.config.vendor.js | 1 - 1 file changed, 1 deletion(-) diff --git a/templates/Angular2Spa/webpack.config.vendor.js b/templates/Angular2Spa/webpack.config.vendor.js index 9499e9c9..9c03275a 100644 --- a/templates/Angular2Spa/webpack.config.vendor.js +++ b/templates/Angular2Spa/webpack.config.vendor.js @@ -31,7 +31,6 @@ module.exports = { 'es6-shim', 'es6-promise', 'jquery', - 'style-loader', 'zone.js', ] }, From 41f1f6fe825ba32f2a7bb69b135bcd7c80da3863 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 16:56:50 +0100 Subject: [PATCH 047/748] Delay Angular 2 bootstrapping until DOMContentLoaded --- templates/Angular2Spa/ClientApp/boot-client.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/templates/Angular2Spa/ClientApp/boot-client.ts b/templates/Angular2Spa/ClientApp/boot-client.ts index c46115f9..37ac3825 100644 --- a/templates/Angular2Spa/ClientApp/boot-client.ts +++ b/templates/Angular2Spa/ClientApp/boot-client.ts @@ -18,4 +18,6 @@ if (hotModuleReplacement) { // Boot the application const platform = platformUniversalDynamic(); -platform.bootstrapModule(AppModule); +document.addEventListener('DOMContentLoaded', () => { + platform.bootstrapModule(AppModule); +}); From cd18489f005e4eeed52699fcc772b5d0b94b180b Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 17:40:17 +0100 Subject: [PATCH 048/748] Fix HMR again following previous change --- templates/Angular2Spa/ClientApp/boot-client.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/templates/Angular2Spa/ClientApp/boot-client.ts b/templates/Angular2Spa/ClientApp/boot-client.ts index 37ac3825..c46b0fd6 100644 --- a/templates/Angular2Spa/ClientApp/boot-client.ts +++ b/templates/Angular2Spa/ClientApp/boot-client.ts @@ -16,8 +16,11 @@ if (hotModuleReplacement) { enableProdMode(); } -// Boot the application +// Boot the application, either now or when the DOM content is loaded const platform = platformUniversalDynamic(); -document.addEventListener('DOMContentLoaded', () => { - platform.bootstrapModule(AppModule); -}); +const bootApplication = () => { platform.bootstrapModule(AppModule); }; +if (document.readyState === 'complete') { + bootApplication(); +} else { + document.addEventListener('DOMContentLoaded', bootApplication); +} From 19684f2b7d50076e85b98f2c5f80fb6dd0c59d42 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Tue, 20 Sep 2016 10:06:23 +0100 Subject: [PATCH 049/748] In Angular2Spa template, use per-component scoped styles. Fixes common config issues like #234. --- .../app/components/app/app.component.css | 6 ++++ .../app/components/app/app.component.ts | 3 +- .../components/navmenu/navmenu.component.css} | 29 +++++++------------ .../components/navmenu/navmenu.component.html | 2 +- .../components/navmenu/navmenu.component.ts | 3 +- .../Angular2Spa/ClientApp/boot-client.ts | 3 -- .../Angular2Spa/Views/Shared/_Layout.cshtml | 1 - templates/Angular2Spa/webpack.config.js | 9 ++---- 8 files changed, 25 insertions(+), 31 deletions(-) create mode 100644 templates/Angular2Spa/ClientApp/app/components/app/app.component.css rename templates/Angular2Spa/ClientApp/{styles/site.css => app/components/navmenu/navmenu.component.css} (66%) diff --git a/templates/Angular2Spa/ClientApp/app/components/app/app.component.css b/templates/Angular2Spa/ClientApp/app/components/app/app.component.css new file mode 100644 index 00000000..63926006 --- /dev/null +++ b/templates/Angular2Spa/ClientApp/app/components/app/app.component.css @@ -0,0 +1,6 @@ +@media (max-width: 767px) { + /* On small screens, the nav menu spans the full width of the screen. Leave a space for it. */ + .body-content { + padding-top: 50px; + } +} diff --git a/templates/Angular2Spa/ClientApp/app/components/app/app.component.ts b/templates/Angular2Spa/ClientApp/app/components/app/app.component.ts index f1bd036c..01fbf8f2 100644 --- a/templates/Angular2Spa/ClientApp/app/components/app/app.component.ts +++ b/templates/Angular2Spa/ClientApp/app/components/app/app.component.ts @@ -2,7 +2,8 @@ import { Component } from '@angular/core'; @Component({ selector: 'app', - template: require('./app.component.html') + template: require('./app.component.html'), + styles: [require('./app.component.css')] }) export class AppComponent { } diff --git a/templates/Angular2Spa/ClientApp/styles/site.css b/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.css similarity index 66% rename from templates/Angular2Spa/ClientApp/styles/site.css rename to templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.css index 18a995fa..e15c6128 100644 --- a/templates/Angular2Spa/ClientApp/styles/site.css +++ b/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.css @@ -1,18 +1,11 @@ -@media (max-width: 767px) { - /* On small screens, the nav menu spans the full width of the screen. Leave a space for it. */ - .body-content { - padding-top: 50px; - } -} - -.main-nav li .glyphicon { +li .glyphicon { margin-right: 10px; } /* Highlighting rules for nav menu items */ -.main-nav li.link-active a, -.main-nav li.link-active a:hover, -.main-nav li.link-active a:focus { +li.link-active a, +li.link-active a:hover, +li.link-active a:focus { background-color: #4189C7; color: white; } @@ -32,31 +25,31 @@ height: 100%; width: calc(25% - 20px); } - .main-nav .navbar { + .navbar { border-radius: 0px; border-width: 0px; height: 100%; } - .main-nav .navbar-header { + .navbar-header { float: none; } - .main-nav .navbar-collapse { + .navbar-collapse { border-top: 1px solid #444; padding: 0px; } - .main-nav .navbar ul { + .navbar ul { float: none; } - .main-nav .navbar li { + .navbar li { float: none; font-size: 15px; margin: 6px; } - .main-nav .navbar li a { + .navbar li a { padding: 10px 16px; border-radius: 4px; } - .main-nav .navbar a { + .navbar a { /* If a menu item's text is too long, truncate it */ width: 100%; white-space: nowrap; diff --git a/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.html b/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.html index 4926cb82..c49d5097 100644 --- a/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.html +++ b/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.html @@ -1,5 +1,5 @@