Skip to main content

23 How to use Vue.js and Turbolinks together

Episode 182 · April 17, 2017

Using Vue.js and Turbolinks together can be a little complex to wrap your head around at first. We not only dive into how this works but we take a look at the vue-turbolinks node module I made so that you can easily use the two together.

Javascript VueJS


Resources

To add Vue-Turbolinks into your Rails Webpacker installation, you can do the following:

yarn add vue-turbolinks
import TurbolinksAdapter from 'vue-turbolinks';

document.addEventListener('turbolinks:load', () => {
  var vueapp = new Vue({
    el: "#app",
    template: '<App/>',
    mixins: [TurbolinksAdapter],
    components: { App }
  });
});

Transcripts

What's up guys? This episode we're talking about making your vuejs applications Turbolinks compatible, so this process is actually exactly the same as what we went through in the previous episode where we made simple MDE compatible with turbolinks. What we have to do is listen for the turbolinks load event. When that happens, we use that to initialize vuejs, and then before we go to the next page we want it to tear down vue so that when the caching happens with turbolinks, we can clear up the vue app, replace it back with the original element that was on the page that we mounted the vue app into, and then turbolinks will cache that properly and our navigation and everything weather it's through link clicking with turbolinks or it's the browser back and forward buttons, all those should still work and so that's what we have to do in this episode. So let's take a look at the basic vue application that we have that comes out-of-the-box with webpacker, we're going to use this as our example, and show you how we can make this compatible with turbolinks.

This app is super straightforward, it adds an event listener when the DOM content is loaded, we initialized vue and we print out the app to the console. That's as simple as it is. It has a single file template for the app itself, which is just a div with the id of app and puts a message onto the page. I've changed this a little bit, so we show the current time on the page, and that's going to give us the ability to check to see if vue actually rerendered or not, so we'll be able to watch that timestamp and make sure that that updated. Of course the obvious first thing that we need to do is change this over to turbolinks load, because that is going to make sure that whenever you navigate to another page, this will initialize for you again, which of course that doesn't happen normally without turbolinks load, because that would only happen and fire the DOM content loaded that single time when you load the page. Let's take a look at this, it does work and we can pay attention to the timestamp here or 55 seconds if we come back it is now 02, and all of that is updating properly. However, if we were to hit back in the back button, that timestamp has not changed, so that's actually turbolinks caching issue that we have to address what has happened is that turbolinks cached the html, the final html when yo navigated away, and of course, because we did that and we didn't clean up the vue application, we don't have the original div with the id of "hello" on it anymore, so if we were to inspect the vue source, we have this div id of "hello" on this page. Turbolinks has cached it though, and the cache version of it is actually the vue html. So the way that vue initializes is it goes and looks for that tag that you specified in your vue initializer, so I have id of "hello", and the vue app looks for the id of "hello", and it takes and removes that item and replaces it with the vue application, so one cheat that you can do is you can force that id on your vue applications parent to have the exact same id of "hello", and that can make it so that you would remove the original item, replace it with one that looks very similar, that has the same matcher, and that can work as the solution for this, but not every time you're going to have the exact same html layouts, so we're not going to use that as our solution for this, because that might change, and you might be mounting a vue application that you didn't write, in which case you wouldn't have the html layout or whatever for that. We're not going to use that solution and that doesn't work with some of the custom element stuff or whatever, so what we're going to be doing is we're going to be building a mix-in for vue, that makes it compatible with turbolinks, so we're going to basically set up a couple event handlers, so that before cache, we're going to clean up vue and vue is going to remember the original element, and then replace it on the page before turbolinks goes and caches that html. Keep in mind when we go through this I'm just explaining how this works, I've actually created a node module with a friend of mine, that we have made so that you can just include this with yarn, and then include that as a mix-in inside your vue app, which I'll show you at the end and you don't have to worry about any of this. All you're going to have to do is make sure you add the event listener here at the top and wrap your vue code and then include that mix-in, but I will mention that later. What we have to do is we have to add:

hello_vue.js

document.addEventListener('turbolinks:before-cache' () => {
    app.destroy 
    }
)

That makes us a pretty good start where we can go turn off the application whenever turbolinks is beginning to navigate to another page, but this is going to be something that we still need to clean up that html. One thing we can do is use some of those events inside of the vue app, where we can have:

hello_vue.js

document.addEventListener('turbolinks:load', () => {
    const app = new Vue({
        el: '#hello',
        template: '<App/>',
        components: { App },
        beforeMount: function() {
            this.$originalElement = this.el.outerHTML 
        },
        destroyed: function() {
            this.$el.outerHTML = this.$originalElement
        }
    })
})

This is going to give us that ability to go and have this app cleaned up automatically for us. If we go here and we click "back", and then we click "back" in the browser, we're going to see that that time stamp has now been updated, so let's pay attention to it. We see 35 seconds, and then we hit back and now we see 43. This is great, but you're starting to notice there are a few errors coming up in the console, and that is because when we go navigate away, well number one: We can't find that element of "hello", and when we hit "back", it's trying to run that before cache callback, we've already destroyed that vue app, plus we've got this constant here, and this should be a variable because it's not really constant since we're initializing every single time that turbolinks loads, so we really want this to be a variable instead of a constant. Since that will change. This, we actually should probably pull this turbolinks cache out to it's own function:

hello_vue.js

import Vue from 'vue/dist/vue.esm'
import App from './app.vue'

function destroyVue() {
    this.destroy()
    document.removeEventListener('turbolinks:before-cache', destroyVue)
}

document.addEventListener('turbolinks:load', () => {
    var element = document.getElementById("hello")
    if (element =! nil) { }
    var app = new Vue({
        el: element,
        template: '<App/>',
        components: { App },
        beforeMount: function() {
            this.$originalElement = this.$el.outerHTML 
            document.addEventListener('turbolinks:before-cache', destroyVue.bind(this)) => {

    }
        },
        destroyed: function() {
            this.$el.outerHTML = this.$originalElement
        }
    })
})

That's going to set everything up pretty well, but we should also make sure to clean up and remove the event listener for the turbolinks before cache in case you're going to another page that didn't actually load the view app, so we don't want this event listener to fire unless we actually initialize a vue application, so we have that set up so that it will create this function, and then run that accordingly every time. There's one last thing I want to do here, and that is I want to grab that hello element, that we were mounting on, so I want to check if that element is present before we initialize vue, so that way this doesn't set up and create an event listener, it doesn't attempt to load vue or any of that stuff unless that element actually exists. This is going to set it up so that we don't even try to load vue if that page doesn't have this specific vue application, tag. So page one does have it but this one does not, page 2 doesn't, page 3 doesn't, and you can see that there are no errors any more, and if we hit back in the browser and go all the way back to page 1, we'll see that we now get 47 52, and we can go down and navigate to any of these other pages, and we'll get 47 59, and so on. So this is all working correctly now, we have a fully functional two callbacks inside of the vue app to take care of some of the turbolinks life-cycle stuff, and we also have some of the turbolinks life-cycle stuff wrapping the vue code so it's all encompassed pretty nicely. This is kind of annoying to have to set up yourself every single time, and so what we've done is we've created a vue turbolinks node module that you can include in your application, and use that as a mix-in so you don't have to define any of these callbacks inside of your vue app or the destroy vue function, so we have provided that for you, and let me show you how to use that.

First thing's first, we need to go into our application and run yarn add vue-turbolinks This is the node module that we created for that, so that package is what you want to add to your package.json, this is going to then give you that package, it depends upon vue, and adds that to your app, so then I'm going to run the bin/webpack-dev-server, start that up again, and then we are going to be able to delete that, and import TurbolinksAdapter from 'vue-turbolinks' This is just a reference to the package, and you can import turbolinks adapter, and then you can get rid of these callbacks, and say mixins: [TurbolinksAdapter] and that should be all you have to do.

If we refresh our page we should see that that works, we get this to run at 50 00, we can go back, go to show, we can go back to that page, we get 50 16, we can go back to another one, we can hit back in the browser and that is all being updated correctly. That turbolinks mix-in cleans this up quite a lot, so that you have no reason to define those callbacks yourself. They can get taken care of and as we go and maybe improve this or add new compatibility things or whatever the case is, we can continue maintaining that node package for you, and then you can update that alongside of your vue application and your vue js stuff, doesn't really have to care too much about being compatible with turbolinks. You will still need to run these couple lines so that your vue code only runs when turbolinks load runs, and you want to make sure that if this is not a vue app that runs on every single page, then you want to check and make sure that that matching element does exist on the page every time before you try to load the vue app.

That's it for this episode, I wanted to walk you through the process of making vue turbolinks compatible, it's very similar to what we did with any other Javascript library, so we basically just have to say: Well, our Javascript that we want to add to the page, like vue even or react or whatever, and this is probably something that we want to initialize after turbolinks loads and then we want to tear it down before turbolinks caches the page so that it can be properly re-rendered next time we visit that page and this is the process of going through that, so I basically took advantage of some of the vuejs lifecycle events, but really the truth was that we're using the turbolinks load in order to initialize the vue life cycle stuff and then set up the turbolinks before cache so that we can destroy before it gets caches and turbolinks navigates to the next page. All of this works really well and so far we haven't ran into any problems, but if you want to use this and you do run into anything let me know. We'll have a link to the GitHub repo for the view turbolinks package and you can submit issues there. Until then, I will talk to you in the next episode. Peace ✌️

Transcript written by Miguel

Discussion