Creating a VueJS (with TypeScript) SPA on ASP.Net Core 2.1

As you might already know, Visual Studio 2017 v15.3+ you can choose web application templates for Angular, React, and React+Redux only.

However, VueJS is not available. It was removed since there were limited maintainers, but now it is back, at least from the command line.

dotnet new --install Microsoft.AspNetCore.SpaTemplates::*

Now if you list them you will see VueJS available.

dotnet new -l

So, there it is.

But wait, this template is using old versions of .Net Core and WebPack (node modules), so, let’s update it.

These are the things we will do in this tutorial:

  • Create a new ASP.NET Core with Vue.js project using this template
  • Migrate it to .Net Core from 2.0 to 2.1
  • Update package.json to update WebPack 2 to 4
  • Turn it into another template so we can reuse it

Let’s first create a new project using the Vue template

dotnet new vue -n vuets -o vuets

Now, let’s open it with our favorite editor and modify the following files:

  • vuets.csproj
  • Startup.cs
  • Program.cs
  • package.json

The migration will be according to the official guide

Let’s start with vuets.csproj and modify as follows:

from:
<TargetFramework>netcoreapp2.0</TargetFramework>
to:
<TargetFramework>netcoreapp2.1</TargetFramework>

Next, replace AspNetCore.All with AspNetCore.App and corresponding version, use dotnet --list-runtimes to see current version.

from:
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.7" />
to:
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.1" />

Keep in mind, Version attribute restricts to use previous versions, in other words, it means minimum required version.

Remove from<DotNetCliToolRefence> element: Microsoft.VisualStudio.Web.CodeGeneration.Tools and use a globally installed tool insteaddotnet tool install -g dotnet-aspnet-codegenerator

Now, let’s modify program.cs file as suggested by the official guide:

Similar to the following highlighted changes:

For startup.cs optionally you can modify it for GDPR, but this is only for templates .cshtml more info here, other than that this file should be modified as follows in those sections:

public void ConfigureServices(
...
// optional GDPR compliant temaplates
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true,
options.MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.None;
});
services.AddMvc()                .SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1);
...
public void Configure(
...
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
   app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy(); // optional GDPR

That’s it related to C# code, now for the frontend part, we need to also update our node modules, specially to use WebPack 4 instead of the older 2 in this template, since the last version is faster. Thankfully it is only a matter to update package.json file, and adding another module.

Update package.json with latest npm packages using VSCode extension Npm Dependency (ext install npm-dependecy)

Once updated versions for npm modules in our package.json file, we need to leave bootstrap as before (3.3.6) since version 4 has some major changes that might broke the sample, but you can always update all files using bootstrap to new version, or use any other CSS framework. Apart from that, we also need to add webpack-dev-middleware , uglifyjs-webpack-plugin and webpack-cli package, as well replace awesome-typescript-loaderwith ts-loader and use beta version of extract-text-webpack-plugin.

So the package.json file should be as follows, remember that it is only based upon the current VueJS template, it might be different on newer versions.

{
"name": "vuets",
"private": true,
"version": "0.0.0",
"devDependencies": {
"@types/webpack-env": "^1.13.6",
"aspnet-webpack": "^3.0.0",
"bootstrap": "^3.3.6",
"css-loader": "^1.0.0",
"event-source-polyfill": "^0.0.12",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"file-loader": "^1.1.11",
"isomorphic-fetch": "^2.2.1",
"jquery": "^3.3.1",
"style-loader": "^0.21.0",
"ts-loader": "^4.4.2",
"typescript": "^3.0.1",
"uglifyjs-webpack-plugin": "^1.2.7",
"url-loader": "^1.0.1",
"vue": "^2.5.17",
"vue-loader": "^15.2.6",
"vue-property-decorator": "^7.0.0",
"vue-router": "^3.0.1",
"vue-template-compiler": "^2.5.17",
"webpack": "^4.16.4",
"webpack-cli": "^3.1.0",
"webpack-hot-middleware"
: "^2.22.3",
"webpack-dev-middleware": "^3.1.3"
}
}

You might be wondering, where is npm tasks in that package.json file?, well commands are in projects file vuets.csproj in case you haven’t noticed.

Optionally you can add those tasks, it won’t affect the dotnet publish command.

Notice, though, that dotnet run command will execute webpack in development mode, and if you haven’t seen there are some Node Packages that add HMR (Hot Module Replacement) integration.

Let’s install node packages npm install and execute dotnet run but if you access your localhost site, it might show some errors related to failing module or template loading, this is normal, since vue-loader from version 13 upward it introduced some changes. On each require call of our components, we need to append .default for example in our boot.ts file, the routes should be modified as follows:

import './css/site.css';
import 'bootstrap';
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
const routes = [
{ path: '/', component: require('./components/home/home.vue.html').default },
{ path: '/counter', component: require('./components/counter/counter.vue.html').default },
{ path: '/fetchdata', component: require('./components/fetchdata/fetchdata.vue.html').default }
];
new Vue({
el: '#app-root',
router: new VueRouter({ mode: 'history', routes: routes }),
render: h => h(require('./components/app/app.vue.html').default)
})

Similarly on the other files.

Also webpack.config.js and webpack.config.vendor.js needs some modifications too.

Remove awesome-typescript-loader and replace old uglify with uglifyjs-webpack-plugin

File: webpack.config.js

...
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = (env) => {
const isDevBuild = !(env && env.prod);
const devMode = isDevBuild ? 'development':'production';
return [{
mode: devMode,
stats: { modules: false },
context: __dirname,
resolve: { extensions: [ '.js', '.ts' ] },
entry: { 'main': './ClientApp/boot.ts' },
module: {
rules: [
{ test: /\.vue\.html$/, include: /ClientApp/, loader: 'vue-loader', options: { loaders: { js: 'ts-loader' } } },
{ test: /\.ts$/, include: /ClientApp/, use: [
{
loader: 'ts-loader',
options: {
appendTsSuffixTo: [/\.vue\.html$/]
}

}
] },
...
plugins: [
new VueLoaderPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(isDevBuild ? 'development' : 'production')
}
}),
...
] : [
// Plugins that apply in production builds only
new UglifyJsPlugin(),
new ExtractTextPlugin('site.css')
])
}];
};

File webpack.config.vendor.js

...
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = (env) => {
const isDevBuild = !(env && env.prod);
const devMode = isDevBuild ? 'development':'production';
const extractCSS = new ExtractTextPlugin('vendor.css');
return [{
mode: devMode,
stats: { modules: false },
resolve: { extensions: [ '.js' ] },
entry: {
...
plugins: [
...
].concat(isDevBuild ? [] : [
new UglifyJsPlugin()
])
}];
};

Now, run dotnet run again, and hopefully, everything will be working fine.

Finally, we will turn this project (based upon the VueJS SPA template) into another template in order to save some time on future VueJS projects.

So it only requires a simple json file inside .template.config directory in this project’s root directory, so create this folder as well the file template.json and edit as follows:

{
"$schema": "http://json.schemastore.org/template",
"author": "vhanla",
"classifications": [ "Web", "MVC", "SPA" ],
"name": "ASP.NET Core 2.1 with VueJS and TypeScript",
"identity": "vuejs.ts",
"groupIdentity": "aspnetcore-vuejs",
"shortName": "vuets",
"tags": {
"language": "C#",
"type": "project"
},
"sourceName": "vuets",
"preferedNameDirectory": "true",
"sources": {
"source": "./",
"target": "./",
"exclude": [
".template.config/**",
"node_modules/**"
]
}
}

Delete all unwanted files and directories from that new template directory, like node_modules, npm lock json or npm-shrinkwrap.json, also bin and obj directories, as well .vscode .vs directories.

Once we have our template directory and files cleaned, we can test it, so first we install it using the following command:

dotnet new --install <path to this template> it will replace existing template, so it will reinstall if we add some modifications to our template.

To uninstall dotnet new -u <path, name or nuget id>

So, after having this new template installed, just start another project as usual:

dotnet new vuets -n test -o test

This will use our brand new template to create another VueJS with TypeScript ASP.NET SPA project inside test directory.

Execute the following commands:

dotnet restore
npm install
dotnet run

Navigate to http://localhost:52570 and enjoy!

CONCLUSION

It is relatively easy to create custom templates not only from scratch but also reusing already created projects, and furthermore reusing templates, customize them and install them as new templates.

Template Repository:

You can see explore this modified template at:

https://github.com/vhanla/vuets

Sources: