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

Feature/stevesa/spaservices extensions package #1367

Closed

Conversation

SteveSandersonMS
Copy link
Member

Our next update to the Angular template changes it substantially so that it's entirely based around Angular CLI. This much better matches the desired usage patterns. The community has welcomed this proposed change. We will very likely do the same thing for the React template, basing an updated template around the create-react-app tool.

The core implementation idea is to stop coupling the SPA APIs to MVC concepts (e.g., before, the routing was integrated with MVC routing, and prerendering was implemented as a tag helper), and instead have a set of middleware APIs that handle:

  • serving the SPA default page (e.g., index.html), thereby making client-side routing work
  • launching a development server (e.g., Angular CLI) and passing through requests to it
  • performing server-side prerendering of complete HTML pages

All of these new middleware APIs work equally well in MVC applications as in Razor Pages applications. For an example of their usage, please see https://github.com/aspnet/templating/blob/feature/stevesa/angular-cli-template/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Startup.cs#L43

To support all this, we need some new runtime pieces. To allow for the possibility of shipping this out-of-band before the 2.1 release, 100% of the new runtime code is in a new package, Microsoft.AspNetCore.SpaServices.Extensions. This can ship with a dependency on SpaServices version 2.0.0, without any loss of back-compatibility (since SpaServices itself doesn't need to change). Later, we'll migrate the new functionality into SpaServices, and reduce SpaServices.Extensions to a set of type forwarders that exists only for back-compatibility (and at that point, the templates will no longer reference it for new applications).

As you'll see, the majority of code here is purely internal so we reserve the option to change implementation details pretty broadly once we get some preview feedback (and even after RTM). So for this review, I'm mostly keen on getting feedback on the public API design choices, though of course feel free to review whatever aspects of it you want.

@SteveSandersonMS
Copy link
Member Author

SteveSandersonMS commented Nov 3, 2017

Following @Eilon's feedback, I've reintroduced the ISpaBuilder concept. Out-of-the-box usage is now: https://github.com/aspnet/templating/blob/feature/stevesa/angular-cli-template/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Startup.cs#L43

The difference is that instead of attaching extra SPA middleware to the IApplicationBuilder app variable captured from the enclosing scope, the developer now attaches SPA middleware to a new ISpaBuilder variable that is passed as a parameter to the configuration callback, i.e., you have app.UseSpa(spa => { ... }) and then perform further config on spa instead of on app.

This more closely parallels how other nested-config patterns work in ASP.NET (e.g., app.Map(childApp => { ... }) or app.UseMvc(routes => { ... })).

A further consequence is that it's more constraining about how other SPA-related middleware can be used. It's now not possible to use middleware like UseProxyToSpaDevelopmentServer directly on an IApplicationBuilder - you now can only use it within the context of a UseSpa configuration callback. This is probably an improvement, since (1) this was the intended usage pattern anyway, and now there are fewer combinations of usages to reason about, and (2) it's easier to discover the available SPA-related middleware via intellisense - it just pops up when you type spa. inside the configuration callback, without being lost among a hundred other middleware APIs.

@jrmcdona
Copy link

jrmcdona commented Nov 5, 2017

@SteveSanderson is there a simple way to load the new template in beta or preview? I am setting up a new project and have some time on my side to test your template out

@jxvalenz
Copy link

jxvalenz commented Nov 6, 2017

Same here. I’m setting up a new POC project and I would love to start testing the new template with angular-cli

Copy link
Member

@Eilon Eilon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good. Let's chat about a few of my comments in person.

public DefaultSpaBuilder(IApplicationBuilder applicationBuilder, string sourcePath, string urlPrefix)
{
ApplicationBuilder = applicationBuilder
?? throw new System.ArgumentNullException(nameof(applicationBuilder));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you use using, bro?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Fix

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

try
{
openBrowserLine = await npmScriptRunner.StdOut.WaitForMatch(
new Regex("open your browser on (http\\S+)"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regex? We should discuss. 😭

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Add timeout to the regex

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
}

#pragma warning disable CS0649
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm why would this warning come up?

Field 'field' is never assigned to, and will always have its default value 'value'
The compiler detected an uninitialized private or internal field declaration that is never assigned a value.

Is Port never read? Or is it read via reflection somewhere (e.g. via serialization)?

If that's the case, should add a comment stating that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Add comment clarifying what CS0649 means
TODO: Add comment saying why it's used here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

public static void UseSpaPrerendering(
this ISpaBuilder spaBuilder,
string entryPoint,
ISpaPrerendererBuilder buildOnDemand = null,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lot of optional params, which can cause major headaches if we ever need to add new parameters in terms of breaking changes (not impossible, just difficult and ugly). Any concerns here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Change UseSpaPrerendering to take an options object callback

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

/// <summary>
/// Gets or sets the URL, relative to <see cref="UrlPrefix"/>,
/// of the default page that hosts your SPA user interface.
/// The typical value is <c>"index.html"</c>.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of typical maybe just say it's the default?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@SteveSandersonMS
Copy link
Member Author

SteveSandersonMS commented Nov 7, 2017

TODO: On UseSpa, remove the defaultPage optional param
Update: done.

var buf = new char[8 * 1024];
while (true)
{
var chunkLength = await _streamReader.ReadAsync(buf, 0, buf.Length);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use ReadLineAsync?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we want notifications of both partial-line chunks (for progress updates) and complete lines (that can be forwarded to the ILogger).

…ew runtime functionality needed for updated templates until 2.1 ships
…endencies" because it breaks the build. Will need to find a different way to enforce this.

This reverts commit 105422b.
…ry to keep restarting Angular CLI etc. on C# changes
…onditional' any more. This is internal, so the name change is fine.
@SteveSandersonMS SteveSandersonMS force-pushed the feature/stevesa/spaservices-extensions-package branch from 87eb919 to eb403ed Compare November 7, 2017 17:55
Copy link
Member

@Eilon Eilon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code changes look good to me. Let's review the API/usage this afternoon.

@SteveSandersonMS
Copy link
Member Author

SteveSandersonMS commented Nov 7, 2017

Further CR feedback:

  • Move UseWebsockets into UseAngularCliServer/UseProxyToSpaDevelopmentServer
  • Strip out console colours from the Angular CLI stdio piping
  • Support IOptions`1[SpaOptions]
  • Change urlPrefix to be a PathString
  • Change UseSpa to take only an Action, and set the UrlPrefix/SourcePath from inside the callback (making them settable)
  • Remove the UseSpaPrerendering comment block from the template
  • Add overload to UseProxyToSpaDevelopmentServer that takes a string for baseUri

Copy link
Member

@Eilon Eilon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updated codes continue to look good. Awaiting the final updates for final actual sign-off.

@SteveSandersonMS
Copy link
Member Author

@Eilon Should be ready for final approval now

…FileProvider to support SPAs not served from wwwroot (e.g., React).
Copy link
Member

@Eilon Eilon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final-approved.

@SteveSandersonMS SteveSandersonMS deleted the feature/stevesa/spaservices-extensions-package branch November 9, 2017 18:14
@SteveSandersonMS
Copy link
Member Author

@Eilon Made a few final tweaks before merging. Will chat when I spot you around.

{
throw new ArgumentException($"The value for {nameof(DefaultPage)} cannot be null or empty.");
throw new ArgumentNullException($"The value for {nameof(DefaultPage)} cannot be null or empty.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change back to ArgumentException - this is also thrown for empty strings.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well spotted. Done.

@SpeedHighway
Copy link

This line:
openBrowserLine = await npmScriptRunner.StdOut.WaitForMatch( new Regex("open your browser on (http\\S+)", RegexOptions.None, RegexMatchTimeout), StartupTimeout);

Is causing my site to fail every time, unless I manually stop the browser quickly, after pressing F5 (or Ctrl F5) in Visual Studio

Because of the fact that the console window appears and states:

** NG Live Development Server is listening on localhost:5155, open your browser on http://localhost:5155/ **

The browser opens immediately, as it starts to do the build. However, the build definitely does not finish in 50 seconds, so then it times out (displaying an error while building) and gets stuck unusable due to hitting the exception for the timeout.

The browser should not be opening up until the webpack: Compiled successfully. appears, unless I'm mistaken.

Thanks

@Eilon
Copy link
Member

Eilon commented Dec 18, 2017

@SpeedHighway can you log this in a new issue? That'll make it easier for us to track and investigate.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants