Using VueJs in Sitecore

Background

We have a very large, (and now pretty old!) enterprise web application utilizing Sitecore MVC and Angular 1.

Over the years Angular has worked well for us in some areas but has shown weaknesses in others. Some of the side effects from it's 2 way databinding, it's global state management and its over complicated digest system are just a few headaches we've head. This leads to the codebase being relatively complicated for new team members. Working on a team which adds new developers frequently Angular works but it could be better.

Caveat to the above, using a combination of Typescript with Angular has really helped us, and abstracting as much away from the angular layer as possible also helps simplify things

VueJs.

VueJs has a fantastic feel to it. it is an easy to pick up, intuitive component based Javascript framework. It feels very much like Angular to use, it's syntax is very similar but overall Vue is much less opinionated. It has improved many of the pain points that I found problematic in Angular and has also introduced concepts seen in React such as the virtual DOM. In terms of performance it is also much better (Angular suffers from an incurable performance issue, as the page gets larger and larger it quickly becomes slower and slower.)

Vue enforces a one way data flow between components meaning there is only one source of truth and global state is managed via a Redux type pattern - Vuex.

Why not Angular 2+?

I'm sure Angular 2 is also a good option but it's something I haven't really focussed any time on. However as a team we are also exploring Angular 2+.

What about React?

Using React with sitecore is certainly doable. The main complexity arises when you need to solve the issue of combining JSX and sitecore field renderers.

There are a few blog posts which provide some answers... and of course you could also use JSS.

https://sitecoreblog.alexshyba.com/sitecore-and-react-how-hard-can-it-be/

http://sitecorepro.blogspot.com/2017/08/sitecore-and-react-simple-integration.html

https://www.sitecorenutsbolts.net/2017/07/04/Sitecore-React-Getting-Started/

What about JSS

If you can use JSS it is certainly be worth exploring. It will, in Sitecore 9.1 come out of beta and looks fantastic.

However there are many scenarios where you might not want to use JSS and ours was one of them.

This isn't a JSS post, if you want more info about JSS - http://jss.sitecore.net.

Integrating Vuejs into a Sitecore solution

A caveat.. I've used Vue many times before but never at enterprise scale so this post really documents a prototyping effort.

These are the main problems I wanted to solve:

  • Installing Vue into existing Sitecore solution and setting up build.
  • Using Vue in Sitecore controller renderings (razor files).
  • Rendering Sitecore field data into Vue components with experience editor support.
  • Communication between different Vue components in different renderings.

Installing and building Vue

This is pretty straightforward.

1. create package.json

yarn init

2. Install the following dependencies

yarn add vue
yarn add webpack webpack-cli --dev
yarn add vue-loader vue-template-compiler vue-  style-loader css-loader --dev
yarn add @babel/core @babel/plugin-syntax-  dynamic-import @babel/preset-env babel-loader cross-env css-loader --dev

3. Configure webpack and babel

The way I've always built my Vue applications is via webpack (If you don't know webpack go learn it - it's a great tool.) Below is my basic dev config. Note it does not include any production build steps (minification, bundling etc etc) which are essential so don't forget those.

'use strict'
  const webpack = require('webpack')
  const path = require('path');
  const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
  mode: 'development',

  entry: [
    './vue/app.js'
  ],
  output: {
    filename: 'main.js',
    path: path.resolve('./vue-build')
       },
       resolve: {
             alias: {
                    'vue$': 'vue/dist/vue.esm.js'
         }
   },
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: 'vue-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [path.join(__dirname, 'src')],
      },
      {
        test: /\.scss$/,
        use: [
          'vue-style-loader',
          'css-loader',
          {
            loader: 'sass-loader',
          },
        ],
      },
      {
        test: /\.css$/,
        use: [
          'vue-style-loader',
          'css-loader',
          {
            loader: 'css-loader',
          },
        ],
      },
    ]
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new VueLoaderPlugin()
  ]
}

note: the 'resolve' section is a real gotcha and is required to build Vue in the style we will be coding later in this tutorial. Don't forget to add it to your webpack config!

And my .babelrc

Babel is a tool to transpile newer Javascript language features (e.g. ES6) into older versions of Javascript (e.g. ES5) that work in current and older browsers or environments.

{
    "presets": [
      [
        "@babel/preset-env" ,
        {
          "modules" : false
        }
      ]
    ],
    "plugins": [
      "@babel/plugin-syntax-dynamic-import"
    ]
  }

Note: If you use typescript you won't need babel. For this prototype I didn't introduce typescript but i would if/when we use Vue outside of a prototyping context.

Update your package.json

...
"scripts": {
    "build": "webpack --config build/webpack.config.dev.js",
  },
...

Run with

yarn build

(Although it won't work until we create './vue/app.js' in the next section..)

Using Vue in Sitecore controller renderings

Create a rendering in Sitecore, for this example I created a simple view rendering with the path:

/vue/Components/HelloWorld.cshtml

Below is the contents of that file.

@inherits System.Web.Mvc.WebViewPage

<!-- component mounted here -->
<div id="vue-hello-world"></div>

<!-- component template -->
<script type="text/x-template" id="hello-world-template">
    <div>
          <h1>Hello Vue</h1>
          <div class="container">
               {{ message }}
          </div>
    </div>
</script>

<!-- This is our webpack output - this should live with your other scripts in the foot of the page not at component level.. but we are prototyping... :) -->
<script type="text/javascript" src ="/vue-build/main.js"></ script>

There are many ways to define Vue templates, to play nicely with Sitecore we are using the x-template option.

Next we need an entry point for our Vue application

/vue/app.js

import HelloWorld from './Components/hello-world.js'

This just imports all our components, each parent component is it's own Vue instance.

The hello-world component

If you've used Vue before you will know a vue component typically has a .vue extension (hence the webpack loader) and has the following makeup:

<template>
    <div>Hello world</div>
</template>
<script>
export default {
    name: 'hello-world',
    data() {
        return ...
    }
</script>
<style>
    .blah {
    }
</style>

Using .vue files doesn't really work for us, we need our markup to come from the rendering.

Instead we can create plain old js files:

/Components/hello-world.js

import Vue from 'vue'

let component = {
   name: 'hello-world',
   template: '#hello-world-template',   
   data()
   {
       return {
             message: "Hello World!"
       }
   },
};

// Initialise Vue
new Vue({
   el: '#vue-hello-world',
   render: h => h(component)
});

export default component

And that's it! If you run the webpack build the hello world Vue component should render.

To render child components:

The syntax to achieve this also caught me out but here's how it done:

hello-world.js

import AChildComponent from './hello-world-child.js'

let component = {
    name: 'hello-world',
    template: '#hello-world-template',
    components: { 'hello-world-child': AChildComponent },
...

hello-world.cshtml

...
<script type="text/x-template" id="hello-world-template">
        ...
        <hello-world-child></hello-world-child>
    </div>
</script>
...

<!-- child component template -->
<script type="text/x-template" id="hello-world-child-template">
    <div>
        <h1>Hello!</h1>
    </div>
</script>

and you would create the child vue component hello-world-child.js. It doesn't need referencing in app.js webpack will sort this out for you.

Vue.Component

As we can't use .vue files, Vue components are another option in .js files.

However coding a Vue application is this style does come with caveats as documented here:

https://frontendsociety.com/why-you-shouldnt-use-vue-component-ff019fbcac2e

Rendering Sitecore field content

Using this method we are simply adding markup and Javascript to standard controller renderings (razor files) which keeps a clean separation of server and client code. So whichever method you use to render sitecore fields should work just fine.

Vuex

You can just use Vuex as usual. Create an instance of the store and ensure each Vue instance (there is one per component in this setup) references that same store.

comments powered by Disqus

Dave Leigh

Web, and long time Sitecore developer based in Bristol, UK, working at True Clarity - trueclarity.co.uk - @technologytalks - Minion.
I occasionally do other things too.