On integrating Vue in an existing product

At Uptrends, like most software companies, we’re always improving the product. We wanted to invest in the frontend of the application and make some major improvements - both performance-wise and in styling - but the existing frontend architecture wasn’t set up for that. So we decided to integrate a modern front-end framework in our product, that would eventually power our entire frontend.

Situation

There was no way we could rebuild the entire application frontend, with over a decade of code, in a modern front-end framework in one go. That would take way too much time and required freezing major feature development. A clean slate was not an option. So we had to integrate, and slowly expand, the new framework in our existing product: a .NET project using Razor, jQuery and KnockoutJS.

What framework to use?

There’s a lot going on in front-end development land. Over the last decade, front-end development changed radically every 3-4 years. We wanted to pick a way of working that would last more than 3 years. This wasn’t a fire-and-forget project, but it should be future-proof and maintainable. Here’s our list of requirements:

We compared Angular, React and Vue. We even had a pizza night / hackathon with all developers to get a bit of a feel for the frameworks! In the end, Vue fitted us best.

Component library project

We decided upon building a component library - a regular Vue CLI project. In this library we developed components that we wanted to use in our app. The library being an external project allowed us to increase component development time immediately thanks to webpack’s watch functionality - with no extra effort since it’s built in the Vue CLI projects.

When a component is ready for integration in the product, it is explicitly included in the build. The component library is then built as a library using vue-cli-service build --target lib. This provides us with a library JavaScript file of all exposed components, but not Vue. Vue is not included in the library build - and this is desirable because it allows us to load Vue in our product without being tied to just these components.

We’ve also set the build up to include the css in the js - although we don’t write it that way, we write SFC’s - so importing a single js file results in importing the complete library with all components fully styled. We’re also using the UMD build of the library, since that provides a global JS variable that is available everywhere in the product - and that makes for easy integration in legacy code.

Integration in product

In the product frontend development project, a msbuild trigger is added to the pre-build to kick off a node build script. This script does two things (leveraging npm-run-all): it triggers a build of the components library, and when that’s done it copies the component library output to the product output folder. We also included a build command to allow hot reloading with webpack watch.

The components library, and the Vue source installed in the product, are then bundled and included in our product master page(s), so the components are available via the afore mentioned global JS variable.

This allows us to instantiate a component to a placeholder in the product like so:

const componentVm = new componentsLib.MyComponent().$mount("#mycomponent-placeholder");

And to interact with the component (or keep our KnockoutJS viewmodel in sync with Vue) like so:

componentVm.$on("somethinghappened", (payload) => { /* do something */ });
componentVm.$watch('prop', (newVal, oldVal) => { /* do something */ });

Extra’s

A couple of special cases were added to improve the setup:

Internationalization / localization

Our product is available in 4 different languages, and our components should support that. We wanted to maintain all the texts inside the components. We added a package called vue-i18n to the components library for this, which is passed to every component instantiation and set to the currently active language like so:

const i18n = componentsLib.i18n;
const componentVm = new componentsLib.MyComponent({ i18n });
i18n.locale = document.documentElement.lang;

Vuex store

Sharing application state between components works easier with Vuex. To this end, the Vuex store is maintained in the components library and passed to every component instantiation:

const componentVm = new componentsLib.MyComponent({ store });

Modern bundle for modern browsers

For performance reasons, we wanted to provide modern browsers with a more modern - and lighter - script bundle with less polyfills. Unfortunately, the --modern flag doesn’t work for the library build, so in the end we decided to write our own Webpack config. It mainly imitates the Vue Cli Webpack build, but allows us to create two versions of the library: a legacy version and a modern version.

Future

This is not a finished project - there’s still a lot of ground to cover before the entire frontend is solely using Vue:

In conclusion

We’re really happy with what we’ve achieved so far. Integrating Vue into our existing application was as easy as we expected. In under a month the first Vue component was released to production - a timespan we’re really proud of. There’s still lots of work ahead, but we’ve made a great start.