Hacking the ASP.NET Core React SPA template for Vue.js
Tags: aspnet, vue
Published: 2019-01-26 22:04:56

---
title: Hacking the ASP.NET Core React SPA template for Vue.js
published: true
description: Hacking the ASP.NET Core React SPA template for Vue.js
tags: asp.net, vuejs, vue
---

# ASP.NET Core SPA templates and Vue.js

Since version 2.1, ASP.NET Core has moved all SPA templates, previously available via the `Microsoft.AspNetCore.SpaTemplates` package, to the core repository. When that was done, all .NET developers that love VueJs were negatively surprises, since the Vue template was simply removed from the new set of templates. So, when you create a new ASP.NET Core Web Application, you have a choice between Angular, React
and React with Redux. The issue is described in details by Owen Caulfield in his [post on Medium](https://medium.com/swlh/asp-net-core-2-1-vue-js-a-marriage-made-in-heaven-6e6ab19b40f5). Owen refers to the [GitHub issue](https://github.com/aspnet/Announcements/issues/289).

# How to deal with that

Ideally, the .NET community needs to look at the issue and create a new template to address the issue. Below, I will go through the requirements for such a template and explain how to work around the problem before we get the template working.

## The React template

Let's have a quick look at how the React+Redux SPA template works.

An application created with that template contains a folder in the web project that is called `ClientApp`. This folder is a home for the React application, which uses WebPack.

![](https://thepracticaldev.s3.amazonaws.com/i/unifpo1gyh4gktsk310z.png)

To build the SPA, there are some additional items in the `csproj` file. You can look at it youself since I will not include these items in the post for the sake of brevity. In short, there is one `ItemGroup` there to include the `ClientApp` folder as content to the output of the build, and two `Target` tags to execute `npm` at the build and publish stages.

There are also some lines of code in the `Startup.cs` file that are important to make the whole thing work.

First, in the `ConfigureServices` method we can find this line:

```csharp
services.AddSpaStaticFiles(configuration => 
    { configuration.RootPath = "ClientApp/build"; });
```

and in the `Configure` method we have a few more lines too:

```csharp
app.UseStaticFiles();
app.UseSpaStaticFiles();

// MVC configuration is skipped but still needed

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

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

```

## Making it work with Vue

So, as we can see, there are no massive changes to the whole application setup to make the SPA work, and hence the `UseReactDevelopmentServer` accepts the `npm` command, it might be easily replaced to run another command instead.

### Replace the client app

So, let's start by replacing the React app with the Vue app. To do that, I created a Vue app in another directory, using the `vue create myapp` command. I added some options like using TypeScript and PWA, but it doesn't really matter. The Vue CLI 3 only uses the WebPack configuration, so the whole build configuration of the ASP.NET Core application should work as before. To check if this is the case, I removed the content of the `ClientApp` folder in my .NET project and replaced it with the content of my new Vue application directory:

![](https://thepracticaldev.s3.amazonaws.com/i/1v7stdhv2io2hwnup96l.png)

You can see that my `ClientApp` folder contains the Vue app instead of the React app. I can try building the whole solution now, and it builds as expected.

### Middleware

However, if I run the app, I get an exception in the `ReactDevelopmentServerMiddleware`, because it tries to execute `npm run start`, but the Vue development server is started by `npm run serve`. It appears to be an easy fix, so I only needed to change the line in my `Startup.cs`:

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

    if (env.IsDevelopment())
    {
        spa.UseReactDevelopmentServer(npmScript: "serve");
    }
});
```

But now, when I start the application, it opens the browser window that continuously hangs trying to load the home page. At the console output, however, I can clearly see that the Vue development server has started successfully and there are no exceptions.

The reason for the hang is this code in the `ReactDevelopmentServerMiddleware` class:

```csharp
Match match = await npmScriptRunner.StdOut.WaitForMatch(new Regex("Starting the development server", RegexOptions.None, ReactDevelopmentServerMiddleware.RegexMatchTimeout));
```

As you can see, it starts the `npm` with a given command, which we can replace, but it waits for Node to produce a certain console output, which is hardcoded to `Starting the development server`. If you look close to the output of the `npm run serve` for Vue, you can see that it says `Starting development server`. So, the code above waits for the output until it times out and throws.

### Change the output message

So, here comes a hack, since everything we did before was rather legit. Now, we need to replace the output message. It can be done by changing the `serve.js` file in the `ClientApp/node_modules/@vue/cli-service/lib/commands` directory. Here is my change:

```js
  }, async function serve (args) {
    info('Starting the development server...')

```

Now, if I run the application again, it starts the browser, but I get an exception that the middleware cannot proxy the request to the development server:

> HttpRequestException: Failed to proxy the request to http://localhost:54252/, because the request to the proxy target failed. Check that the proxy target server is running and accepting requests to http://localhost:54252/

(the port number can vary)

At the same time, I can see that at the time the development server of Vue was still building and linting the app. When that is done, I refreshed the page, and everything worked as expected.

### Note on Browser Sync

It is possible to use the Browser Sync by installing the Vue CLI plugin by executing the `vue add browser-sync` in the `ClientApp` directory and using the `serve:bs` as an argument for the middleware instead of `serve`. But then the whole thing stops working again. That's because the plugin uses its own code to handle the `serve:bs` command. But it can also be fixed by changing the text to `Starting the development server` in the `ClientApp/node_modules/vue-cli-plugin-browser-sync/index.js` file.

### Publishing

If you run the `dotnet publish` command for the React app, you will see that the distribution version for the SPA is built to the `build` directory in the `ClientApp`. That also corresponds with this line in the `Startup.cs` file:

```csharp
services.AddSpaStaticFiles(configuration => 
    { configuration.RootPath = "ClientApp/build"; });
```

and this line in the `csproj` file:

```xml
<DistFiles Include="$(SpaRoot)build\**; $(SpaRoot)build-ssr\**"/>
```

As you can see, it is very easy to fix by changing `build` to `dist` in both places. The `build-ssr` part can be safely removed if you don't use the Server-Side Rendering. So, the code would be:

```csharp
services.AddSpaStaticFiles(configuration => 
    { configuration.RootPath = "ClientApp/dist"; });
```
in the `Startup.cs` and

```xml
<DistFiles Include="$(SpaRoot)build\**"/>
```
in the `csproj` file.

When those changes are done, you can start developing and publishing your Vue SPA app hosted in the .NET Core web application service.

## Shortcut

It is not nice to hack the code that run `npm` commands for the Vue CLI, so you might want to use the complete code for the Vue development server middleware that I've composed from the React development server middleware. Unfortunately, many helper classes for the middleware are internal, so I had to include those classes as well. All that code has the Apache 2.0 licence so it is not a problem to use the modified version of it as soon as the origin of the code is stated clearly. [Here is my gist](https://gist.github.com/alexeyzimarev/f0262426aa38e2c1ed2913252ceb5e7a). If you copy this file to your project, you can just use it:

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

    if (env.IsDevelopment())
    {
        spa.UseVueDevelopmentServer(npmScript: "serve"); // use serve:bs for Browser Sync
    }
});
```