Best way to integrate webpack builds with ASP.NET Core 3.0?
Asked Answered
F

4

30

I'm upgrading my ASP.NET Core app to V3, and using Visual Studio 2019 for development / debugging. The process has been smooth except for this:

public void Configure(…..
                app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
                {
                    HotModuleReplacement = false,
                    ReactHotModuleReplacement = false
                });

UseWebpackDevMiddleware is no more: https://github.com/aspnet/AspNetCore/issues/12890 .

I'm now looking to understand the best way to have VS run webpack every time I debug, ideally only on JS code that has changed. This was the value I was getting from UseWebpackDevMiddleware. My app is a React app, and it seems like there is some new replacement for this if your app was started from CreateReactApp, but mine was not. (I believe apps that stated from this but then separated are called "ejected.") Is it somehow possible for me to still take advantage of whatever that facility is, even though my app does not leverage CreateReactApp? Also, what is the role of CreateReactApp after it bootstraps your new React application? I imagined it would be only used for inflating template code at the first go.

What is the role of Microsoft.AspNetCore.SpaServices.Extensions in all of this?

I don't need hot module replacement; I don't need server side prerendering. I'm really just trying to understand how to get my JS to transparently build (via Webpack) as part of my debugging process. Is this something that I could hook into MSBuild for? I imagine others are going to face the same question as they upgrade.

Thanks for any suggestions.

Franko answered 24/9, 2019 at 15:19 Comment(0)
E
6

You mention VS. My solution is good for Visual Studio, but not VS Code.

I use WebPack Task Runner: https://marketplace.visualstudio.com/items?itemName=MadsKristensen.WebPackTaskRunner

This adds webpack.config.js tasks into the "Task Runner Explorer" in Visual Studio, and you can then bind those tasks to events like "Before Build" or "After Build"

Eozoic answered 24/9, 2019 at 15:26 Comment(6)
Thanks for your comment. But would editing a JavaScript (or, in my case, Typescript) file trigger a build in VS upon debugging?Franko
Double clicking a task in Task Runner Explorer will launch it. So you could skip the binding to build, and then just double click on Watch - Development to start the webpack is watching the files... It will keep running.Eozoic
This doesn't do the hot module replacement though, which is the main thing which UseWebpackDevMiddleware offersPalpebrate
@JamieF The link to the extension is broken as it includes the closing square bracket as part of the query parameter. This should work: https://marketplace.visualstudio.com/items?itemName=MadsKristensen.WebPackTaskRunnerAddington
"Watch - Development" on the Task Runner works perfectlyBatruk
I ended up using the NPM task runner instead. marketplace.visualstudio.com/… It doesn't require webpack to be globally installed which is important because some projects may use a different version of webpack. This also allows you to have a custom set of scripts so you can perform certain steps before the watch.Soubrette
P
33

So, I was using UseWebpackDevMiddleware for HMR for a much smoother dev process - In the 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"
        })
    ]
});
Palpebrate answered 8/10, 2019 at 9:22 Comment(9)
This to me should have been the real answer since this allows you to retain HMR where as the task runner you have to manually trigger it.Renault
Could you please explain, why we use "wwwroot" in contentBase, not "dist"?Moule
wwwroot is the default "public" path for .NET applications.Tamra
UseSpa is no longer available in 3.1. asp.net core is not a stable platform, another breaking change.Ajaajaccio
This appears correct that UseSpa is gone as of 3.1 and 5.0 according to the documentation. @Kram, do you have any thoughts on a new approach that doesn't rely on this method? If so, would you be able to update your answer? Thanks...Franko
Hano Johannes Rossouw: Actually, Jamie's Solution is the much more concise one... if you run "Watch - Development " on the Task Runner as he suggests: code changes are automatically transpiled same as HMRBatruk
Wayne, BenjiFB: Still exists in 3.1, moved to SpaServices.Extensions github.com/dotnet/aspnetcore/blob/v3.1.5/src/Middleware/…Kurtzman
Any GitHub repo for this?Marje
I followed this steps but it is not working for me as expected. Here is the content of my project. #67517146Marje
I
7

In my opinion, Kram's answer should be marked as accepted as it really gives what's needed. I've spent some time setting up a .NET Core/React/Webpack project recently, and I could not get the spa.UseReactDevelopmentServer to work, but the spa.UseProxyToSpaDevelopmentServer works like a charm. Thanks Kram!

Here are my few gotchas they might help someone:

webpack.config.js (excerpt):

const path = require('path');
const webpack = require('webpack');

output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'build.[hash].js',
},

devServer: {
    contentBase: path.resolve(__dirname, 'public'),
    publicPath: '/dist',
    open: false,
    hot: true
},

plugins: [
    new webpack.HotModuleReplacementPlugin()
]
  1. DevServer sets its root by publicPath property and completely ignores the output.path property in the root. So even though your output files (for prod) will go to dist folder, under webpack server they will be served under a root by default. If you want to preserve the same url, you have to set publicPath: '/dist'. This means that your default page will be under http://localhost:8083/dist. I could not figure out a way to place assets under subfolder while keeping index in the root (other than hardcode the path for assets).

  2. You need HotModuleReplacementPlugin in order to watch mode to work and also hot: true setting in the devServer configuration (or pass it as a parameter upon start).

  3. If you (like me) copy some dev server configuration with open: true (default is false), you will finish up with two browsers opened upon application start as another one is opened by the launchSettings "launchBrowser": true. It was a bit of a surprise at the first moment.

Also, you will have to enable CORS for your project otherwise your backend calls will get blocked:

Startup.cs (excerpt):

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options => 
    {
        options.AddPolicy(name: "developOrigins",
            builder =>
            {
                builder.WithOrigins("http://localhost:8083");
            });
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
        app.UseCors("developOrigins");
}

If anything else will come to my mind I will update the answer, hope this helps to someone.

Webpack 5 update

webpack-dev-server command doesn't work anymore. Use:

"build:hotdev": webpack serve --config webpack.config.development.js

You might also need to add target: 'web' to your webpack.config.js in Webpack 5 to enable hot module reload to work:

module.exports = {
    target: 'web'
}

Alternatively you might need to create a browserlist file. Hope this issue gets fixed soon.

Intrigante answered 22/6, 2020 at 5:38 Comment(5)
hey man, so you just add HotModuleReplacementPlugin and I guess you added the option hot to your webpack script and Hot reload was working? I followed Kram's and add some of yours editing but i cannot make hot reload works. I still have to rebuild the solution everytime i do a change in css or js -.-Peacetime
I do have hot: true in the devServer settings (I've edited my answer to add it there too). Then I have a an npm script "start": "webpack-dev-server --config webpack.config.development.js" and I just start the server using yarn start.Kurtzman
thank you very much for your replay! I'm sure new in .Net so i feel very lost. I think my main problem is that in this project they use Razor pages. So i don't have a inde.html to generate for my dist/ folder. Or at least i didn't figured out how to do it yet. If you have any suggestion on how to work with webpack and razor.. is very welcome!Peacetime
Webpack doesn't need razor pages, it supplies it's own index.html, you only need to tell .NET back-end where to look for the page. Try to look at some blogs, ie codeburst.io/…. That should give you more understanding about the whole concept.Kurtzman
thanks for the link!! Is so difficult to find documentation online about this, maybe your link will help mePeacetime
E
6

You mention VS. My solution is good for Visual Studio, but not VS Code.

I use WebPack Task Runner: https://marketplace.visualstudio.com/items?itemName=MadsKristensen.WebPackTaskRunner

This adds webpack.config.js tasks into the "Task Runner Explorer" in Visual Studio, and you can then bind those tasks to events like "Before Build" or "After Build"

Eozoic answered 24/9, 2019 at 15:26 Comment(6)
Thanks for your comment. But would editing a JavaScript (or, in my case, Typescript) file trigger a build in VS upon debugging?Franko
Double clicking a task in Task Runner Explorer will launch it. So you could skip the binding to build, and then just double click on Watch - Development to start the webpack is watching the files... It will keep running.Eozoic
This doesn't do the hot module replacement though, which is the main thing which UseWebpackDevMiddleware offersPalpebrate
@JamieF The link to the extension is broken as it includes the closing square bracket as part of the query parameter. This should work: https://marketplace.visualstudio.com/items?itemName=MadsKristensen.WebPackTaskRunnerAddington
"Watch - Development" on the Task Runner works perfectlyBatruk
I ended up using the NPM task runner instead. marketplace.visualstudio.com/… It doesn't require webpack to be globally installed which is important because some projects may use a different version of webpack. This also allows you to have a custom set of scripts so you can perform certain steps before the watch.Soubrette
R
2

Extension is here : AspNetCore.SpaServices.Extensions

You could find examples here : https://github.com/RimuTec/AspNetCore.SpaServices.Extensions

With core 3.1 or 5.0 React with webpack

using :spaBuilder.UseWebpackDevelopmentServer(npmScriptName: "start");

Riyadh answered 27/4, 2021 at 12:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.