In this article, we will start by looking at how the app initialization code works in Vue 2 apps. Then we’ll see what drawbacks it has and how those are eliminated with the new initialization syntax used in the 3rd version of the Vue framework.
Let’s start with the current way of doing initialization in Vue 2. Usually, in the src/main.js
file, we’re bootstrapping the app by calling a new Vue as a constructor for creating an application instance.
import Vue from "vue";
import App from "./App.vue";
import router from './router'
import SomeComponent from '@/components/SomeComponent.vue'
import SomePlugin from '@/plugins/SomePlugin'
const vue2AppCopy = new Vue({
router,
render: h => h(App)
});
vue2AppCopy.component('SomeComponent',SomeComponent);
vue2AppCopy.use(SomePlugin);
vue2AppCopy.$mount('#app')
This application instance will be serving all the logic throughout the lifetime of our SPA. That all works well, and for about 3 years, we’ve been using this syntax to bootstrap our Vue apps.
However, in Vue 3 the initialization code syntax has changed. Let’s have a look at the new syntax first and then take a look at the benefits of using it.
In Vue 3, we have a dedicated createApp
function to do just that. The createApp function takes a root component (App.vue
) as an argument and returns a Vue app instance. So the simplest app initialization will look like this:
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
The Vue app instance returned by the createApp is also called an application context object. This object can be used to further add more functionality to the app during the bootstrapping process. Here’s a more advanced initialization code example:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import SomeComponent from '@/components/SomeComponent.vue'
import SomePlugin from '@/plugins/SomePlugin'
const myV3App = createApp(App)
myV3App.component('SomeComponent', SomeComponent)
myV3App
.use(SomePlugin)
.use(router)
// add more functionality to myV3App
// now we're ready to mount
myV3App.mount('#app')
Compared to V2, not much has changed when it comes to adding extra logic, such as plugins and components, right?
You can find a full overview of the supported methods in the Vue 3 documentation.
That's good, but there’s one small but important change - we use a dedicated function, not the new Vue
instance:
//v2
const vue2App = new Vue({}) // Working with the main Vue instace
//v3
const myV3App = createApp(App).mount('#app') // Create a copy of the Vue instance
So why is using that new dedicated createApp
function better than using the new Vue
constructor?
In the Vue 2 app initialization code, we used the Vue object imported from the library to create this and all other new app instances.
With this approach, it was impossible to isolate some of the functionality to only one of the Vue instances since the Vue apps were still using the same Vue object imported from the library.
To demonstrate this, let’s look at the following example - as you can see, both vue2AppOne
and vue2AppTwo
will have access to a directive called myDirective
:
Vue.directive('myDirective', {
/* ... */
})
Vue.component({
/* ... */
})
const vue2AppOne = new Vue(/**/).mount('#app1')
const vue2AppTwo = new Vue(/**/).mount('#app1')
It might not be so common to create multiple Vue apps in a website or application.
But with a project growing in size, being developed by separate teams, and the popularity of frontend microservices in mind you may at some point find yourself doing this as well.
And then it will be close to impossible to get another Vue instance with different capabilities using the v2 syntax.
The new syntax in Vue 3 allows us to make the configuration of each app as an individual custom object, since the apps are configured using a dedicated function (createApp
) to create the independent instances.
The new architecture gives us the possibility to have two or more isolated Vue instances that do not share any features by default, even if they are created in one file.
However, if you want to share some functionality between 2 instances - you can! In the following example, vue3AppOne
and vue3AppTwo
share the LocalePlugin
but not the SearchInputComponent
.
const config = {/* some global config */}
const vue3AppOne = Vue.createApp(config)
vue3AppOne.component('SearchInput', SearchInputComponent)
vue3AppOne.use(LocalePlugin)
const vue3AppTwo = Vue.createApp(config)
vue3AppTwo.use(LocalePlugin)
To demonstrate this behavior we’ve created a code repository with 2 simple Vue 3 instances that do not share components and directives thanks to the new createApp
syntax. Have a look at it to play with it locally.
In the companion repository, we initialize two Vue 3 apps in 2 different containers on one page template, see public/index.html.
<div id="header-app"></div>
<div id="main-app"></div>
One app will serve as a relatively simple header markup, while the other will be capable of using the router and working with the store.
With the Vue 3 syntax, we can easily separate them in the initialization code in the src/main.js file:
import { createApp } from 'vue'
import App from './App.vue'
import Header from './Header.vue'
import router from './router'
import store from './store'
createApp(App)
.use(store)
.use(router)
.mount('#main-app')
createApp(Header)
.mount('#header-app')
If you run the app using vue serve
you should be able to see both parts working on one page. In the console output you will see that the Main app has access to vue-router
and the store using VueX
while the Header app does not.
created () {
console.log('Hello from Main app')
console.log('Main app router', this.$route)
console.log('Main app store:', this.$store)
}
If you are using vue-test-utils
(version < 2.0.0) for writing test for your Vue 2 components you may have come across the cases when you need to use the createLocalVue
method to avoid polluting the global Vue instance.
We have the same potential issues in our test scenarios as we have in Vue 2 apps. We pollute the global Vue instance when we add components, plugins, etc., and they’re all shared with every available Vue instance.
To work around this issue, we have to use createLocalVue
, which (you guessed it) creates a new local Vue instance, that is isolated.
import { createLocalVue, mount } from '@vue/test-utils'
import MyPlugin from '@/plugins/MyPlugin'
const localVueForTest = createLocalVue()
localVueForTest.use(MyPlugin)
mount(Component, {
localVueForTest
})
This is no longer an issue in Vue 3, since all app extensions: plugins, mixins, and global components do not mutate the global Vue instance.
So when using vue-test-utils
(version >=2.0.0) with Vue 3, the app init code in the test file will look like this:
import { createStore } from 'vuex'
import { mount } from '@vue/test-utils'
import App from '@/App'
import MyPlugin from '@/plugins/MyPlugin'
const wrapper = mount(App, {
global: {
plugins: [MyPlugin]
}
})
In this article, we looked at the differences between the initialization code for Vue 3 and Vue 2 apps, as well as the benefits of the new approach.
Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.
More than 200.000 users have already joined us. You are welcome too!
© All rights reserved. Made with ❤️ by BitterBrains, Inc.