The Story of our Progressive Migration from Backbone to Vue.js

Sit comfortably as I tell you a tale of adventures and discoveries—a new chapter of scaling our startup's tech stack. A quest that, in the last months, has led the Snipcart team in newly found territories.

A quest that we are now ready to share, to help anyone who could possibly have to go down the same route at any given moment.

I'm talking about our current prochegressive migration from our old Backbone architecture to the younger and more modern Javascript framework: Vue.js. Given the rising popularity of the latest, we certainly aren't the firsts to do the move, and won't be the lasts.

Gitlab, for example, also wrote about their choice to go with Vue.

In fact, Vue's growth is quite impressive, having surpassed Javascript libraries like Backbone and Ember, and slowly creeping up to Facebook-backed React's level. It still shows no sign of slowing its ascension.

Backbone vs Vue.js

I won't explain in details Vue's history or how it works here, because we already made a blog post about it. However, it's important to recall that it is a progressive framework, designed from the ground up to be incrementally adoptable.

Knowing this, it should be a child's play to slowly implement Vue.js components into our pre-existing code, right? Well, let's dive deeper into our experience so far, starting from the beginning.

Why Snipcart needed to move on to a new framework?

We started Snipcart almost four years ago (time flies, does it?). At that time the JavaScript ecosystem was far from what it is today. If you go back four years, you can forget about tools like Webpack. The most advanced “module” system at that time was RequireJS, and it was a pain to configure.

As a developer-first product, we really need to keep our things up to date. However, we know we can’t always be on the edge either.

If so, we would always be rewriting everything.

But we knew for a while that Backbone and RequireJS were making it harder to maintain Snipcart.

About two years ago, we switched from RequireJS to Webpack. It gave us a big boost in productivity, allowing us to switch from AMD to CommonJS, to use Pug as a templating engine, etc.

We also wrote about our Webpack refactoring at the time, find this blog post here.

But we now face another problem, if we can call it a problem. We were looking at the new kids on the block such as React and Vue.js, and realized that the way these frameworks were dealing with the components was much more friendly.

This is something that can’t be done with Backbone. You can create components for sure but you’ll always end up with something like this:

var ComponentView = Backbone.View.extend({
    template: `<span>This is a subcomponent<span>`,
    render: function() {
        this.$el.html(_.template(this.template)());
        return this;
    }
});

var MainView = Backbone.View.extend({
    template: `
      <div>
        This is a test
        <div id="component-holder"></div>
      </div>
    `,
    render: function() {
       this.$el.html(_.template(this.template)());
       
       var component = new ComponentView();
       component.render().$el.appendTo(this.$('#component-holder'));
       return this;
    }
});

It creates an unnecessary separation between the template and the component. You understand that we had to take a look at those newly available options.

Why Vue.js?

The number of Javascript frameworks out there can be quite overwhelming when comes the time to choose one, no matter the size and type of project you're working on.

Ultimately, you have to make a choice, and ours was Vue.js.

Now, besides the fact that I am completely in love with everything related to Vue.js (fundamentals, dev flow, community, etc.) we still had to do an analysis as a team to make sure the refactoring was going to be profitable.

You can't rely only on your love for a framework, or its trendiness for a decision this important.

Technically, our only constraint was that it had to be an incremental refactor (meaning a component based library was preferable). After reading numerous reviews and case studies we ended up choosing Vue anyway.

Notable arguments were:

  1. Most of our backbone templates could be simply pasted in our Vue components without any modification.
  2. We had past experiences with it (both Vue and Vuex).
  3. The incremental usability of Vue is just not comparable to anything else we've seen.
  4. Documentation is simple, but exhaustive.

If, like us, you're working with Backbone, and wondering if Vue.js could make your life easier, here's a comparison between the two frameworks.

Where to begin?

The first thing we had to do was to update our Webpack version. We were still running on v1 but wanted to leverage awesome Vue single file components. To do so, we had to upgrade to Webpack v2. Thanks to their awesome documentation, the update was a breeze. We had few properties to change but that was about it. We've been able to add a loader for .vue files and make everything work together beautifully.

Components

After that, we needed to add our first Vue component to our application. Our dashboard consists of four main regions; the left menu, the right menu, the header and the content itself.

To start our migration, we decided to keep our menus and header the way they are at the moment. We also decided to let Backbone take care of the routing. Otherwise, we'd have to rewrite all of our routers including the logic to render Backbone views and everything. One of the things we really liked about Vue is its modularity. The routing in Vue is handled by another library that is completely optional.

The first component we wrote was the form to create a custom shipping rate:

vuejs-refactoring

Basically, we needed to be able to render a Vue component in our content view. What we did first was to create a Backbone view that will be responsible for rendering Vue components.

To do so, we decided to apply the adapter pattern. It's a popular refactoring pattern to combine two interfaces together without messing with the source code too much.

In our case, it's a wrapper that allows us to render Vue components inside a Backbone view. The code of this view, which I named VueComponentView (clearly the best class name I ever wrote :) ), looks like this:

const Vue = require('vue').default
const store = require('components/store').default

module.exports = class VueComponentView extends Core.View {
    constructor(opts) {
        super(opts)
    }

    get template() {
        return "<div class='vue-app'></div>"
    }

    init (opts) {
        this.component = opts.component
        this.defaultArgs = opts.defaultArgs
    }

    afterRender() {
        const el = this.$('.vue-app').get(0)

        new Vue({
            el,
            store,
            render: (h) => {
                if (this.defaultArgs) {
                    return h(this.component, { attrs: this.defaultArgs })
                } else {
                    return h(this.component)
                }
            }
        })
    }
}

In our router, we render the view like this:

const ShippingMethod = require('components/ShippingMethod.vue');

createCustomShippingMethod: functon() {
  let view = new VueComponentView({
    component: ShippingMethod
  });
  
  this.appView().showView(view)
}

This way, we've been able to start adding new components in Vue into our dashboard. Without having to rewrite it all ;)

Vue & Backbone together

It's easy when your component is isolated from the rest of your application, but it's not always how it works in real life, and we quickly fronted this problem.

We are currently working on a awesome new feature, it will allow our merchants to create recovery campaigns to help to retrieve abandoned carts by sending automatic emails.

The UI to create those campaigns consists of multiple Vue components. However, we need to create an email template for each campaign steps. We already have an email template editor that is really nice, and that is working beautifully, so we wanted to avoid rewriting it. We wanted to use a Backbone component that would be able to communicate with our Vue components.

To enable this communication we thought about creating an event bus, about a shared JS object, name it.

But we ended up using Vuex.

Why?

We knew that we'd need a state manager like this in the long run. Vuex was simple and it's just JavaScript, so we can use it in Backbone as well.

Perfect.

So how did we process? Let's look at some code.

button(:data-templateId='step["templateId"]', 
    @click.prevent='updateStepIndex(index, $event)') Edit email template

//functions needed to show our editor, in the methods object of our Vue Component
updateStepIndex: function(index, e){
    this.$store.commit('UPDATE_CURRENT_STEP_INDEX', index);
    this.showTemplateEditor(e);
},
showTemplateEditor: function(e){
    var templateId = ev.target.dataset.templateid;

    var opts = {
        type: 'recoverycampaign'
    }

    if(templateId){
        opts.templateId = templateId;
    }

    window.dashboard.appView.showModal(new TemplateEditorView(opts));
}

What we do first is we update our current step index in Vuex through a commit, so once we're done with our template editor we can update the template ID for the according step.

I don't really love the idea of saving such a local state in Vuex, although I will live with it since I know we will eventually refactor our template editor in a Vue component, enabling us to easily delete the field. It's also not often that we opt for this, so I felt the trade off was worth it.

Now, let's check the template editor code, here's our saving success callback:

function success(status, payload){ 
    if(payload.type == 'RecoveryCampaign'){ 
        @inject('store').commit('UPDATE_CURRENT_STEP_INDEX_TEMPLATE_ID', payload.templateId);
    }

    this.inject('flash').addMessage({
        type: 'success',
        message: 'Your email template has been saved succcessfully.'})     

    this.inject('appView').hideModal();
}

So here it is, we save our template. If the type was a recovery campaign, we update the template ID of the current step through a commit to our Vuex store, and we hide the modal. We won't dive in the Vuex commit logic but it's pretty straight forward. We get the right step with the index and we simply update its templated value. After that, the modal closes and, bam!, you're back to your Vue component.

You might think that it would've been easier to simply create a callback for our editor to execute in the success function, but we thought this logic was a little bit too specific and bonded to this use case only, so we decided to use the Vuex approach for the time being.

Where do we go from here?

As I said, this is the story of a progressive migration. For the moment we aren't looking at a complete one, especially for the dashboard. However, the next version of Snipcart will be a complete rewrite of the front-end elements, so we are going to migrate completely for this part.

But for now, we want to continue adding value for our customers and not only write some code for the sake of writing code. ;)

We plan on refactoring small bits of our dashboard when we have time, and all new features will be written using Vue and our VueComponentView approach.

For us, it really has been a positive experience so far. I personally expected it to be way harder, especially with the routing. I quickly realized that Vue is so simple that it is effortless to integrate into our stack. Sure, we've only started our migration and we'll certainly hit some issues or limits along the way, but for now, it's been mostly delightful!

One thing's for sure, it's that Vue.js is much easier to learn and master than Backbone. There's way less boilerplate and the component-based approach is just what we needed.

Conclusion

It is not the ultimate conclusion to this story, as we still find ourselves at the beginning of our adventure with Vue.js. Still, as with any good story, there are some things that we would like you to take away from this first chapter.

  • Be smart: don't rewrite your whole application unless you really need to. Refactoring is good, but don't go crazy about it uselessly.
  • We talked about Vue.js because it was our choice and the one that fits our needs. It is not necessarily the best for all use cases, so do your research.

Here's a good tool for comparisons with other frameworks.

  • That being said, Vue.js is lean and gives you the freedom to use the tools you like. It is great to work with, so if it fits into your migration process, we strongly recommend it.

We will keep you guys in touch with the rest of the story as it moves along. We hope this piece can help developers in the same situation as us. We would absolutely like to read about your thoughts, experiences, pains or anything related to this subject, so hit the comment section below!

To be continued..


If you found this post valuable, please take a moment to share it on Twitter! I would also suggest subscribing to our newsletter to keep in touch with any new blog posts or with anything concerning Snipcart. Comments? Questions? The comment section is right below. ;)

Suggested posts: