Written by Paolo Mioni✏️
And can you run two different versions of Vue.js on the same page at the same time?
I think most people who use Vue.js in their projects nowadays are using it to create single-page applications (SPAs), with on-page content generated by Vue, or to build entire websites using Vue Router to create dynamic URLs for subpages, and maybe Nuxt.js for server-side rendering.
Their Vue projects come with a prepackaged system that includes Vue.js and its plugins, builds them, transpiles them as needed, and produces a neat package ready to be deployed in production. People forget that you can also include Vue.js directly on webpages to take care of small reactive components scattered around a normal HTML page.
Why use Vue as a general-purpose library?
Vue.js is so small and practical — and it’s so easy to make complex things quickly and easily with it — that you can use it as a general-purpose library, the way one would have used jQuery in the past (in case you’re wondering, no, you shouldn’t use jQuery anymore in 2020).
Vue can be added to a page fairly easily, weighs little (only 33kB at the time of writing), and can be used to take control of your existing HTML to add reactive behaviors, even if your HTML wasn’t designed to do that to begin with. And it can easily be retrofitted to existing websites.
In my company, we sometimes use it only for specific components in a page. For example:
- Wherever you need advanced user interaction
- Whenever you need to keep track of a complex set of data input by the user (application state)
- When you need to update data frequently from a web service
- When you need to perform complex animations and you want to maintain state easily
Generally, our rule of thumb is to use vanilla JavaScript when we want to create simple things, like:
- Image carousels
- Video players
- Dynamic menus
- Scrollbars
- Standard forms
But once we want to create a sophisticated component with a lot of animation and data/state, we tend to move to Vue.js. Some examples of the latter might be:
- Interactive calendars
- Finance tickers
- Complex forms
- Interactive animations
- Anything that requires keeping track of a complex set of user preferences or data
Advantages and disadvantages of using Vue
With Vue, you can also find many plugins that already support many complex UX behaviors and patterns out of the box. The advantages over vanilla JavaScript are the same as using Vue in general. For one, you can have an isolated component, with all of its programming and presentation logic in the same .vue file, much like you would using a Web Component.
It is much handier than having HTML, JavaScript, and SASS/CSS in three separate files in different directories. This not only eases your development, but also improves reusability. As stated in the Vue manual, “Separation of concerns is not equal to separation of file types.”
You also have a component scaffolding in place already, with prebuilt hooks for the major states during the component lifecycle, like creation, mounting, update, and destruction.
Vue gives you a separate namespace to put all of your properties and methods, which is isolated from the global context, and you get all of rendering speed advantages of a reactive library.
Of course, there are some disadvantages when introducing Vue.js in a project made with vanilla JavaScript, one of which is getting acquainted with Vue in general: the concept of reactivity and Vue’s internal structure are not that easy to grasp at first. You also have to add a new build procedure to your frontend bundle routine on webpack (or whatever bundler you choose to use).
You might have some extra work to do in getting the component’s content indexed on search engines as well. This shouldn’t be a big problem if you’re building an interactive component as opposed to the main text contents of your page, which you need to be indexed. You can read this article if you need more information on this matter
How to integrate Vue.js into your existing project
You can integrate Vue.js within an existing HTML page very easily. There are basically two ways:
1. Create and add individual components, one by one
You can include Vue.js in a script
tag from Cloudflare, Unpkg, or any other code CDN, define your own component in a JavaScript file, and add it on the spot in a specific part of your page. You just need to use the appropriate tag and Vue will insert your component in its place. Here is a sample template for the component:
// Create a component called <editable-text>
Vue.component('editable-text', {
data: function () {
return {
message: "Change me"
}
},
template: `<div><p>Message is: {{ message }}</p>
<input v-model="message" placeholder="edit me" /></div>`
})
// activate Vue on the <div> that contains the component
new Vue({ el: '#components-demo' })
And here is how you insert it in the HTML:
<div id="components-demo" class="components-demo">
<h3>Here is some content generated with Vue</h3>
<editable-text></editable-text>
</div>
You can do this for several separate components, each with their own separate Vue instance. You can see an example of two different components created with this technique in the following Pen (keep in mind that Vue.js is already included in the Pen using CodePen’s library inclusion settings):
2. Activate Vue on whole pages (or all pages in your site)
If you’re using several components repeatedly and you would like to include them anywhere on a page, or on any page in your website, you can just register all components globally and activate Vue on a <div>
containing all of the page contents (or a designated section of the page where you want to put your dynamic components).
Then, you can include the various components anywhere in the HTML of the page using their tags and pass each component instance its own custom properties. Here is a Pen that shows how you can do this:
This is very handy if you want to keep your original HTML structure but sprinkle some “Vue magic” on part of its contents. You just need to add the components in the appropriate places, and Vue will do the rest for you once you load it.
When not to use Vue.js as a general-purpose library
Uusing Vue.js much like one would have used jQuery works quite fine if you have full control of the site/application you’re building, and your code doesn’t have to run alongside third-party components/software in the foreseeable future.
However, if you’re building a widget that needs to be included in several sites, or you’re building an application that needs to run alongside code created by a separate development team, you should make an analysis before choosing Vue.js as your base framework.
The problem
One big issue with using Vue.js as a general-purpose library is that it can be problematic to make two different versions of Vue.js work on the same page.
The problem I’m describing is not when somebody uses two or more instances of Vue.js in the same page — you can easily do that without conflicts — but when somebody uses two completely different versions of it, say Vue 2.5 and Vue 2.6.
It does not happen to everybody, but this problem can arise in the following cases:
- On big sites with separate groups of developers, who often work on different tools/widgets/components that will need to run on the same page. Someone’s using Vue.js on one of these widgets, and someone else — who has a completely different development process, different build tools, maybe even different code repositories — decides they need to use Vue.js to do some part of the job, do not tell/ask anybody, and choose a different version.
- Two different developers working on the same project decide to both adopt Vue.js, don’t tell each other or their lead developer, and end up with code problems at some point.
- Someone is developing a widget that needs to be included as a code snippet in a third-party website, and they’re using Vue.js. The website is using Vue.js already, but a different version of it.
Mine was the first case. Somebody had included their own version of Vue.js in the main JavaScript code of a website, and my team and another team started having big problems (in a staging environment, mind you, not in production) with our own Vue.js applications, which had both been happily running in their own separate pages for the previous couple of years.
The symptoms
My team noticed the problem first after publishing our application in a shared staging environment. Some of our links were disappearing: we had buttons with CTAs suddenly going empty once they were activated. The common denominator is that these buttons contained internal links built with a <router-link>
.
We first attributed the issue to some unknown webpack compilation problem, maybe something that didn’t compile well without us noticing a warning in the compilation log. I personally rebuilt the application and reloaded it (the next day, if I remember correctly), and the problem magically went away.
We were sort of relieved since the bug we were facing had no reason to exist. The links that were disappearing were normal links to normal paths within the Vue Router, nothing fancy was happening there at all, and the code was working perfectly fine in our development environment. Here is the code for one of those links:
<router-link
v-if="hasCars"
v-link-tracking="'cta:inventory:view-result'"
:to="{ name: 'search-results', query: linkQuery }">
{{ this.$i18n('search-form-summary-go-to-results') }}
</router-link>
It wasn’t until a couple weeks later that the problem resurfaced, with exactly the same symptoms. There was no error being generated in the browser console, by the way. Everything was failing silently.
Being sure of the fact that our build had been done correctly this time, I did another couple tests and decided that the problem had to be some sort of conflict with some other code running in the staging environment. I included some of the other JavaScript files from the website we were working on into our development environment, and lo and behold, the same issue appeared there, too. The links all went AWOL.
I tried to find the culprit among the various JavaScript files I had included, and once I had singled out the one, I got hold of its source code and found a version of Vue.js included there. It was a different minor version of Vue.js: we were using 2.5.*, and this was 2.6.*.
Once I called out the problem on a ticket in our shared Jira platform, another company doing another app on the site — in their case using Vue.js version 2.3.* — found out that the duplicate inclusion was causing them big problems with VueX. In their case, the error was producing messages in the console:
These messages clearly showed that VueX had been called more than once.
Struggling to find a solution
I looked into all the possible solutions. Having worked with jQuery in the past, I remember that it has a way to include multiple versions of it in the same page, to avoid conflicts. There is a function called jQuery.noConflict
that allows you to do just that. You can find its docs here.
Since jQuery is a much lower-level library than Vue.js, and since in its heyday it was practically ubiquitous on the web, it is reasonable that the problem of multiple inclusions was noticed at some point (probably pretty soon!) and that such a function would have been created. I remember using it without major problems.
And no matter how much people like to bash jQuery now that we’re in the era of reactive frameworks, webpack, and ECMAScript versions, big kudos to its authors for building a huge library that was able to be included on the same site page twice (or more!) in different versions without creating conflicts.
Remembering that, I tried to find some literature on how to do the same thing with Vue.js, finding none. So I did some tests and started looking at Vue’s code.
Why you should be careful before including Vue 2 twice
I’ve used Vue.js extensively over the last few years, led a few big projects using it, and also wrote a few plugins to go with it. Generally speaking, though, you don’t always have much time to look at the internals of a framework you’re using. This time, I decided to start having a look and make a few educated guesses about the possible causes.
1. Automatic router inclusion within browsers
As you can read from the installation instructions, if you are not including it within a module system and you’re loading it directly from the browser, Vue Router will install itself in the current global Vue instance in the window (if present). You can find this code snippet in Vue Router in version 3.1.5 at the end of the compiled version, and at the end of the source code at line 260 the index.js
file:
if (inBrowser && window.Vue) {
window.Vue.use(VueRouter);
}
This line says that if the code is running within a browser (and not in a Node.js webpack compilation routine, for example), it will look for the current Vue instance in the window and install itself in it.
Imagine a case in which:
- Multiple scripts include Vue in the window’s execution context.
- At least one of them relies on loading the router from a separate source file as opposed to installing it within its own separate module system.
- The various scripts load asynchronously, using the
async
attribute of thescript
tag (see here for more information).
If we have several async
scripts, they may execute in any order, and you have no guarantee that the router you’re loading will attach itself to the right Vue instance since both scripts will try to create a Vue variable in the window. There is a race condition here, and depending on which code has loaded first, you might end up with a wrong version of the router on your Vue instance, or no router at all!
This was definitely not our case because our code was compiled with webpack and was not loading a Vue instance in the browser’s window.
2. Multiple router inclusion
Maybe the problem is much simpler. In order to try and reproduce the issues in this article, I created a Pen including two different versions of Vue and Vue Router, creating two separate Vue instances from the two versions of Vue, and attaching each router to the two different Vue instances.
The first creation of the router goes fine. When you create the second router, the router looks fine if you log it in the console. However, once you attach it to the Vue instance something fails silently an the router is not defined on the Vue instance.
You can check everything in the Pen here (open the console to see the logs). This error is probably the one we incurred when the links were not working.
If, however, the Vue versions are the same, Vue Router obviously installs just fine, as you can see in this Pen.
3. Trying to install VueX more than once
The error in VueX is easier to explain and probably was due to trying to install the same VueX twice.
In VueX’s installation function, which you can find in the store.js
module, there is a safeguard mechanism to avoid multiple installation of the Store. At line 6, you find the following code:
let Vue // bind on install
This sets a local variable Vue
within the module’s scope. This is used again when you install the store, in the install function at the end of the module:
export function install (_Vue) {
if (Vue && _Vue === Vue) {
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue
applyMixin(Vue)
}
The function accepts a Vue instance called _Vue
.
It makes a first security check: if the global Vue
variable we’ve seen before is not undefined, it checks if the Vue instance that has been passed is the same as the one it already has saved in the Vue
variable (see below). It either throws an error (if it is not in production mode) or simply returns.
Otherwise, it saves the _Vue
instance it has received in the local Vue
variable, ready to make further checks.
The error message we see in the above snippet is the same we saw in the console above, so this shows this was our case. However, if the problem had happened with code built for production, the check would have failed silently, providing no clue as to the cause of the error.
One thing that should be noted: this check at the beginning of the install function is just meant to verify that you do not install VueX twice onto the same Vue instance. But if you pass two different instances of Vue, they will not test true
for the following equivalence in the code above:
_Vue === Vue
And they will pass the test both if the instances are of different versions of Vue, and if the instances of the same version of Vue. You can see this for yourselves in this CodePen.
This seems to allow VueX to be installed onto different instances of Vue. In older releases, however, that section of code did not contain the instance check between the Vue version, just a check on the local Vue
variable. The check has been added in a commit you can find here.
So probably one of the codebases involved was using an old version of VueX, and another codebase was trying to initialize VueX from a source where it had already been installed, leading to the error.
Conclusion
While Vue.js is a wonderful framework, and I enjoy using it very much, the issues above show its origin as a reactive JavaScript framework, which is supposed to totally take over the page.
It was not designed to work as a general-purpose library, which is supposed to work alongside other versions of itself, included by other scripts. And you should (almost) always try to use software for its intended purpose instead of requiring it to do things it was not designed for!
The above problems can happen even if your own Vue application is properly built and packaged with webpack using a module system. Even though you would think that such a build should be totally isolated and therefore safe, someone else might include a different version of Vue onto the same site without telling you.
There is probably something in Vue’s code (or in some legacy versions of the code) that seems to prevent different versions of Vue from working at the same time. In our case, the problem could only be solved by removing the double inclusion of Vue. The dev team that arrived last had to remove their global inclusion of Vue and work with something else.
This, in my opinion, is enough to warn you that you should think twice before building any widget that needs to be included as a stand-alone component in third-party websites, such as a privacy management system or a finance ticker, using Vue.js. If you’ve managed to do so successfully, please let me know in the comments, I would be very glad to be proved wrong!
Experience your Vue apps exactly how a user does
Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket.
LogRocket is like a DVR for web apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.
Modernize how you debug your Vue apps - Start monitoring for free.
The post Can you use Vue.js as a general-purpose JavaScript library? appeared first on LogRocket Blog.
Top comments (0)