How to organize your dependencies in a Vue application
Anyone familiar with Vue knows that a Vue application has one entry point - a file
This article will discuss how to organize global dependencies to avoid this.

Many may think - why is it necessary, if there is, for example, Nuxt , which will do it for you? I used it in my projects too, but in simple projects this may be redundant. In addition, no one has canceled projects with legacy-code that fall on you like snow on your head. And to connect the framework there - practically to do it from scratch.
The inspirer of such an organization was Nuxt. It was used by me on a large project with Vue.
Nuxt has a great feature - plugins. Each plugin is a file that exports a function. The config is passed to the function, which will also be passed to the Vue constructor when creating the instance, as well as the entire store .
In addition, an extremely useful feature is available in each plugin
In addition to being
A striking example of Dependency Injection is the vue-router . It is used not so often - to get the parameters of the current route, to make a redirect, but this is a global dependence. If it can be useful in any component, then why not make it global? In addition, thanks to this, its state will also be stored globally and change for the entire application.
Another example is vue-wait . The developers of this plugin went further and added a property
Let's see how the project looks now: looks like this:
Now we want to connect axios to our project and create a configuration for it. I stuck to Nuxt terminology and created a
As you can see, the file
At this stage, we achieved that we removed the configuration of the plug-in from
By the way, the benefit of transferring the object to
If you like ES6-style, you can even do this:
We have already created the first plugin and now our project looks like this: Since in most libraries where this is really necessary, Dependency Injection has already been implemented , we will create our own simple plugin. For example, try to repeat what it does . This is quite a heavy library, so if you want to show a loader on a pair of buttons, it is better to abandon it. However, I could not resist its convenience and repeated in my project its basic functionality, including syntactic sugar in the store.
Create
I already have a vuex module, which I also called
-
-
-
this plugin we use it.
And connect our plugin
Now we implement the function
Now about magic. The Vue documentation says that it is enough to write
However, in fact, it turned out that it is not. As a result of googling, there was no answer why such a construction did not work. Therefore, I decided to contact the authors of the plugin who have already implemented it.
As in our example, I looked at the plugin code
Instead of the prototype, it is proposed to use the global mixin. The effect is basically the same, perhaps with the exception of some nuances. But considering that the store inject is being done here, it does not look exactly the right way and does not at all correspond to the one described in the documentation.
The idea of the solution with the prototype that is used in the function code
After these manipulations, we are able to access
Project structure ::
As a result of the manipulations, we received one import and one function call in the file
When you add a new plug-in, you just need to create a file that exports the function, import it into
In my practice, such a structure proved to be very convenient, moreover, it is easily transferred from the project to the project. Now there is no pain, if you need to do Dependency Injection or configure another plugin.
Share your experience with dependency management in comments. Successful projects!
main.js
. There, in addition to creating an instance of Vue, there is an import and a kind of Dependency Injection of all your global dependencies (directives, components, plug-ins). The larger the project, the more dependencies become, which, moreover, each have their own configuration. As a result, we get one huge file with all configurations. This article will discuss how to organize global dependencies to avoid this.

Why write it yourself?
Many may think - why is it necessary, if there is, for example, Nuxt , which will do it for you? I used it in my projects too, but in simple projects this may be redundant. In addition, no one has canceled projects with legacy-code that fall on you like snow on your head. And to connect the framework there - practically to do it from scratch.
Inspirer
The inspirer of such an organization was Nuxt. It was used by me on a large project with Vue.
Nuxt has a great feature - plugins. Each plugin is a file that exports a function. The config is passed to the function, which will also be passed to the Vue constructor when creating the instance, as well as the entire store .
In addition, an extremely useful feature is available in each plugin
inject
. It does Dependency Injection to the root instance of Vue and to the object store
. And this means that in each component, in each storage function, the specified dependency will be available through this
.Where can it come in handy?
In addition to being
main.js
significantly “thinner,” you will also be able to use dependency anywhere in the application without unnecessary imports. A striking example of Dependency Injection is the vue-router . It is used not so often - to get the parameters of the current route, to make a redirect, but this is a global dependence. If it can be useful in any component, then why not make it global? In addition, thanks to this, its state will also be stored globally and change for the entire application.
Another example is vue-wait . The developers of this plugin went further and added a property
$wait
not only in the Vue instance, but also in the vuex store. Given the specifics of the plugin, this is extremely useful. For example, the store has an action that is called in several components. And in each case, you need to show the loader on some element. Instead of before and after each call to action cause $wait.start('action')
and $wait.end('action')
you can simply call these methods once the action. And it is much more readable and less verbose than dispatch('wait/start', 'action' {root: true})
. In the case of the store is syntactic sugar.From words to code
Basic project structure
Let's see how the project looks now: looks like this:
src
- store
- App.vue
- main.js
main.js
import Vue from'vue';
import App from'./App.vue';
import store from'./store';
new Vue({
render: h => h(App),
store
}).$mount('#app');
We connect the first dependency
Now we want to connect axios to our project and create a configuration for it. I stuck to Nuxt terminology and created a
src
directory plugins
. Inside the directory - files index.js
and axios.js
.
As mentioned above, each plugin must export a function. At the same time inside the function we want to have access to the store and subsequently - the function .src
- plugins
-- index.js
-- axios.js
- store
- App.vue
- main.js
inject
axios.js
import axios from'axios';
exportdefaultfunction (app) {
// можем задать здесь любую конфигурацию плагина – заголовки, авторизацию, interceptors и т.п.
axios.defaults.baseURL = process.env.API_BASE_URL;
axios.defaults.headers.common['Accept'] = 'application/json';
axios.defaults.headers.post['Content-Type'] = 'application/json';
axios.interceptors.request.use(config => {
...
return config;
});
}
index.js
:import Vue from'vue';
import axios from'./axios';
exportdefaultfunction (app) {
let inject = () => {}; // объявляем функцию inject, позже мы добавим в нее код для Dependency Injection
axios(app, inject); // передаем в наш плагин будущий экземпляр Vue и созданную функцию
}
As you can see, the file
index.js
also exports the function. This is done in order to be able to pass an object there app
. Now let's change a bit main.js
and call this function. main.js
:import Vue from'vue';
import App from'./App.vue';
import store from'./store';
import initPlugins from'./plugins'; // импортируем новую функцию// объект, который передается конструктору Vue, объявляем отдельно, чтобы передать его функции initPluginsconst app = {
render: h => h(App),
store
};
initPlugins(app);
new Vue(app).$mount('#app'); // измененный функцией initPlugins объект передаем конструктору
Result
At this stage, we achieved that we removed the configuration of the plug-in from
main.js
a separate file. By the way, the benefit of transferring the object to
app
all our plugins is that within each plug-in we now have access to the store. You can use it freely, causing commit
, dispatch
as well as addressing store.state
and store.getters
. If you like ES6-style, you can even do this:
axios.js
import axios from'axios';
exportdefaultfunction ({store: {dispatch, commit, state, getters}}) {
...
}
The second stage - Dependency Injection
We have already created the first plugin and now our project looks like this: Since in most libraries where this is really necessary, Dependency Injection has already been implemented , we will create our own simple plugin. For example, try to repeat what it does . This is quite a heavy library, so if you want to show a loader on a pair of buttons, it is better to abandon it. However, I could not resist its convenience and repeated in my project its basic functionality, including syntactic sugar in the store.
src
- plugins
-- index.js
-- axios.js
- store
- App.vue
- main.js
Vue.use
vue-wait
Wait plugin
Create
plugins
another file in the directory - wait.js
. I already have a vuex module, which I also called
wait
. He does three simple steps: -
start
- sets the state property of the object named action
in the true
-
end
- removes from the state property of the object with the name action
-
is
- receives from the state property of the object with the name of action
this plugin we use it.
wait.js
exportdefaultfunction ({store: {dispatch, getters}}, inject) {
const wait = {
start: action => dispatch('wait/start', action),
end: action => dispatch('wait/end', action),
is: action => getters['wait/waiting'](action)
};
inject('wait', wait);
}
And connect our plugin
index.js
::import Vue from'vue';
import axios from'./axios';
import wait from'./wait';
exportdefaultfunction (app) {
let inject = () => {}; Injection
axios(app, inject);
wait(app, inject);
}
Inject function
Now we implement the function
inject
.// функция принимает 2 параметра:// name – имя, по которому плагин будет доступен в this. Обратите внимание, что во Vue принято использовать имя с префиксом доллар для Dependency Injection// plugin – непосредственно, что будет доступно по имени в this. Как правило, это объект, но может быть также любой другой тип данных или функцияlet inject = (name, plugin) => {
let key = `$${name}`; // добавляем доллар к имени свойства
app[key] = plugin; // кладем свойство в объект app
app.store[key] = plugin; // кладем свойство в объект store// магия Vue.prototype
Vue.use(() => {
if (Vue.prototype.hasOwnProperty(key)) {
return;
}
Object.defineProperty(Vue.prototype, key, {
get () {
returnthis.$root.$options[key];
}
});
});
};
Magic Vue.prototype
Now about magic. The Vue documentation says that it is enough to write
Vue.prototype.$appName = 'Моё приложение';
and $appName
become available in this
. However, in fact, it turned out that it is not. As a result of googling, there was no answer why such a construction did not work. Therefore, I decided to contact the authors of the plugin who have already implemented it.
Global mixin
As in our example, I looked at the plugin code
vue-wait
. They offer this implementation (the source code is cleaned for clarity):Vue.mixin({
beforeCreate() {
const { wait, store } = this.$options;
let instance = null;
instance.init(Vue, store); // inject to storethis.$wait = instance; // inject to app
}
});
Instead of the prototype, it is proposed to use the global mixin. The effect is basically the same, perhaps with the exception of some nuances. But considering that the store inject is being done here, it does not look exactly the right way and does not at all correspond to the one described in the documentation.
And if still prototype?
The idea of the solution with the prototype that is used in the function code
inject
was borrowed from Nuxt. It looks a lot more right way than the global mixin, so I settled on it. Vue.use(() => {
// проверяем, что такого свойства еще нет в прототипеif (Vue.prototype.hasOwnProperty(key)) {
return;
}
// определяем новое свойство прототипа, взяв его значение из ранее добавленной в объект app переменнойObject.defineProperty(Vue.prototype, key, {
get () {
returnthis.$root.$options[key]; // геттер нужен, чтобы использовать контекст this
}
});
});
Result
After these manipulations, we are able to access
this.$wait
from any component, as well as any method in the store.What happened
Project structure ::
src
- plugins
-- index.js
-- axios.js
-- wait.js
- store
- App.vue
- main.js
index.js
import Vue from'vue';
import axios from'./axios';
import wait from'./wait';
exportdefaultfunction (app) {
let inject = (name, plugin) => {
let key = `$${name}`;
app[key] = plugin;
app.store[key] = plugin;
Vue.use(() => {
if (Vue.prototype.hasOwnProperty(key)) {
return;
}
Object.defineProperty(Vue.prototype, key, {
get () {
returnthis.$root.$options[key];
}
});
});
};
axios(app, inject);
wait(app, inject);
}
wait.js
exportdefaultfunction ({store: {dispatch, getters}}, inject) {
const wait = {
start: action => dispatch('wait/start', action),
end: action => dispatch('wait/end', action),
is: action => getters['wait/waiting'](action)
};
inject('wait', wait);
}
axios.js
import axios from'axios';
exportdefaultfunction (app) {
axios.defaults.baseURL = process.env.API_BASE_URL;
axios.defaults.headers.common['Accept'] = 'application/json';
axios.defaults.headers.post['Content-Type'] = 'application/json';
}
main.js
:import Vue from'vue';
import App from'./App.vue';
import store from'./store';
import initPlugins from'./plugins';
const app = {
render: h => h(App),
store
};
initPlugins(app);
new Vue(app).$mount('#app');
Conclusion
As a result of the manipulations, we received one import and one function call in the file
main.js
. And now it’s immediately clear where to find the config for each plug-in and every global dependency. When you add a new plug-in, you just need to create a file that exports the function, import it into
index.js
and call this function. In my practice, such a structure proved to be very convenient, moreover, it is easily transferred from the project to the project. Now there is no pain, if you need to do Dependency Injection or configure another plugin.
Share your experience with dependency management in comments. Successful projects!