5👍
I’m afraid this isn’t going to be the simple answer you might have been hoping for… there’s a lot to unpick here.
Let’s start with factory.js
. That is not a factory. It’s a singleton. Singletons have problems around dependencies, configuration and the timing of instantiation and that’s precisely the problem you’re hitting. More on that later.
Now let’s take a look at the plugin. First up, these two lines:
if (install.installed) return;
install.installed = true;
That shouldn’t be necessary. Vue already does this automatically and should ensure your plugin is only installed once. Perhaps this came from an old tutorial? Take a look at the source code for Vue.use
, there’s not a lot to it:
Digging into the Vue source code is a really good habit to get into. Sometimes it will melt your mind but there are some bits, like this, that aren’t particularly difficult to follow. Once you get used to it even the more opaque sections start to become a little clearer.
Back to the the plugin.
Vue.prototype.$imgixBaseUrl = options.baseUrl;
It is not clear why you are adding this to the prototype.
I’m going to assume you are already familiar with how JavaScript function prototypes work.
Component instances are actually instances of Vue
. So any properties added to Vue.prototype
will be inherited by your components with almost no overhead. Consider the following simple component:
<template>
<div @click="onClick">
{{ $imgixBaseUrl }}
</div>
</template>
<script>
export default {
methods: {
onClick () {
const url = this.$imgixBaseUrl
// ...
}
}
}
</script>
As $imgixBaseUrl
is an inherited property it is available within onClick
via this.$imgixBaseUrl
. Further, templates resolve identifiers as properties of the current Vue instance, so {{ $imgixBaseUrl }}
will also access this.$imgixBaseUrl
.
However, if you don’t need to access $imgixBaseUrl
within a component then there is no need to put it on the Vue prototype. You might as well just dump it directly on Vue
:
Vue.imgixBaseUrl = options.baseUrl;
In the code above I’ve ditched the $
as there’s no longer a risk of colliding with component instance properties, which is what motivates the $
when using the prototype.
So, back to the core problem.
As I’ve already mentioned, singletons have major problems around creation timing and configuration. Vue has its own built-in solution for these ‘do it once at the start’ scenarios. That’s what plugins are. However, the key feature is that plugins don’t do anything until you call install
, allowing you to control the timing.
The problem with your original code is that the contents of factory.js
will run as soon as the file is imported. That will be before your plugin is installed, so Vue.prototype.$imgixBaseUrl
won’t have been set yet. The ImgixClient
instance will be created immediately. It won’t wait until something tries to use it. When Vue.prototype.$imgixBaseUrl
is subsequently set to the correct value that won’t have any effect, it’s too late.
One way (though not necessarily the best way) to fix this would be to lazily instantiate ImgixClient
. That might look something like this:
import Vue from "vue";
import ImgixClient from "imgix-core-js";
var imgixClient = null;
export function getClient () {
if (!imgixClient) {
imgixClient = new ImgixClient({
domain: Vue.prototype.$imgixBaseUrl
});
}
return imgixClient;
}
So long as nothing calls getClient()
before the plugin is installed this should work. However, that’s a big condition. It’d be easy to make the mistake of calling it too soon. Besides the temporal coupling that this creates there’s also the much more direct coupling created by sharing the configuration via Vue
. While the idea of having the ImgixClient
instantiation code in its own little file makes perfect sense it only really stands up to scrutiny if it is independent of Vue.
Instead I’d probably just move the instantiation to within the plugin, something like this:
import ImgixClient from "imgix-core-js";
export default {
install (Vue, options) {
Vue.imgixClient = Vue.prototype.$imgixClient = new ImgixClient({
domain: options.baseUrl
});
Vue.component("CustomComponent", component);
}
}
I’ve made a few superficial changes, using a default
export and wrapping the function in an object, but you can ignore those if you prefer the way you had it in the original code.
If the client is needed within a component it can be accessed via the property $imgixClient
, inherited from the prototype. For any other code that needs access to the client it can either be passed from the component or (more likely) grabbed directly from Vue.imgixClient
. If either of these use cases doesn’t apply then you can remove the relevant section of the plugin.