dotnet/aspnetcore

[Announcement] Obsoleting Microsoft.AspNetCore.SpaServices and Microsoft.AspNetCore.NodeServices

danroth27 opened this issue ยท 307 comments

ASP.NET Core supports integration with various single-page app (SPA) frameworks, including Angular, React, and React + Redux. Initially integration with these frameworks was accomplished with ASP.NET Core specific components that handled scenarios like server-side prerendering and integration with webpack. But as time went on, industry standards changed and the SPA frameworks each released their own standard command-line interfaces (e.g., Angular CLI, create-react-app).

When ASP.NET Core 2.1 was released in May 2018, we responded to the change in standards by providing a newer and simpler way to integrate with the SPA frameworks' own toolchains. This new integration mechanism exists in the package Microsoft.AspNetCore.SpaServices.Extensions and remains the basis of our Angular and React project templates since ASP.NET Core 2.1.

To clarify that the older ASP.NET Core specific components are not relevant or recommended, we are now officially obsoleting the pre-2.1 integration mechanism and marking the supporting NPM packages as deprecated.

The contents of the following NuGet packages have all been unnecessary since ASP.NET Core 2.1, and so are now being marked as obsolete:

  • Microsoft.AspNetCore.SpaServices
  • Microsoft.AspNetCore.NodeServices

For the same reason, the following NPM modules are being marked as deprecated:

  • aspnet-angular
  • aspnet-prerendering
  • aspnet-webpack
  • aspnet-webpack-react
  • domain-task

These packages will later be removed in .NET 5. If you are using these packages, please update your apps to use the functionality in Microsoft.AspNetCore.SpaServices.Extensions instead along with the functionality provided by the SPA frameworks you are using. To enable features like server-side prerendering and hot module reload please refer to the documentation for the corresponding SPA frameworks. The functionality in Microsoft.AspNetCore.SpaServices.Extensions is not obsolete and will continue to be supported.

Is UseWebpackDevMiddleware going away with this change? Or UseSpaStaticFiles?

We've been using Microsoft.AspNetCore.NodeServices as a way of executing JavaScript in Node on the server from .NET Core - specifically by calling the InvokeAsync method(s).

We'd previously been using the ClearScript library (in an earlier .NET Framework project), but that does not yet support .NET Core/.NET Standard.

If Microsoft.AspNetCore.NodeServices goes away, our only alternative would probably be to fork the source code.

Is UseWebpackDevMiddleware going away with this change? Or UseSpaStaticFiles?

@NotMyself As far as I understand UseWebpackDevMiddleware will go away and UseSpaStaticFiles will not.

Seriously, I liked this webpack middleware with HMR, because it accelerates the inner loop and is more comfortable instead of having two processes (dev server + ASP.NET Core app). It also reflects more the production running app - that is served by ASP.NET Core using e.g. Kestrel.
I use it with vuejs (started with this obsoleted template).

I would appreciate, when useful libraries aren't become obsolete after years of supporting it. That weaknesses the trust using a library. At least, I expect a comprehensive migration guide for those who using the obsolete libraries.

We use the ISpaPrerenderer.RenderToString() functionality to server render a VueJS application, passing application context with the customDataParameter.

Is there a recommended migration path to move to functionality in Microsoft.AspNetCore.SpaServices.Extensions? This seems to use RenderToString() itself.

Any reason why there's no official vue project template?

Any reason why there's no official vue project template?

@bugproof Maintaining templates isn't effortless. This has been discussed in issue: aspnet/JavaScriptServices/issues/412.

So how would HMR work with Webpack now? Webpack runs its own web server.

My company also uses the Microsoft.AspNetCore.NodeServices to execute JS from .Net. Is there a recommended alternative, if NodeServices is going away?

I get the main gist of this issue, but losing UseWebpackDevMiddleware seems unfortunate. It works great for frameworks that don't have a CLI.

I'm not sure I understand how NodeServices is related to this? What if people want to run JS code from C# for their own purposes?

@alexdresko I agree with you. UseWebpackDevMiddleware is a nice compromise to supporting multiple frameworks. I have been using it for around a year to do Vue.js development, example. It works quite nicely.

Is UseWebpackDevMiddleware going away with this change? Or UseSpaStaticFiles?

@NotMyself The webpack middleware is part of Microsoft.AspNetCore.SpaServices and will be obsoleted as part of this change. The support for SPA static files is part of Microsoft.AspNetCore.SpaServices.Extensions and will continue to be supported.

@flcdrg @atrauzzi @ericgreenmix Could you please share with us some details on how you are using NodeServices today? Why do you need to execute JavaScript on the server at runtime? If there is significant interest in this functionality it might be interesting to spin NodeServices off as a community driven project. Is that something any of you would be interested in taking on?

@danroth27

We use NodeServices to run some NPM packages that don't have equivalent .NET libraries and aren't worth a rewrite. This is one of the benefits as mentioned by the team before.

We also have a functions-as-a-service platform that we're building for customers to write their own custom logic in our app using Javascript. There are alternatives like Edge.js (which is not .net core compatible) but NodeServices makes this easy.

There's a lot of value in it and if it's not going to be maintained then we would definitely like to see it as a separate community project.

@danroth27 For the same reason that people do interop generally. In our case, we're using a PDF generation library to do reporting, because there is no equivalent available in netcore for linux platforms. As @manigandham points out, that was a general feature of the library.

@danroth27 As others have said, we use it to leverage various NPM packages that don't have equivalents in .Net.

As the repositories readme states:

This provides a fast and robust way for .NET code to run JavaScript on the server inside a Node.js environment. You can use this to consume arbitrary functionality from NPM packages at runtime in your ASP.NET Core app.

What led to the decision of deciding to obsolete the NodeServices without an equivalent replacement? It seems like an oversight to me.

What led to the decision of deciding to obsolete the NodeServices without an equivalent replacement?

@ericgreenmix Our primary scenario for building NodeServices was to enable integration between SPA frameworks and ASP.NET Core for things like like server-side prerendering and HMR. Since we now integrate with the CLIs for Angular and React directly this functionality is no longer needed for these specific scenarios. Providing NodeServices as a generic way to into JS from .NET Core is not something we want to maintain long term.

if it's not going to be maintained then we would definitely like to see it as a separate community project.

@manigandham Makes sense. We certainly would encourage folks that are interested in continuing to maintain this functionality as a community based project to do so. If such a community project were to spin up it is more than welcome to use the existing NodeServices code as a starting point with the appropriate changes to the namespaces and package names. We'd be happy to point folks that are interested in this functionality to whatever the community comes up with.

UseWebpackDevMiddleware is a nice compromise to supporting multiple frameworks. I have been using it for around a year to do Vue.js development, example. It works quite nicely.

@NotMyself We'd like to enable the .NET + Vue community to ship a Vue template that integrates with the Vue CLI similar to what we do for Angular and React. I believe we still have some functionality to make public to make that reasonable to do, but I think that's something we'd like to address in the .NET 5 time frame.

@flcdrg @atrauzzi @ericgreenmix Could you please share with us some details on how you are using NodeServices today? Why do you need to execute JavaScript on the server at runtime? If there is significant interest in this functionality it might be interesting to spin NodeServices off as a community driven project. Is that something any of you would be interested in taking on?

We also use nodeservices to run puppeteer module on server side for PrintToPDF functionality. Spinning off as a community driven project is indeed a good idea

@flcdrg @atrauzzi @ericgreenmix Could you please share with us some details on how you are using NodeServices today? Why do you need to execute JavaScript on the server at runtime? If there is significant interest in this functionality it might be interesting to spin NodeServices off as a community driven project. Is that something any of you would be interested in taking on?

We use TypeScript/JavaScript to perform customer-specific data transformations together with a common .NET application.

We would be interested in taking on this as a community project (otherwise we'll probably end up forking/copying it privately)

@danroth27 I might be missing something here, but there is no official react CLI for doing server side prerendering is there?

If I have a fully built JS bundle, it might be that only way to call it is to treat it like a JavaScript "DLL". That means there is no single convention that could reliably apply to it for a CLI tool.

I think it makes sense to continue offering an official way to call into JS from .net. Also, I wouldn't be surprised if losing this API surprised many more people than those chiming in here. It's about more than SPAs at the end of the day.

@danroth27 sounds reasonable to me. Seems like only my development workflow is changing. My deployment strategy should still work fine. So instead of having .net core proxy requests to my vue app, I can flip it over and have webpack proxy requests to the .net core app.

Might taking some fiddling to get F5 start-up and debug working the way I have it now, but that should be achievable. Let me know if you want someone to dog good a vue template.

I might be missing something here, but there is no official react CLI for doing server side prerendering is there?

@atrauzzi When we switched from providing our own infrastructure to using the CLI for Angular & React we made the decision that we would not try to provide our own infrastructure beyond what the CLIs supported. I may not be fully up to date on the latest features in create-react-app, but from their readme: "If you want to do server rendering with React and Node.js, check out Next.js or Razzle. Create React App is agnostic of the backend, and just produces static HTML/JS/CSS bundles."

We would be interested in taking on this as a community project (otherwise we'll probably end up forking/copying it privately)

@flcdrg That sounds great. And based on the responses here it sounds like there are some folks that also interested in the scenario and would probably be interested in helping out. Since we no longer plan to maintain this code ourselves, I expect that turning the NodeServices (and maybe also the webpack middleware functionality if folks are interested) into a community based project will involve copying the code into a new community maintained repo and updating the namespaces and package IDs accordingly. I'm sure the .NET Foundation would be happy to work with you to help make this community project successful. If you'd like to discuss in greater detail what that might entail we can discuss offline.

Apologies, but due to changing circumstances we aren't able to take over Microsoft.AspNetCore.NodeServices, so extending an invitation to others who might be interested in taking it on.

@danroth27 I wouldn't base this decision around create react app as there are an infinite number of ways for runnable react code to be produced. Any new functionality being made has to work with bundled code, not passing through to a single external CLI.

Moreover, the nodejs services imply much more than just server side rendering. So I feel like there's more at stake here. Scoping this to only SSR risks missing people who are using it to execute JS code for other purposes.

Just to add how we use the NodeServices package โ€“ we use it to provide SSR when rendering Vue components. Without this, the only way to provide SSR for our application would be to switch the application over to using Nuxt with Node to serve the front-end (vue-cli doesn't provide SSR, so a plugin to use this wouldn't be a way to migrate. We would have to rewrite our application to use Nuxt).

This would be similar with React (as far as I understand it). The only way to do SSR would be to either statically generate your templates (via something like create-react-app) or use Next.js with Node to serve the front-end. Again, this is a pretty big migration path from using .NET with Node Services.

Regarding WebpackDevMiddleware:

I managed to replace this with UseReactDevelopmentServer fairly easily (even though my project doesn't even use React!). The good thing about UseReactDevServer is that it automatically runs the NPM script for you and figures out a port to use. As far as I can see the only React-Ism in that middleware is that it expects the text "Starting the development server" in the server output (https://github.com/aspnet/AspNetCore/blob/2e0cf080dffa957fa0a665da5ec87d2be1b402ee/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddleware.cs#L88).

It'd be nice if there'd be a general UseDevelopmentServer that doesn't depend on that string. Again, the NPM script starting and the port detection are really useful for development.

It's undocumented, but I made this little express server that works for me as a WebpackDevMiddleware replacement: https://gitlab.com/raphaelr/clientapp-spaservices-extensions/blob/master/Test.SpaServicesExtensions/ClientApp/dev-server-for-aspnet.js

I think we're getting carried away here.

One part of the original functionality (NodeServices) takes a .js file, loads it and allows you to make calls into the resultant scope -- and to capture returns in C# space. Webpack, or any CLI seems unrelated.

.net code shouldn't need to think at the level of webpack or any specifically targeted command line utility. It should only be thinking about .js files that are built by something from the outside as a resource/asset.

Basically: Run the toolchains some other way before the .net code runs. So long as it outputs a .js file, then .net is in a better position to make general assumptions about what to call. Integrating with CLI tools really mixes concerns.

@danroth27 Is there going to be a migration path for applications that execute javascript at run-time? In our environment, we use a large amount of .NET Core / Standard middleware and libraries that integrate easily with our hosting environment, monitoring stack etc. We can then use VueJS components in both SSR and browser, and with the benefits of running our backend on .NET Core.

Dropping NodeServices without an alternative would most likely cause us to move away from .NET Core for our VueJS applications, and involve significant work for us.

Please provide a migration path for Webpack. Deprecation is fine, as long as there is a clear migration path.

+1 on the replacement for Webpack. We use AngularJS (1.7, not the new Typescript stuff) in a webapp that originally was .NET Framework 4.6.1 but now fully on .NET Core 2.2 with UseWebpack. We were able to get rid of a whole bunch of custom code and dramatically increase developer productivity, and this would suck having to go back to the old way....

What is the recommended way forward for those projects that consume node libraries using dotnet services?

As an example, a project I'm working on has a load of calculations written in javascript (executed client and server side) and nodeservices invokeasync is used to run these server-side. Is the recommended way forward to write these services in node instead?

It would be a shame if so, we much prefer .net ecosystem - it's just that we have specific requirements with regard to running js modules common with other parts of the system.

I have also been using INodeServices directly heavily to run javascript programs, e.g. for generating PDFs using JS libraries.

Could this be moved to Microsoft.Extensions?

Node services was advertised (READMEs, might need to google for blog posts) as an abstraction over Node and running JavaScript for any type of application (web, service, console, ...), with SPA integrations being one of the consumers.

It would be a loss and a then missing feature to use node services. Is there a recommended migration path for INodeServices?

Providing NodeServices as a generic way to into JS from .NET Core is not something we want to maintain long term.

@danroth27 Is this a new decision?

I agree with @dasMulli that https://github.com/aspnet/AspNetCore/blob/master/src/Middleware/NodeServices/README.md#microsoftaspnetcorenodeservices definitely implied it would be an officially supported generic abstraction layer - and a pretty useful one at that.

If it is not a new decision, please can someone who knows the internal technical roadmap have a quick look over other READMEs? It feels like if something is an implementation detail and not a long-term public API then that should be pointed out before we start relying on it in prod environments.

If this is being moved over to a community project I'd love to get involved, but I'm very new to .net and will need someone fairly experienced to lead.

Also, please could you update the labels on this issue to include breaking change?

I haven't been able to find any resources to server side render react or other javascript frameworks on .net core without using these prerendering services.

It seems that by making this obsolete this means .net core is not a sensible choice for any form of web app which involves SSR.

Is there a documented alternative way to achieve SSR of javascript?

create-react-app is not a react standard CLI just a boilerplate and nothing to do with SSR. Without NodeServices SSR is not possible (in the efficient way)

Example:
we have a simple product page for an e-commerce website.

Client side render:

  • we need an API endpoint what returns with a json
  • we have the client app in JS (it doesn't matter it is react or something else, or it bundled with webpack or not)
  • user downloads the js bundle as a static file (maybe from a cdn or a cache server)
  • user gets the js bundle it makes a ajax request to the api and render the page.
  • the .net API and the JS don't need to be on the same server.

Server side render:

  • we have the js app it has shared logic with the client js but most of the time it has different entry point.
  • on the .net side we fetch the product data and pass it to the js process
  • the js app does not make any ajax request because it has the data
  • the app renders the page and returns with it to the .net app
  • the .net app returns with the rendered page.
  • hydration etc... is common but optimal.
  • the .net API app and the JS app needs to be on the same server.

No matter what library we are using for SSR next.js or anything. Somehow that SSR process needs to get the data.

ps.: sorry for my English, I hope it was clear enough, feel free to ask if something is not make any sense.

@danroth27

Could you please share with us some details on how you are using NodeServices today? Why do you need to execute JavaScript on the server at runtime? If there is significant interest in this functionality it might be interesting to spin NodeServices off as a community driven project. Is that something any of you would be interested in taking on?

We are using NodeServices to run our webpack build which doesn't seem possible with the SpaServices.Extensions.
We are also using NodeServices to generate email templates using razor to generate mjml and running it through mjml via NodeServices.

@danroth27
According to the docs on MSDN, Microsoft.AspNetCore.SpaServices.Extensions prerendering relies on aspnet-prerendering npm package. Am I missing something? Will there be an alternative NPM package to facilitate prerendering?

The way I understood it is that NodeServices nuget will become obsolete but the functionality in Microsoft.AspNetCore.SpaServices.Extensions will remain so that pre-rendering can be taken advantage of. However, I don't understand how this is achieved without aspnet-prerendering npm package and can't find documentation about it.

Thanks everyone who shared details on their usage scenarios for NodeServices. We understand that this library along with the functionality in SpaServices is fulfilling a need for some users. This functionality shipped with .NET Core 2.1, which is a Long Term Support (LTS) release, and will continue to be supported for the support lifetime of that release. The functionality will also ship in an obsoleted form as part of .NET Core 3.1, also an LTS release, with the intent to remove it in .NET 5. For folks seeking to continue to use this functionality after that time period the code is open source and available for anyone to use and maintain as they see fit. We would also happily support anyone from the community who is interested in taking this code and maintaining it as an independent project.

@danroth27 Fair enough, deprecation is necessary sometimes for a long-term goal.

I recognise that microsoft might not want to provide an official way for this sort of thing, but is there any advice on what to do for those of us who do have to run javascript on the server-side for application functionality? We'd prefer not to, but it's a necessity for us.

Is using INodeServices at runtime in this way recommended, or would we be better off looking at some sort of RPC communication between processes, or even just rewriting those services in Node? It would be great to have some advice on the best way to achieve server-side js in the .net ecosystem.

@LyleDavis As far as I know there's nothing technically wrong with approach taken by NodeServices for calling JavaScript from .NET at runtime. The issues with maintaining this code are related to keeping it up to date with the latest Node releases.

The issues with maintaining this code are related to keeping it up to date with the latest Node releases.

That's the most helpful comment so far, I believe.

We would also happily support anyone from the community who is interested in taking this code and maintaining it as an independent project.

This actually sounds like a good option. You've done really great job creating and maintaining it so far. Having a node process being managed by asp.net application is a huge relieve for developers.
Now at this point the community around it seems to be mature enough to be able to maintain it. Speaking of that, what would be the recommended process of doing so? Is it just about taking current source code and publishing it under a different name?

poke commented

@a-stankevich:

Is it just about taking current source code and publishing it under a different name?

You could probably run a git filter-branch to extract the original history of that code. That way, you are automatically including proper attribution for the original work.

Now at this point the community around it seems to be mature enough to be able to maintain it. Speaking of that, what would be the recommended process of doing so? Is it just about taking current source code and publishing it under a different name?

@a-stankevich Yes, we would recommend moving the code to a new repo, and renaming the packages and namespaces to remove the "Microsoft" branding. Since the code is already owned by the .NET Foundation we would also be happy to help get the project setup as a new .NET Foundation project.

If anyone is interested in taking this on please feel free to reach out to me and we can discuss in further detail setting this up as a community project.

@danroth27

If anyone is interested in taking this on please feel free to reach out to me and we can discuss in further detail setting this up as a community project.

What are the requirements?

What are the requirements?

@alexdresko None really other than a strong desire to contribute to the .NET community and ecosystem. The code is open source and freely available so anyone can do with it what they want.

I think just moving to next.js for react/nuxt.js for vue/angular universal and decoupling SPA app completely from ASP.NET Core API is the only reasonable choice right now if you want SSR and it integrates better with the rest of your SPA codebase. I think Jet.com does it that way too - backend is written in F#, frontend using next.js + react

Node would be used to serve your SPA app and pre-render it on the server.

It was always confusing to me - the whole C# - Node interop thingy and it got even more confusing after reading this announcement. It looks like a real mess right now

See https://www.reddit.com/r/dotnet/comments/cswtz6/what_are_your_opinions_on_server_side_rendering/

Ideally, I would just use blazor on the client but it's still a preview and many things can change

This is stupid and drops out functionality that many people use! Your supposed to add functionality, not remove it

The React integration on Microsoft.AspNetCore.SpaServices.Extensions only works if you use the create-react-app template when creating your app and if you don't eject and change the app afterwards.

This happens because create-react-app aims to use only one dependency (with a "curated" set of packages assumed to be the best option -- which is not always the case). If you want to "step outside the box", you need to eject your application.

We had to eject our app a couple of months ago in order to use some newer packages not yet supported by create-react-app, and I understand this is quite common when apps reach a certain level of complexity.

I support the drive to a "newer and simpler way", but it would be helpful if this "new way" addressed situations such as this. UseWebpackDevMiddleware was a perfect solution, as it allowed us to have the flexibility that create-react-app does not allow.

Is anybody facing a situation like this? What alternatives are you considering? Any feedback is greatly appreciated.

Thanks in advance.

Any recommendations on how to integrate webpack (when no spa framework is used for example) with HMR without UseWebpackDevMiddleware?

This is unclear currently. A deprecation, but no migration path has been announced for this.

The deprecation essentially kills a whole bunch of stuff that works as-is today, with no replacement:

What doesn't work in the proposed 'replacement':

  1. Using multiple "mini-SPAs" in a single webapp, i.e., using many MVC Controller/Views, and each View being its own SPA
  2. Using frameworks other than React or Angular, i.e., legacy AngularJS or Vue.js
  3. SSR from what I can tell of ANY framework without a lot of work
  4. Using React when you have to step outside of what create-react-app supports
  5. HMR when not using Angular or React (and only via create-react-app -- not supported if you have to step outside of that -- which is common)

I know this deprecation alone will cost hundreds of thousands of dollars worth of productivity just for my day of job, since we are affected by 1, 2, and 4 above (since we use a lot of small SPAs within a single .NET Core webapp, some of which use React and others use legacy AngularJS 1.7 with no near-term plan to migrate away, and even for the ones that use React, create-react-app is insufficient so would not be supported, and there's a few MVC apps where we some Views are AngularJS and others are React)

Today that works with UseWebpackDevMiddleware(). I really hope this decision is reversed, because I for one am not looking forward to having to build and support my own replacement.

First, I'd like to echo some of the other feedback already posted here such as the very good points brought forth by @jhaygood86. Our company will also suffer since we have a non create-react-app compatible react app that we would very much like to port to core 3 as well as maintain for a forseeable future.

Of course, you could argue there is nothing stopping us from doing this. But things such as SSR was a real factor when deciding on platform. I can't shake the feeling of being thrown under the bus, because JavascriptServices was more or less dotnet core's flagship for getting web devs aboard and now you pull the plug ~2 years later - very dissapointing.

As others, I also understand the need / want to simplify things, but the very agressive move to rely on create-react-app is just to restricting IMO. For example, something as simple as using LESS is simply not possible (AFAIK) without ejecting.

Please reconsider.

Just to add to the excellent points raised by @jhaygood86 (points 1-3,5 apply to me), there also doesn't seem be any way of:

  • Hosting a SPA in a Razor Page (granted, this didn't exist prior to deprecation) as part of a larger webapp with non-SPA parts
  • Mapping the SPA endpoint to anything other than the webapp's root folder via app.UseSpa(), like you could with a Controller/View

I also have the feeling of having the rug yanked out from under us again, just like when certain SPA templates were discontinued.

There are lots of good points being raised here!

We totally understand there are use cases for the NodeServices library and the original SpaServices APIs. Our hope is that this is a really good fit for being a community-maintained package. In general .NET Core developers don't rely on all the packages they consume being supplied by Microsoft directly, and we have a healthier ecosystem when there are multiple participants making their own choices about what to ship and when.

The existing packages aren't going to be removed during the 3.x timeframe, and the sources will be available indefinitely. Nobody's app is getting broken during 3.x. So there's plenty of opportunity for a graceful migration. There's clearly some demand, so here's an opportunity to kick-start an open source project with an existing implementation and existing users.

If anybody here is interested in creating a project for this (DotNetNodeServices?) I'd be very happy to help with the actual migration of sources from here and here into a new external repo. New packages would need a new name so you can publish them, and for clarity it would be wise to change the namespaces of the classes, but otherwise no other code changes should be necessary initially. If you have any trouble making things work from the new codebase I can help with figuring out what's needed.

My hunch here is that people commenting actually want the libraries officially maintained. They are useful and are being used, so there's no question over their utility.

What about an MS maintained spinoff? That way people can trust it and the project will outlive the interests of maintainers?

It is quite sad to me to see my PR finally rejected after 1+ year of waiting. Probably this decision to make these components obsolete was "in the air" long ago.
At this point i think it would be better to clearly state these change of direction as soon as they rise internally in the core team.
In any case, also I see these libs useful, and it will be very helpful to have an official migration path, not only for "famous" frameworks (angular , vue) but a generalized migration path that can be applied to other less famous frameworks (like Aurelia)
I think this will also help the team to reconsider the decision and better evaluating the impact that it will cause; it will also be a great help for the community.

This is really missing an opportunity, if aspnet core wants to stay relevant with the javascript ecosystem it has to invest more in this space, and this goes in the opposite direction.

Ouch. I see that Bootstrap4 UI extensions will continue to be maintained, so you have some interest in frontend extensions. I'm just not really sure what the disconnect is. Being so friendly with Spa spaces gets a lot of buy-in from a lot of people. I'd understand if this was an Aurelia library but... we're talking Angular, React, etc here.

It also hurts our trust that the other things will be maintained. Are we going to see React templates disappear without notice at the start of the next LTS? What about the dotnet cli tools? Microsoft.AspNetCore.SpaServices.Extensions? How do we know?

I hope we can migrate all of these tools to community-backed libraries with Microsoft's assistance.

Is there a migration guide we can reference? Or a starter template app?

Since there are virtually no proper charting libraries out there for .Net Core, my ASP.Net Core app relies on NodeServices to produce charts using the Chart.js library. Please provide a migration path or a separate MS maintained project / Nuget library.

My company uses NodeServices to call legacy nodejs code from a Asp.Net Core Application. NodeServices is an awesome solution for doing so. Our only option seems to be that we maintain our own fork.

I am totally fine by removing chaff that individual packages already do or now do. What I am reading is there is no HMR ability now?

I am upgrading my 2.2 site to 3.0 and using Razor/MVC + Vue and love the HMR. Saves a ton of time. If I am understanding it incorrectly, meaning you still CAN do HMR, then I would like is an example that shows:

Asp.Net Core 2.2:

app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {
	HotModuleReplacement = true,
	HotModuleReplacementClientOptions = new Dictionary<string, string> {
		{ "reload", "true" }, { "aggregateTimeout", "300" }, { "poll", "1000" }
	}
});

Asp.Net Core 3.x:

?????

Thanks

I think the biggest thing being missed here is that Microsoft.AspNetCore.NodeServices is useful for things outside of SPAs. Ideally it shouldn't be getting lumped in with this change as it serves a much more generic purpose.

@atrauzzi Agreed. I use razor to generate mjml markup and use NodeServices to transform the mjml into html for generating emails that render correctly in most email clients. It saves a ton of time for our team.

Yeah, I can think of quite a few uses for it that are immensely helpful for .net developers who need to briefly tap into the JS ecosystem.

It allows someone to create any type of bundle and invoke it for a result. Regardless of any specific convention - heck, you can make up your own. Just pick a type in the bundle and use it.

Somebody have a solution now ??

Asp.Net Core 2.2:

app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {
	HotModuleReplacement = true
	}
});

Asp.Net Core 3.x:

?????

Thanks

@Grandizer @rich25200

You can still continue to use app.UseWebpackDevMiddleware(...) in ASP.NET Core 3.0. It will show up as deprecated but will continue to work.

@Grandizer @rich25200
You can still continue to use app.UseWebpackDevMiddleware(...) in ASP.NET Core 3.0. It will show up as deprecated but will continue to work.

Very true. Just wanted to not have "eventual" old practices if there was a new way. At a minimum I think the warning should give you some pointers on where to go for common X, Y and Z scenarios. Or at least point to an in-depth article about it.

I understand that it's a matter of priorities and, with all the new features in .net core 3.0, this wasn't a priority.
For the future, is it al least planned a way to inject some node code in main process? I
n this way you don't have to maintain all SPA framework "adapter", but only leave the possibility to run any "ng build" or "npm run webpack --whatever parameter" in the main thread

For our purposes, we have a site that uses each page as a mini-SPA using React, with server-side rendering, but still using MVC for routing and HTML generation. We also have several existing React SPA sites that use MVC but were not generated using create-react-app. NodeServices is used to execute the serverside rendering for these sites.

We also use material-ui for our UI framework, and SSR for this requires the render callback to return both the body and style headers for JSS (and since we use security headers, these need generating with a nonce attribute).

Some also use loadable-components, which also requires the script headers to include to be returned by the SSR to include in the Razor, since the files to load change depending on the route and the file names are unpredictable.

We also inject configuration for OpenID and which APIs to use into the page both as parameters to the ssr function, and in the razor as a global object on the window. Allows us to deploy the same container to any environment, rather than needing to compile separate javascript for each environment just to target a different single-sign on provider or API.

This all makes the code executed for ssr something that we need explicit control over the parameters going into and values returned from, and made NodeServices a perfect fit for invoking.

Whilst not able to replace the loss of UseWebpackDevMiddleware, for those looking to execute arbitrary javascript code for purposes such as SSR, an interim solution for those who don't want to use the deprecated library may be to replace the usage of NodeServices with one of the 3rd party libraries for running Javascript from .NET that support .NET Standard 2.0, such as:

Another possibility, Jint, is arguably the easiest to integrate against but currently has a bug in it's Javascript parsing that results in errors with React.

So long as the code to execute is pure javascript and not reliant on node libraries like fs or c-based libraries, both of these are capable of executing the required code for at the very least react serverside rendering. This does have an added benefit of not needing to install nodejs alongside the application (reduces size and complexity of packing your application in a docker container, at the very least).

Still doesn't allow for HMR during development, and still reliant on 3rd party developers instead of an official solution though. Also won't work when you need node-only libraries.

I don't think either of those libraries are good to suggest as alternatives.

how can i do server side rendering in the project. my project is (angular 8 universal with mvc core 3.0)
please can u show us step by step. how will I do. i don't understand. thanks.

For Webpack at least I managed to get something working:

  1. Ensure you have the webpack development server running.
  2. Install Microsoft.AspNetCore.SpaServices.Extensions
  3. Apply in your Startup:
 if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();

    app.UseWhen(
        ctx => ctx.Request.Path.StartsWithSegments("/build") ||
               ctx.Request.Path.StartsWithSegments("/__webpack_hmr"),
        spaApp =>
        {
            spaApp.UseSpa(spa =>
            {
                spa.UseProxyToSpaDevelopmentServer("http://localhost:8080/");
            });
        }
    );
}

Essentially in the condition of UseWhen you need to add the output path(s) of your webpack compilation. In the UseProxyToSpaDevelopmentServer you enter the port number as configured for the webpack-dev-server or webpack-dev-middleware. This method also works for Hot Module Replacement!

You still need to run your webpack development server manually otherwise your static files won't be served. This way is actually better too since the (probably slow) compilation of the webpack bundles is not tied to the compile/run/debug cycle of the main ASP.NET Core server.

There is surely a way to do that from the ASP.NET Core server app, and make sure the process is not starting duplicately. I will try to look into that later.

*Edit: * I integrated automatic startup as well, find a more complex example here. This uses a dev-server.js file to be executed by node. This one also has multiple compilations support which I needed, but the essence is that we don't run the server when we already are running it (we check by acquiring a mutex), From the web server process we invoke node. The experience at the end is almost the same as the previous webpack middleware.

For Webpack at least I managed to get something working:

  1. Ensure you have the webpack development server running.
  2. Install Microsoft.AspNetCore.SpaServices.Extensions
  3. Apply in your Startup:
 if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();

    app.UseWhen(
        ctx => ctx.Request.Path.StartsWithSegments("/build") ||
               ctx.Request.Path.StartsWithSegments("/__webpack_hmr"),
        spaApp =>
        {
            spaApp.UseSpa(spa =>
            {
                spa.UseProxyToSpaDevelopmentServer("http://localhost:8080/");
            });
        }
    );
}

Essentially in the condition of UseWhen you need to add the output path(s) of your webpack compilation. In the UseProxyToSpaDevelopmentServer you enter the port number as configured for the webpack-dev-server or webpack-dev-middleware. This method also works for Hot Module Replacement!

You still need to run your webpack development server manually otherwise your static files won't be served. This way is actually better too since the (probably slow) compilation of the webpack bundles is not tied to the compile/run/debug cycle of the main ASP.NET Core server.

There is surely a way to do that from the ASP.NET Core server app, and make sure the process is not starting duplicately. I will try to look into that later.

how we can start application with server side rendering?

arruw commented

NodeServices are very useful, we are using some of NPM libraries that are not available in .NET Core. Please reconsider it and keep NodeServices alive :)

You want to stop server side rendering for SPA templates. The only reason i see is you want to promote Blazor. But don't do this, community need these packages.

For the webpack users: Additional, more basic, example without HMR: Sebazzz/IFS@c8e7d27

how we can start application with server side rendering?

Sorry, this approach is a migration for Webpack. I currently don't use frameworks in combination with SSR so I can't help you there. I can imagine you can use the same approach but then place the call after your final routing/UseEndpoints call.

Hi MS team,
It is really annoying if you simply discontinue something you were serving.
You better be careful taking stupid decision over discontinuing something were maintained and gained much popularity to the community.
It is better to, push your service Open-Source repository and let it be maintained by community. And, this will not affect you and the community. It will takeoff your responsibility but your service will remain there to help the community and also increases your value.

@irejwanul The team have already publicly stated here on this issue that they'd love someone to step up and take over maintaining this software. Are you offering?

This is a bad call, guys. Listen to the community.

The lack of documentation how to do it in a proper "new" way is a really big problem.
Have anyone has any demo with Angular 8 and ASP Core and server side rendering?
The project which so far served the role of an advanced startup is outdated already.
https://github.com/TrilonIO/aspnetcore-angular-universal

This is really a shame. Can't remember reading anything on NodeServices being primarily intended for React and Angular in the documentation, but upon removing it, it is simply stated as a matter of fact. Oh well, I might be wrong. Anyways, I'll go and fork the old source code now.

Our primary scenario for building NodeServices was to enable integration between SPA frameworks and ASP.NET Core for things like like server-side prerendering and HMR. Since we now integrate with the CLIs for Angular and React directly this functionality is no longer needed for these specific scenarios. Providing NodeServices as a generic way to into JS from .NET Core is not something we want to maintain long term.

@danroth27 If you think this bridge is no longer needed, then you need to have a walkthrough on how to do SSR on Aspnet Core backend. Because otherwise this will lead the community to head to Express and other Node servers to run the public facing site, while doing SSR.

Or maybe @SteveSandersonMS has something nice in his sleeves for us?

For me, when upgrading to 3.0 from 2.2, and ignoring the obsolete warning I get another problem - the time taken to get the hot update went from being instantaneous on 2.2 to being tens of seconds, even minutes.

Really odd - the output window shows that VS has compiled the change straight away, but the client doesn't request it. Frustrating - will have to keep on 2.2 for now - anyone else seen this?

I'm shocked, it was looking like NodeServices was meant to execute nodejs scripts from C# and this was awesome, now this functionality is gone!

I have no words on how you can deprecate such a wonderful feature @danroth27

In these end I reverted to using webpack-dev-server

Steps:

  1. Add package to package.json: "webpack-dev-server": "3.8.2",
  2. Add webpack.development.js
const merge = require('webpack-merge');
const common = require('./webpack.config.js');
const ExtractCssPlugin = require('mini-css-extract-plugin');

const webpackDevServerPort = 8083;
const proxyTarget = "http://localhost:8492";

module.exports = merge(common(), {
    output: {
        filename: "[name].js",
        publicPath: '/dist/'
    },
    mode: 'development',
    devtool: 'inline-source-map',
    devServer: {
        compress: true,
        proxy: {
            '*': {
                target: proxyTarget
            }
        },
        port: webpackDevServerPort
    },
    plugins: [
        new ExtractCssPlugin({
            filename: "[name].css",
            chunkFilename: "[id].css"
        })
    ]
});

Note that the proxy setting here will be used to proxy through to ASP.Net core for API calls

Modify launchSettings.json to point to webpack-dev-server:

"profiles": {
    "VisualStudio: Connect to HotDev proxy": {
      "commandName": "Project",
      "launchBrowser": true,
      "launchUrl": "http://localhost:8083/",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "http://localhost:8492/"
    }
  }

(also I had some problem with configuring the right locations in webpack, and found this useful

Also, will need to start webpack-dev-server which can be done via a npm script:

  "scripts": {
    "build:hotdev": "webpack-dev-server --config webpack.development.js --hot --inline",

And then this is bootstrapped

            app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "ClientApp";

                if (env.IsDevelopment())
                {
                    spa.UseReactDevelopmentServer(npmScript: "build:hotdev");
                }
            });

(or you can install the npm task runner extension and:

  "-vs-binding": {
    "ProjectOpened": [
      "build:hotdev"
    ]
  }

Alternatively I realise you can proxy the other way using the following - this way any request to under "dist" will be pushed through to the proxy webpack-dev-server

            app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "dist";

                if (env.IsDevelopment())
                {
                    // Ensure that you start webpack-dev-server - run "build:hotdev" npm script
                    // Also if you install the npm task runner extension then the webpack-dev-server script will run when the solution loads
                    spa.UseProxyToSpaDevelopmentServer("http://localhost:8083");
                }
            });

And then you don't need to proxy though from that back any more and can just serve /dist/ content

  • both hot and vendor-precompiled using as web.config.js like this:
module.exports = merge(common(), {
    output: {
        filename: "[name].js",
        publicPath: '/dist/',
    },
    mode: 'development',
    devtool: 'inline-source-map',
    devServer: {
        compress: true,
        port: 8083,
        contentBase: path.resolve(__dirname,"wwwroot"),
    },

    plugins: [
        new ExtractCssPlugin({
            filename: "[name].css",
            chunkFilename: "[id].css"
        })
    ]
});

This is really a shame obsolating UseWebpackDevMiddleware without replacement. Okay, it still works (but as I read, much slower than before), but for how long? What others suggested may work, but now we are hacking workarounds instead of a convenient one line app.UseWebpackDevMiddleware. Not good.

I also noticed that for vue HotModuleReplacement is broken in .net core 3.0. Works in 2.2. Unfortunately this is a deal breaker to upgrade to 3.0 until there is a solution / workaround :(

On the one hand, I'm grateful for this obsolete warning, as otherwise I feel like the rug really would have been yanked out from under us if it were a surprise. If I'm understanding the LTS schedule correctly, this will continue to be supported until at least November 2022 (assuming 3.1 is released next month on schedule) as long as you don't upgrade to .NET 5 between now and then. I hope Microsoft commits to that support, including for new versions of Webpack and Node released in the meantime.

On the other hand, I'm involved in several projects that use this functionality and moving to some other approach is going to take a fair amount of effort. Some use the "mini-SPA" approach, others needed custom webpack configurations, and others were just React code written from before create-react-app was even a thing. Also, FWIW, most are not full SPAs, and I hope Microsoft continues to support this hybrid client-server approach. But in all cases, we can't just switch to use the CLI. Even the create-react-app documentation states that it doesn't support all scenarios, and that you can "eject" if you can't fit into the narrow, opinionated mold it dictates. But what you guys have created here does support all of these scenarios! I've used this as a selling point to get people on ASP.NET Core. And now it's going away, and I have to explain to my clients why they will have to re-architect the front-end build process unnecessarily in the next year or two from what was very recently a new and Microsoft-supported thing, or rely on what may or may not be community supported. Worse, the potential for this getting even better with time as a supported Microsoft project goes out the window. This is really sad.

Please make an effort to support non-SPA, non-create-react-app React + Webpack apps, perhaps in a manner similar to what @markalroberts proposed, but without requiring such customization. I'm cool with a proxy to an external process (to avoid Node integration) so long as that external process is started automatically when debugging.

Edit: Despite that a proxy approach works for me, others here need SSR or full raw Node integration. I am not trying to dismiss their concerns, and I hope there is an adequate solution for them as well.

@danroth27
When ASP.NET Core 2.1 was released in May 2018, we responded to the change in standards by providing a newer and simpler way to integrate with the SPA frameworks' own toolchains.

You responded by providing a limited-scope solution that only caters to very specific uses of Angular and React, conveniently the only two solutions for which Microsoft ever officially provided templates.

The old solution allowed full control over the Webpack integration, critically also for users that use something other than Angular or React. Or Vue for that matter. Or one of a million other smaller frameworks. Or those that don't use a framework at all, and only use Webpack as a bundling and building tool for their JavaScript and CSS, along with the convenience and sped-up workflow that hot-reloading brings. The appearance of dedicated CLI tools for two or three frameworks does not suddenly constitute a radical shift in standards.

That doesn't even begin to touch on the lower-level NodeServices API. Something that now is also being killed off while it finds valid use in many other scenarios outside of SPA pipelines.
For instance: integrating Google's Puppeteer to drive a headless Chromium to render PDFs. This is - and frankly to the embarassement of MS - still one of the best freely available, flexible and full-featured solutions to PDF rendition for .NET applications.

And all of this is in direct contrast to earlier messaging from Microsoft that these Node.js interop facilities should be treated as a well-supported official go-to solution too...

So it's at best disingenuous to set this messaging up as if the new solution is "better" in any way; shape or form. And at worst it's an outright lie.


<rant>
I cannot prove it in any way, but to me this feels like oldskool Microsoft PR-spin around an alternate agenda. Specifically one aimed at leveling the browser-side development field in preparation for widescale promotion of Blazor as the shiny new go-to technology, since it doesn't have the clout to get there on its own.
Ofcourse, with the caveat that MS cannot afford to drop the support for the most popular React or Angular options for fear of huge public backlash, so they need to come up with some kind of stop-gap that only screws over everyone else at first.

Not saying that's what it is. But it's certainly what it looks like.
</rant>


@paulirwin
On the one hand, I'm grateful for this obsolete warning, as otherwise I feel like the rug really would have been yanked out from under us if it were a surprise.

You don't consider the current situation having the rug yanked out from under us?

  • Nothing about this planned obsolesence was surfaced far ahead of time through any major channels. Many people are only finding out now because they are migrating to .NET Core 3, or are attempting to use a tried solution set-up from previous projects under a new .NET Core 3 project.
  • Everything points back at this GitHub discussion with a distinct non-presence from Microsoft largely stonewalling everyone.
  • No proper documentation regarding migration paths to the new offering.
  • Proper migration paths aren't even possible for many scenarios that worked fine under the old and now officially obsolete offering.
  • The obsolete offering already is exhibiting issues when attempting to use it with .NET Core 3.
  • Microsoft is not investing time or effort to fix any issues, closing open bug reports while citing that it "doesn't align with future vision" and pointing back to this very issue on GitHub.

This farce looks and feels like one of the top brass in the .NET Core program management overheard someone in a bar after a JavaScript meetup laughingly claim that it wouldn't be possible to handle a large-scale migration situation worse than Google did with Angular ... and then asked someone to hold their beer for them.

@paulirwin
I'm cool with a proxy to an external process (to avoid Node integration) so long as that external process is started automatically when debugging.

FYI; that's basically what the old and obsolete SpaServices did ...
SpaServices is a bit of veneer on top of NodeServices which in turn is actually quite a simple piece of logic that spins up a separate Node.js process using Process.Start and talks to it over HTTP localhost loopback and adds some safeguards to shut down the process together with the parent process or spin up a new Node.js process if the old one crashes or times out on a response.
It just looks complex and that's mainly because of a horribly, horribly over-engineered and largely superfluous DI architecture.

As an aside: I know this, because at one point I actually took the NodeServices code; stripped it all back down to its bare essentials; got it to run under .NET Framework 4.5; and then poured it into a Visual Studio extension to be able to power a Less compiler for live side-by-side previews. This was a few years after Mads Kristensen axed that functionality from the popular WebEssentials extension and - going against earlier promises - had failed to ever bring it back.

The back-porting was actually so easy that at the time I was seriously wondering if limiting general availability of the NodeServices infrastructure to .NET Core had been part of some alternate agenda to push adoption rates for .NET Core vs .NET Framework.

On top of the basic NodeServices the C# UseWebpackDevMiddleware() adds what is little more than a stripped down integrated proxy for binary data streams sent back over that loopback.
All the heavy lifting is done by the official webpack-dev-middleware running in the Node.js process, wrapped in an MS-developed facade from the aspnet-webpack package that emulates the middleware API made common-place by Node.js webserving frameworks like Express.

@rjgotten I tend to agree with your thoughts on this. It will be interesting to see how Microsoft responds to the community on this. It really is like having the rug yanked out from underneath. Clearly, based on the feedback on this issue alone, this is a widely used feature that is going to severely impact many teams if/when it is removed.

It will be interesting to see how Microsoft responds to the community on this.

Imho, they're going to have to do something or this breach of trust is going to generate quite the backlash and potentially extend into future web-related products and frameworks, like their new little glamor-baby Blazor as well - making it even more dead-on-arrival than it currently already is.

You'd already have to be quite mad to foist multiple megabytes of wasm-ified CIL on users, which they still need to finish compiling in-browser eating even further resources on limited mobile devices. But with this flagrant display of complete disregard for the continuity of the JS and browser-side eco-system?

I personally would no longer touch the likes of Blazor with a ten foot pole if it comes to the development of anything that is running publicly or which is business-critical. Doubly so if it's a vendor-locked "closed" initiative with no viable migrations in a worst-case scenario. And if I end up in a situation where I would have to advise a business on potentially adopting it, this is also exactly the type of concerns that I would raise which would lead to cautioning against its adoption.

@pealmeid
I support the drive to a "newer and simpler way", but it would be helpful if this "new way" addressed situations such as this. UseWebpackDevMiddleware was a perfect solution, as it allowed us to have the flexibility that create-react-app does not allow.

Is anybody facing a situation like this? What alternatives are you considering? Any feedback is greatly appreciated.

::raises hand::

I'm facing this because I'm building and maintaining stuff using an alternative framework and literally need raw Webpack dev middleware to have a workable development experience. I also have to deal with several scenarios like Puppeteer-based PDF rendering that use the lower-level InvokeAsync from NodeSerivces.

As I've taken a private fork of NodeServices before, I'll probably end up doing so again to facilitate those lower-level InvokeAsync needs. And then for SpaServices: the only thing I really need out of it is the WebpackDevMiddleware with HMR support. Writing my own variant on those which cuts back on the cruft and workarounds in the existing one would probably eventually be a thing, because I wouldn't want to deal long-term with headaches related to some of the existing approach. But to start out, I could just extract the existing ones and dump the rest.

@danroth27
The issues with maintaining this code are related to keeping it up to date with the latest Node releases.

Not seeing it. Atleast not for the aspnet-webpack middleware gluecode.

Most if not all of the complex parts there that would have to touch sensitive Node APIs are imported from third party packages with zero burden to "keep up to date with the latest Node releases" - outside of maybe bumping a package version to incorporate support for a new Node engine version.

What I do see is that the middleware gluecode has unnecessary burden created by the maintainers themselves, due to poor architectural choices and hacky workarounds.

For instance: there's some manual hacking of the semi-internal Node.js require.cache going on to obtain 'fresh copies' of the webpack configuration file. The middleware could have just used the vm module to run separate Webpack dev servers in separate contexts instead - if you really needed fresh copies.

But you actually don't. The only reason you're taking fresh copies is because the gluecode is doing something stupid and is adding HMR support by explicitly mutating the configuration object's entries property. Specifically: it adds the HMR runtime if not yet present and - if the module is installed - it preceeds that with the event-source-polyfill for MSIE/Edge to support the EventSource API that HMR needs.

What the code should be doing there is supply a Webpack plugin that uses the entryOption compiler hook to process the entries and add the necessary HMR modifications that way. (Granted: there's a problem there in that there is yet to be designed a afterEntryOption hook that allows interception of normalized entry data, before the make hook actually adds the entries. So probably you'd need to hook make as well and create entries yourself. But MS could've contributed that after hook as a patch, even. It has had an open feature request since feb 2018.)

That would not just have cleaned up the mess which is currently there, but also would have enabled you to widen support from the limited scenario where entries are specified as an object, to the full spec where there can be a single entry, an array, or a function. (Yes; Webpack supports dynamic entry generation via a callback function.)

Moving on, the code still mutates the configuration by manipulating the plugins array.
Also unnecessary. You can just new Plugin.apply( compiler ) them directly to the compiler instance for the same effect without needing to mutate the user configuration.

There's more there as well.
Such as the fact that the code is using the wrong hook (done vs afterEmit) to copy updated output to an on-disk filesystem. Or rather that it is using a completely wrong approach to write files out to disk to begin with.

It could have been handled in a much better way by creating a filesystem implementation that multiplexes the in-memory memory-fs filesystem and the base NodeFileSystem and which writes to both and reads from the in-memory one.
And then just assign that to compiler.outputFileSystem. Potentially you might need to do that in the environment or afterEnvironment hook to ensure it happens after webpack-dev-middleware sets the output filesystem to the in-memory one, but that's a minor thing to try.

Doing it that way would also have removed the need to have copyRecursiveToRealFsSync process and recheck the entire output filesystem for each single partial re-compilation. And it would have removed the need for the pathJoinSafe workaround function to exist.

I could probably continue on, but I'm going to stop now.
It's getting rather late in the evening.


In short though:
Please don't shift blame for burden caused by uninformed design and malpractice on your own part to external parties or the work they've done.