It’s hard enough trying to choose frameworks for your tech stack. It sometimes becomes frustrating when you find out they don’t integrate very easily. Recently I’ve had the opportunity to integrate ASP.NET Core and Vue.js so I thought I’d share my knowledge with you!
First, let’s create a Blank Solution. Within this solution, we will be creating two projects. Creating two separate projects allows a better separation of concerns between back-end and front-end code.
Next, we will add an ASP.NET Core Web Application project and use the Web Application (Model-View-Controller) template.
Finally, we create a Blank Node.js Web Application project.
Our solution tree should look something like this:
Make sure to go to the properties of your solution and set the startup project to single and select the MVC project.
Let’s install the packages we will need. Instead of installing packages manually through the command line, we will instead populate the package.json in our Node.js project and install all the packages in one go. Simply copy and paste the following code block, replacing the default code. Then run a clean install of the packages.
// package.json
{
"name": "your-project-name-here",
"version": "1.0.0",
"description": "YourProjectNameHere",
"scripts": {
"build": "webpack --colors --progress",
"watch": "webpack --colors --progress --watch"
},
"dependencies": {
"@babel/polyfill": "^7.4.0",
"@babel/runtime": "^7.7.4",
"core-js": "^3.0.0",
"vue": "^2.6.10"
},
"devDependencies": {
"@babel/core": "^7.3.3",
"@babel/plugin-proposal-class-properties": "^7.3.3",
"@babel/plugin-proposal-decorators": "^7.4.0",
"@babel/plugin-proposal-object-rest-spread": "^7.4.0",
"@babel/preset-env": "^7.3.1",
"babel-loader": "^8.0.5",
"clean-webpack-plugin": "^2.0.1",
"css-loader": "^2.1.1",
"mini-css-extract-plugin": "^0.5.0",
"node-sass": "^4.13.0",
"sass-loader": "^7.3.1",
"vue-loader": "^15.7.2",
"vue-template-compiler": "^2.6.10",
"webpack": "4.29.3",
"webpack-cli": "3.2.3"
}
}
In our Index view of the Home feature, we can add a custom element.
// Index.cshtml
This is where our matching Vue component will mount.
Then in the layout, we can add some stylesheets and scripts that will be generated later by Webpack. In the head of our layout we will add these link tags:
// _Layout.cshtml
And at the end of our body will add these script tags:
// _Layout.cshtml
That’s it for our MVC project! We’ve given our Vue application a home to live. Now we have to move in.
Let’s create a file, webpack.config.js, and a folder named src. Then, rename server.js to index.js and move it into the src folder. Within the src folder, create two more folders: components and styles. Finally, in the styles folder create style.scss.
Our Node.js Project should look something like this:
Create a file named HelloWorld.vue in our components folder and insert the following code:
// HelloWorld.vue
Hello, world!
In the same folder, create a JavaScript file named component-loader. First, let’s import Vue and our HelloWorld component.
// component-loader.js
import Vue from 'vue';
import HelloWorld from './HelloWorld.vue';
Second, let’s create an array of objects that contains our component and its corresponding custom element name.
// component-loader.js
const components = [
{
component: HelloWorld,
element: 'hello-world'
},
];
When you add more Vue components, all you will need to do is import the component and add another object with its reference and custom element name to the components array. I suggest translating from pascal-case to kebab-case as per Vue’s style guide, but ultimately the choice is up to you.
For example,
// component-loader.js
const components = [
{
component: HelloWorld,
element: 'hello-world'
},
{
component: NewComponent,
element: 'new-component'
}
];
Finally, let’s create and export the function that will render our Vue components.
// component-loader.js
export default {
loadComponents() {
components.forEach(({ component, element }) => {
// Is the custom element in the DOM?
if (!document.querySelector(element)) {
return;
}
// Create a new Vue instance and mount it to the custom element.
new Vue({
render: createElement => createElement(component)
}).$mount(element);
});
}
}
This function will take the components array and check if each custom element is present on the current page’s DOM. If it finds the element, it initializes and mounts the related Vue component. Once mounted, we’re in Vue land!
In index.js we want to import our component loader and call it.
// index.js
import ComponentLoader from './components/component-loader';
ComponentLoader.loadComponents();
Last but not least, we want to configure Webpack. Let’s add our constants. This includes plugin imports, paths, and babel configuration. For brevity, we’ll skip how this all works and just go through the setup.
// webpack.config.js
const path = require('path');
const CleanPlugin = require('clean-webpack-plugin');
const ExtractCssPlugin = require('mini-css-extract-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const sourceDir = path.resolve(__dirname, './src');
const styleDir = path.resolve(sourceDir, './styles');
const buildDir = path.resolve('../YourProjectNameHere.MVC/wwwroot/dist');
const getSourcePath = srcPath => path.resolve(sourceDir, srcPath);
const babelLoader = {
loader: 'babel-loader',
options: {
presets: [['@babel/preset-env', { modules: false, useBuiltIns: 'usage', corejs: 3 }]],
plugins: [
'@babel/plugin-proposal-object-rest-spread',
['@babel/plugin-proposal-decorators', { legacy: true }],
'@babel/plugin-proposal-class-properties'
]
}
};
Next, we insert our module.exports
// webpack.config.js
module.exports = {
devtool: 'source-map',
entry: {
master: getSourcePath('index.js'),
style: `${styleDir}/style.scss`
},
mode: 'development',
module: {
rules: [
{
test: /\.scss$/,
use: [ExtractCssPlugin.loader, 'css-loader', 'sass-loader']
},
{
exclude: /(node_modules|bower_components)/,
include: sourceDir,
test: /\.js?/,
use: [{ ...babelLoader }]
},
{
test: /\.css$/,
use: [ExtractCssPlugin.loader, 'css-loader']
},
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
chunks: 'all',
name: 'vendor',
test: /[\\/]node_modules[\\/]/
}
}
},
},
output: {
filename: '[name].min.js',
chunkFilename: '[name].min.js',
globalObject: 'this',
path: `${buildDir}`,
publicPath: '/wwwroot/dist/'
},
plugins: [
// Deletes build directory before rebuilds.
new CleanPlugin({
cleanOnceBeforeBuildPatterns: [`${buildDir}/**`],
dangerouslyAllowCleanPatternsOutsideProject: true
}),
// Extract CSS from bundles, and compile them into a single CSS file
new ExtractCssPlugin({
filename: '[name].min.css'
}),
// Clone any other rules you have defined and apply them to the corresponding language blocks in .vue files.
new VueLoaderPlugin()
],
};
If you have a different structure to your solution, you may need to tweak the path constants and entry points to suit your needs.
With that, we’ve integrated MVC and Vue!
Once within a Vue entry point, you have all the great parts of Vue such as single-file components and reactive data. Adding more component entry points is easy too.
There is one caveat. Vue components have no elegant way to receive data from a Razor view. It is possible to send data through an API controller in the MVC project and make an AJAX call from your Vue components; however, we’ll have to cover that in a future article so stay tuned!
We love to make cool things with cool people. Have a project you’d like to collaborate on? Let’s chat!
Stay up to date on what BizStream is doing and keep in the loop on the latest in marketing & technology.