dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.59k stars 10.06k forks source link

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

Closed danroth27 closed 3 years ago

danroth27 commented 5 years ago

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:

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

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.

bugproof commented 5 years ago

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

atotalnoob commented 5 years ago

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

pealmeid commented 5 years ago

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.

ryhled commented 5 years ago

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

Sebazzz commented 5 years ago

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

jhaygood86 commented 5 years ago

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.

GGAlanSmithee commented 5 years ago

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.

jmoerdyk commented 5 years ago

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:

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

SteveSandersonMS commented 5 years ago

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.

atrauzzi commented 5 years ago

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?

PhotoAtomic commented 5 years ago

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.

valeriob commented 5 years ago

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.

Frozenthia commented 5 years ago

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.

crhistianramirez commented 5 years ago

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

jxlarrea commented 5 years ago

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.

dogfoodsilo4 commented 5 years ago

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.

Grandizer commented 5 years ago

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

atrauzzi commented 5 years ago

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.

ChristopherHaws commented 5 years ago

@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.

atrauzzi commented 5 years ago

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.

rich25200 commented 5 years ago

Somebody have a solution now ??

Asp.Net Core 2.2:

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

Asp.Net Core 3.x:

?????

Thanks

jessehallam commented 5 years ago

@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 commented 5 years ago

@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.

ciacco85 commented 5 years ago

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

MorleyDev commented 5 years ago

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.

atrauzzi commented 5 years ago

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

webtasarim16 commented 5 years ago

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.

Sebazzz commented 5 years ago

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.

webtasarim16 commented 5 years ago

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 5 years ago

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 :)

chamikasandamal commented 5 years ago

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.

Sebazzz commented 5 years ago

For the webpack users: Additional, more basic, example without HMR: https://github.com/Sebazzz/IFS/commit/c8e7d27768f8f6aaf16d13b3f68aaf6c20ca4e0d

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.

irejwanul commented 5 years ago

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.

flcdrg commented 5 years ago

@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?

jcmontx commented 5 years ago

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

kmiskiewicz commented 5 years ago

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

emilmuller commented 5 years ago

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.

poimis commented 5 years ago

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?

markalroberts commented 5 years ago

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?

alextof commented 5 years ago

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

markalroberts commented 5 years ago

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"
    ]
  }
markalroberts commented 5 years ago

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

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"
        })
    ]
});
poganytamas commented 5 years ago

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.

poganytamas commented 5 years ago

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 :(

paulirwin commented 5 years ago

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.

rjgotten commented 5 years ago

@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?

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.

ericgreenmix commented 5 years ago

@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.

rjgotten commented 5 years ago

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.

rjgotten commented 5 years ago

@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.

rjgotten commented 5 years ago

@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.