If you've been regularly building apps with Vue, you've noticed that in every app, you repeat some common functionalities over and over. For instance, you use a Log Component in every Vue app you work on. In addition, you might expose a few global functions or properties that you always find useful and usable in your apps. If this is you, then most probably you can start packing your common code into a Vue plug-in.

A Vue plug-in is a self-contained code that adds a global-level functionality into your app. When you create it, you package it as an NPM package. Then, whenever you need this plug-in, you install it into your app to avoid repetition of components, directives, global functions, and the like.

Vuex (https://next.vuex.vuejs.org/) and Vue Router (https://next.router.vuejs.org/) are two examples of Vue plug-ins. You'll almost use these two plug-ins in every Vue app you develop.

In summary, here's a list of possible scenarios for which you might consider building a custom Vue plug-in:

  • Adding some global methods or properties
  • Adding directives
  • Adding global mixins (https://v3.vuejs.org/guide/mixins.html)
  • Adding global instance methods
  • Adding a library that provides an API of its own while injecting some combinations of the above

Generally, I monitor some common functionality that I keep copying over and over from one app to another. Based on that, I decide whether to create a plug-in or not. They make your life easier and your code more organized by promoting common functionality into a common package that all apps can use. When you change the plug-in source code and publish the package again, you only need to go over all the apps that are using the plug-in and upgrade it.

Vue 3 Plug-ins

Vue 3 introduced a long list of changes and additions to the Vue framework. The most obvious is the way you create and load a Vue app.

Previously, in Vue 2, you instantiated an object of the Vue global constructor that represented the entire app. You used this instance to install plug-ins, and define directives, components, and other Vue intrinsic objects. This approach has its own pitfalls and limitations. For instance, if you create multiple Vue apps on the same page, they all share the same Vue global constructor. Hence, if you define a directive on the Vue global constructor, all the instances on the page have access to this directive. You use the Global API (https://vuejs.org/v2/api/#Global-API) to create an app in Vue 2. It has all the functions required to interact with the Vue global constructor.

In Vue 3, things have drastically changed when it comes to defining apps. You no longer use the Vue global constructor to instantiate apps. Instead, Vue 3 introduced the Application API (https://v3.vuejs.org/api/application-api.html), which I will introduce shortly, to standardize creating and instantiating apps. Vue 3 introduced the createApp() function that returns an app instance. It represents the app context and will be used to define plug-ins, components, directives, and other objects. In a nutshell, this app instance replaces the Vue instance in Vue 2.

In Vue 3, the global and internal APIs have been restructured with tree-shaking support in mind.

This approach has tremendous advantages, especially when it comes to instantiating multiple Vue apps without cluttering the Vue global space. An app instance creates a boundary and isolates its components, directives, plug-ins, and other Vue intrinsic objects from other app instances.

Moreover, Vue 3 brings over the new Composition API (https://composition-api.vuejs.org/api.html#Options-Data). Vue 2 had the Options API (https://vuejs.org/v2/api/#Options-Data) sitting at its core. In Vue 2, you construct your component as an object with properties. For example, data, props, watch, and many other properties are part of the Options API that you can use to attach functionality onto a component.

In Vue 3, you can still make use of the Options API. It's still there and will continue to be there for a while. This makes migrating Vue 2 apps to Vue 3 much easier, as Vue 3 is backward compatible with the Options API. However, Vue 3 introduces the Composition API. This API is optional. You use the Composition API inside your Vue component by using the setup() function. This function takes as parameters two inputs the props and context.

setup(props, context) {    
    const attrs = context.attrs;    
    const slots = context.slots    
    const emit = context.emit' 
}

This code snippet represents the setup() function signature.

The props object represents the component's props. Whatever props you define on the component are available to the setup() function. Note that the props object is reactive. This means that the object is updated inside the setup() function when new props are passed into the component.

A context input parameter is an object that contains three main properties:

  • attrs
  • slots
  • emit

Both attrs (https://v3.vuejs.org/api/instance-properties.html#attrs) and slots (https://v3.vuejs.org/api/instance-properties.html#slots) are proxies to the corresponding values on the internal component instance. This ensures that they always expose the latest values even after updates so that we can destructure them without worrying about accessing a stale reference.

setup(props, { attrs, slots, emit }) {}

$attrs now contains all attributes passed to a component, including class and style.

Finally, the emit() is a reference to the $emit() (https://v3.vuejs.org/api/instance-methods.html#emit) function on the internal component instance. You can use it inside the setup() function to emit events to the parent component.

Just remember that you can always mix both the Options and Composition APIs inside a single component. This is again helpful when upgrading your existing apps to Vue 3.

Note that any plug-in you develop in Vue 3 must support both APIs. This, of course, makes your plug-in both compatible and flexible to cover all bases.

As in Vue 2, you still need to define an install() function to represent your plug-in in Vue 3. This function will be used later by the Vue framework to install your plug-in. The only difference in Vue 3 is that this function receives an app instance. Back in Vue 2, the install() function received the Vue global constructor.

To support the Options API inside a plug-in, you make use of the Application Config (https://v3.vuejs.org/api/application-config.html) that's part of the Application API (https://v3.vuejs.org/api/application-api.html). Listing 1 shows a sample Vue 3 plug-in that uses the Application Config.

Listing 1: i18n Vue 3 plugin

export default {  
    install: (app, options) => {    
        app.config.globalProperties.$translate = key => {      
            return key.split('.').reduce((o, i) => {        
                if (o) return o[i]      
            }, options)    
        }  
    }
}

The code in Listing 1 shows to define a global property using the app.config.globalProperties object. In the coming sections, I'll explore both the Application API and Application Config.

Listing 2 shows the code for the sample plug-in written in Vue 2. Notice the use of the Vue.prototype object to define instance methods that can be accessed inside a component instance.

Listing 2: i18n Vue 2 plugin

export default {  
    install: (Vue, options) => {    
        Vue.prototype.$translate = function(key) {      
            return key.split('.').reduce((o, i) => {        
                if (o) return o[i]      
            }, options)    
        }  
    }
}

In part one of this series (CODE Magazine, January/February 2021), you learned about the provide/inject API. Now, in order to support the Composition API, you must make use of the provide/inject API to provide the plug-in functionality inside the setup() function. Listing 3 shows how to add support for the provide/inject API.

Listing 3: i18n Vue 3 plugin with support for Composition API

export default {  
    install: (app, options) => {    
        function translate(key) {      
            return key.split(".").reduce((o, i) => {        
                if (o) return o[i];      
            }, options);    
        }

        app.config.globalProperties.$translate = translate;

        app.provide("i18n", {translate});  
    }
};

In addition to making the translate() function available on the component instance level, the plug-in provides the same function and makes it available inside the setup() function.

You can play around with the plug-in in Listing 3 by visiting this link: https://codesandbox.io/s/vue3-plugin-7rmc8

Now that you have a general overview on how to develop Vue 3 plug-ins, let's move forward and explore the Application API and Application Config in detail.

Application API

Vue 3 provides the new Application API. You create an app using the createApp() method. It returns an app instance that exposes the entire Application API.

import { createApp } from 'vue'
const app = createApp({})
app.mount('#app')

The code snippet creates and mounts a new Vue 3 app using the createApp() function. The app instance exposes the following methods and objects.

Any APIs that globally mutate Vue's behavior are now moved to the app instance in Vue 3.

Let's look at some of the functions. You can read more about the function by following its corresponding link.

The component() Function

Use the component() function to register or retrieve a global component: (https://v3.vuejs.org/api/application-api.html#component):

// register a component
app.component('my-component', {  /* ... */})
// retrieve a registered component
const MyComponent = app.component('my-component')

The config Object

The config() object is an app global configuration object (https://v3.vuejs.org/api/application-api.html#config):

app.config = {...}

The directive() Function

Use the directive() function to register or retrieve a global directive (https://v3.vuejs.org/api/application-api.html#directive):

To register a directive using an object:

app.directive('my-directive', {  
    // Directive has a set of lifecycle hooks:
    beforeMount() {},  
    /* ... */
})

To register a directive using a function:

app.directive('my-directive', () => {  /* ... */})

To retrieve a directive:

const myDirective = app.directive('my-directive')

The mixin() Function

This function is used to register an app-global mixin that's available in all component instances: (https://v3.vuejs.org/api/application-api.html#mixin)

app.mixin({  
    created() {      
        console.log('A global mixin!)  
    }
})

The mount() Function

The mount() function mounts a root component of the application instance on the provided DOM element: (https://v3.vuejs.org/api/application-api.html#mount).

The provide() Function

The provide() function sets a value that can be injected into all components within the application. Components should use inject to receive the provided values: (https://v3.vuejs.org/api/application-api.html#provide).

You can read part one of this series (in CODE Magazine, January/February 2021) where I divulge the details of the provide/inject API in Vue 3 ().

The unmount() Function

The unmount() function unmounts a root component of the application instance on the provided DOM element: (https://v3.vuejs.org/api/application-api.html#unmount).

The use() Function

The use() function installs a Vue plug-in. This function accepts, as a first input, an object representing the plug-in and having an install() function. It also accepts a function representing the plug-in itself. In addition, it accepts, as a second input, an options parameter. Vue automatically passes the options input parameter to either the install() function or the plug-in function, depending on what's being passed as a first input parameter (https://v3.vuejs.org/api/application-api.html#use).

This is just a brief summary of the available functions on the Application API. You can read more about the function by following its corresponding link.

Now let's focus on the app.config object.

Application Config

An app config is an object that you use to store Vue app global configurations. This object exposes several properties and functions. In this article, I'll focus on only a few of them.

const app = createApp(App);
app.config = { ... };
app.mount('#app');

The snippet creates a new Vue 3 app, sets the app.config object, and finally mounts the app into a DOM root element.

Let's explore the most important features of the Application Config as far as this article is concerned.

The globalProperties Object

The app.config.globalProperties object allows you to add a global property that can be accessed in any component instance inside the application. The component's property takes priority when there are conflicting keys.

You define a global property:

app.config.globalProperties.foo = 'bar'

You retrieve a global property inside a component instance:

this.foo

You can also define a global instance method:

app.config.globalProperties.$translate = (key) =>   /* ... */

To use this method inside a component instance:

this.$translate('key');

The errorHandler() Function

The errorHandler() function defines a handler for uncaught errors that occur during the execution of the app. Vue runtime calls the error handler providing information about the error thrown and some additional information.

app.config.errorHandler = (err, vm, info) => {  
    console.log(err);  
    console.log(vm);  
    console.log(info);
};

The err input parameter represents the actual error object, the info input parameter is a Vue specific error string, and the vm input parameter is the actual component instance.

The warnHandler() Function

The warnHandler() function defines a handler for runtime Vue warnings. This handler is ignored in production.

For the sake of building a plug-in in this article, I'll focus on the app.config.globalProperties object.

Vue Environment Variables Plug-in

Lights! Camera! Action!

In this section, I'll implement a new Vue 3 plug-in that loads environment variables and makes them available in your app.

Before diving into the plug-in details, let's explore how the Vue CLI (https://cli.vuejs.org/) allows you to use environment variables in your app that are defined inside a .env file.

Vue CLI Service

The Vue CLI comes with a binary named vue-cli-service. This binary is the brains behind the Vue CLI.

The command npx vue-cli-service serve starts a new dev server, based on the webpack-dev-server (https://github.com/webpack/webpack-dev-server) that comes with Hot-Module-Replacement (HMR) working out of the box.

The command npx vue-cli-service build produces a production-ready bundle in the dist/ directory, with minification for JS/CSS/HTML.

You can check the list of commands available from this binary at any time by running the following command:

npx vue-cli-service help

When you use the vue-cli-service to run the app, the environment variables are loaded from the .env file.

The Vue CLI defines a concept named mode. There are three typical modes in the Vue CLI:

  • development
  • test
  • production

You can use the mode when defining environment variable files in your app. The available files are listed in Table 1.

When you run the command npx vue-cli-service serve, the NODE_ENV environment variable is set to development. Hence any of the following files .env, .env.local, and .env.development are loaded if present.

When you run the command npx vue-cli-service build, the NODE_ENV environment variable is set to production.

The Vue CLI loads automatically the environment variables defined in the app.

The one requirement to define Vue environment variables, is to prefix their names with VUE_APP_. Let's say you define the following environment variable inside the .env file:

VUE_APP_APIKEY=...

Next, inside your app, you'll access this environment variable as follows:

process.env.VUE_APP_APIKEY

You can read about the Modes and Environment Variables in the Vue CLI by following this link: https://cli.vuejs.org/guide/mode-and-env.html#modes.

The important takeaway of this section is that the Vue CLI loads the environment variables and makes them available to your app to use.

Some CLI plug-ins inject additional commands to vue-cli-service. You can see all injected commands by running: npx vue-cli-service help.

Plug-in Introduction

The Vue 3 plug-in that you're going to implement in this section depends on the vue-cli-service loading the environment variables from all the different files and making those environment variables available to the app without the need for the VUE_APP_ prefix.

Let's say you have defined the following environment variable:

VUE_APP_APIKEY=...

Eventually, when using the Options API, you'll be able to access this environment variable as follows:

this.$env.APIKEY

When using the Composition API, you'll be able to access this environment variable as follows:

const { APIKEY } = inject('env');

Let's look at the plug-in implementation in detail.

Plug-in Implementation

To start with, let's make sure you have the latest bits of the Vue CLI installed locally on your computer. The latest version of the Vue CLI that I'll be using in this article is v4.5.9 (https://github.com/vuejs/vue-cli/releases/tag/v4.5.9).

Install or Update Vue CLI

If you already have an older version of the Vue CLI, you can upgrade it by running the command:

npm update -g @vue/cli
# OR
yarn global upgrade --latest @vue/cli

If you don't have the Vue CLI installed locally, install it by running the command:

npm install -g @vue/cli# OR
yarn global add @vue/cli

To verify the Vue CLI installation, on your terminal window, run the following command and check the version:

> vue --version
@vue/cli 4.5.9

Create a New Vue 3 App

Now that you've installed the Vue CLI on your computer, open a terminal window and run the following command to create a new Vue 3 app:

> vue create vue-env-variables

Vue CLI v4.5.9
? Please pick a preset:   
    Default ([Vue 2] babel, eslint) 
> Default (Vue 3 Preview)           
          ([Vue 3] babel, eslint)   
  Manually select features

Make sure you select the second option labeled as Default (Vue 3 Preview). This option guarantees that the Vue CLI creates a Vue 3 rather than a Vue 2 app.

Once the Vue CLI finishes scaffolding and creating the app for you, all you need to do is change the directory to the app root folder and run the app:

cd vue-env-variables
npm run serve

The command runs the app and makes it available on port 8080. You can open the app in a browser by visiting http://localhost:8080/.

What's left for now is to install the following two NPM packages to allow you to use SASS when writing your CSS:

npm i sass-loader
npm i node-loader

Note that the Vue CLI initializes a new GIT repository inside the app root folder and creates an initial commit to track all the files inside GIT.

Add the .env File

Let's add a .env file into the root folder of the project with the following content:

VUE_APP_CATSAPIKEY=e8d29058-baa0-4fbd-b5c2-3fa67b13b0d8
VUE_APP_CATSSEARCHAPI=https://api.thecatapi.com/v1/images/search

I've placed two Vue environment variables that you're going to use later to access an online images API (the same one I used in part one of this series). Save the file.

Now that you have the app created and the .env file populated with two environment variables, let's add the plug-in.

Add the Plug-in

Inside the /src folder, add a new JavaScript file and name it install.js. Inside this file, paste the code shown in Listing 4.

Listing 4: Vue Env Variable plugin

import { inject } from 'vue';
import { getVueEnvVariables } from './env-helper';

export const envSymbol = Symbol();

export default {  
    // eslint-disable-next-line no-unused-vars  
    install(app, options) {    
        // access process.env object    
       const env = process.env;

    // get an object of all vue variables    
    const vueVariables = getVueEnvVariables(env);

    // make $env property available   
    // to all components using Options API    
    app.config.globalProperties.$env = vueVariables || {};

    // provide the env variables to support all components     
    // using Composition API or Options API    
    app.provide(envSymbol, vueVariables);  
    },
};

export function useEnv() {  
    const env = inject(envSymbol);  
    if (!env) throw new Error('No env provided!');

    return env;
}

Let's focus on the install() function in Listing 4.

The file also imports the ./env-helper.js helper module. It exports the getVueEnvVariables() function that the plug-in uses.

The module file defines a constant of type Symbol (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol). The Symbol() function returns a unique identifier. It can be used as a key to provide the environment variables to the app as you will see shortly.

In addition, it exports a default ES6 plug-in module (https://hacks.mozilla.org/2015/08/es6-in-depth-modules/) containing a single install() function.

Moreover, it exports the useEnv() function that you can use inside the Composition API as a hook to retrieve the environment variables' object.

The install() function expects two input parameters, the app instance and options, that your plug-in might use. Upon registering the plug-in later on, you can pass any additional settings/configurations via the options parameter.

First of all, the code loads all environment variables from the process.env object. The process object (https://nodejs.org/dist/latest-v8.x/docs/api/process.html#process_process) is a global that provides information about, and control over, the current Node.js process. The process.env is an object property that contains the user environment. This also includes the environment variables defined on the local system.

Remember that the Vue CLI is nothing but a Node.js command-line interface. Its main purpose is to make all the environment variables available on the local system via the process.env object. The code stores the user environment in a local variable named env. Then it passes this variable to the getVueEnvVariables() function that the ./env-helper.js module defines.

Let's explore this function in more depth. Listing 5 shows the entire source code for this function.

Listing 5: getVueEnvVariables() function

function getVueEnvVariables(envVariables) {  
    return Object.entries(envVariables).reduce(   
        (accum, envVariable) => {    
        const vueVariable = getVueEnvVariable(envVariable);

        // ignore none vue variables    
        if (!vueVariable) return accum;

        // accumulate the env variable objects    
        return { ...accum, ...vueVariable };  
        }, {});
}

Given the .env file that you've defined previously, the envVariables object when printed should look as follows:

BASE_URL: "/"

NODE_ENV: "development"

VUE_APP_CATSAPIKEY:"e8d29058-baa0-4fbd-b5c2-3fa67b13b0d8"

VUE_APP_CATSSEARCHAPI:"https://api.thecatapi.com/v1/images/search

Note that the Vue CLI adds two additional environment variables: BASE_URL and NODE_ENV.

The function tries to reduce the envVariable input parameter into an object containing only the environment variables whose names start with the VUE_APP_ prefix.

It starts by looping over the key/value pairs in the envVariable input parameter. For each key/value pair, it calls the getVueEnvVariable() function.

Listing 6 defines the getVueEnvVariable() function.

Listing 6: getVueEnvVariable() function

function getVueEnvVariable(envVariable) {  
    // access one env variable at a time  
    const [key, value] = envVariable;

    // return if the env variable name   
    // doesn't start with VUE_APP_  
    if (!isVueEnvVariable(key)) return null;

    // remove the prefix "VUE_APP_" from the env variable name  
    const cleanedKey = cleanVueVariable(key);
    
    // construct a new key:value object  
    return {    
        [cleanedKey]: value,  
    };
}

The function in Listing 6 receives a single environment variable in the form of a key/value pair object. It then checks if this is a VUE environment variable; that is, the key starts with VUE_APP_ prefix.

The isVueEnvVariable() function uses a regular expression to check if the environment variable key starts with the VUE_APP_ prefix.

function isVueEnvVariable(envVariableKey) {  
    return envVariableKey.match(/^VUE_APP_{1}/g);
}

The function returns null if it's not a Vue environment variable. Otherwise, it calls the cleanVueVariable() function that replaces the VUE_APP_ prefix with an empty string:

function cleanVueVariable(envVariableKey) {  
    return envVariableKey.replace(     
        /^VUE_APP_{1}/g, '');
}

Finally, it returns a new object with the newly constructed environment variable key and keeping the same value.

For instance, given the envVariable input parameter has the following value:

{
VUE_APP_CATSAPIKEY:"e8d29058-baa0-4fbd-b5c2-3fa67b13b0d8"
}

This function returns a new object as follows:

{
CATSAPIKEY:"e8d29058-baa0-4fbd-b5c2-3fa67b13b0d8"
}

Now back to the getVueEnvVariables() function where you left off. The getVueEnvVariable() function either returns null or an object representing the Vue environment variable. If the return value is null, do nothing and move to the next environment variable. Otherwise, merge this new Vue environment variable object with the rest of similar objects and return.

The end result of this function, given the existing .env file you added previously, is the following object:

{
CATSAPIKEY:"e8d29058-baa0-4fbd-b5c2-3fa67b13b0d8",

CATSSEARCHAPI:"https://api.thecatapi.com/v1/images/search,
}

Back to Listing 4. The variable vueVariables stores the object returned from calling the getVueEnvVariables() function with the env input parameter.

// make $env property available 
// to all components using Options API

app.config.globalProperties.$env = vueVariables || {};

The plug-in exposes the vueVariables' object on a global property named $env that is defined on the app.config.globalProperties object. To revise this, go back to the section on Application Config.

Now the $env object is exposed to the Options API for all components in the app. Still, you need to expose the same object inside the Composition API.

app.provide(envSymbol, vueVariables);

To do so, expose the object via the app.provide() function. As you know, this function expects key and value input parameters. It uses the envSymbol variable as the key and, of course, the value is the environment object variables' object.

Finally, let's explore the useEnv() function.

export function useEnv() {  
    const env = inject(envSymbol);  
    if (!env) throw new Error('No env provided!');

    return env;
}

Any component that wants to access the environment variables' object needs to use this function as a hook inside the setup() function of the Composition API.

It uses the built-in inject() function that Vue 3 defines to inject the environment variables, provided by the plug-in, into a local variable named env. It throws an exception if there are no environment variables provided. Otherwise, it returns the env variable containing all the environment variables.

That's it! The plug-in is ready.

Let's use the plug-in in the next section.

Using the Plug-in

Let's use the plug-in in a sample Vue app. For this purpose, I'll be using the same example code that I used in part one of this series. The goal of the app is to display two random cat images.

You can see the app live by following this link: https://stackblitz.com/edit/vue3-prop-drilling-composition-api-provide-inject.

Open the App.vue file and paste the source code shown in Listing 7.

Listing 7: App Component

<template>  
    <div class="app">    
        <h2>My Cats App!</h2>    
        <cats-collection />  
    </div>
</template>

<script>
    import CatsCollection from './components/CatsCollection';

export default {  
    name: 'App',  
    components: {    
        'cats-collection': CatsCollection,  },
};
</script>

<style>
#app {  
    font-family: Avenir, Helvetica, Arial, sans-serif;  
    -webkit-font-smoothing: antialiased;  
    -moz-osx-font-smoothing: grayscale;  
    text-align: center;  
    color: #2c3e50;  
    margin-top: 60px;}
</style>

The App component uses the CatsCollection component. Listing 8 defines this component.

Listing 8: CatsCollection Component

<template>  
    <section class="cats-collection">    
        <h4>My Best Collection!</h4>    
        <section class="cats-collection__favorites">      
            <favorite-cat :index="1" />      
            <favorite-cat :index="2" />    
        </section>  
    </section>
</template>

<script>
import FavoriteCat from './FavoriteCat';

export default {  
    name: 'CatsCollection',  
    components: {    
        'favorite-cat': FavoriteCat,  
    },
};
</script>

<style lang="scss" scoped>.cats-collection {  
    width: 90%;  
    margin: 0px auto;  
    border-radius: 5px 5px 0 0;  
    padding: 20px 0;  
    background: lightblue;

    &__favorites {    
        display: flex;    
        width: 100%;    
        justify-content: space-around;  
    }
}
</style>

The CatsCollection component instantiates two instances of the FavoriteCat component. For each instance, it sets the index property to be an integer representing the ID of the component. Listing 9 defines the FavoriteCat component. This component displays a random image using the Cat API (https://thecatapi.com/).

Listing 9: FavoriteCat component

<template>  
<section class="favorite-cat">    
    <p>Favorite Cat {{ index }}</p>    
    <img v-if="imageUrl" :src="imageUrl" alt="Cat" />  
</section>
</template>

<script>import { ref, onMounted, computed } from 'vue';
import axios from 'axios';
import { useEnv } from '../install';

export default {  
    name: 'CatsCollection',  
    props: {    
        index: {      
            type: Number,      
            default: 1,    
        },  
    },  
    setup(props) {    
        const env = useEnv();    
        let imageUrl = ref('');

        const loadNextImage = async () => {      
            try {        
                const { CATSAPIKEY, CATSSEARCHAPI } = env;        
                axios.defaults.headers.common['x-api-key'] = CATSAPIKEY;
                
                let response = await axios.get(CATSSEARCHAPI, {          
                    params: { limit: 1, size: 'full' },        
                });

            const { url } = response.data[0];        
            imageUrl.value = url;      
            }   catch (err) {        
                console.log(err);      
                }    
        };

        onMounted(() => {loadNextImage(); });

        return {      
            imageIndex: computed(() => props.index),      
            imageUrl,    
        };  
    },
};
</script>

<style lang="scss" scoped>.favorite-cat {  
    img {    
        max-width: 200px;    
        border-radius: 5px;  
    }
}
</style>

Let's look at the setup() function in more depth. Listing 10 shows the setup() function.

Listing 10: setup() function

setup(props) {    
    const env = useEnv();    
    let imageUrl = ref('');

    onMounted(() => {loadNextImage();});

    return {      
        imageIndex: computed(() => props.index),      
        imageUrl,    
    };  
},

To start with, the setup() function receives as input the props that you define on the component. In this case, the props object contains a single property named index.

Then it uses the useEnv() hook to get access to the environment variables provided by the plug-in and assigns the returned object to a local constant named env.

It also defines the reactive imageUrl variable to be a ref(''). You can read more about ref() here: (https://composition-api.vuejs.org/api.html#ref).

It calls the loadNextImage() function when the component mounts using the onMounted() lifecycle hook: (https://v3.vuejs.org/api/composition-api.html#lifecycle-hooks).

Finally, it returns both the imageIndex, as a computed read-only property, and the imageUrl.

Let's move on to the loadNextImage() function. Listing 11 shows the loadNextImage() function.

Listing 11: loadNextImage() function

const loadNextImage = async () => {      
    try {        
        const { CATSAPIKEY, CATSSEARCHAPI } = env;        
        axios.defaults.headers.common['x-api-key'] = CATSAPIKEY;

        let response = await axios.get(CATSSEARCHAPI, {          
            params: { limit: 1, size: 'full' },        
        });

        const { url } = response.data[0];        
        imageUrl.value = url;      
    }   catch (err) {        
        console.log(err);      
        }    
};

In the context of this article, you mostly care about this line of code:

const { CATSAPIKEY, CATSSEARCHAPI } = env;

You're destructing the env variable and extracting two environment variables defined inside the .env file. The Cats API Key and Cats Search API base URL.

Remember that the .env file already defines these environment variables using the VUE_APP_ prefix. However, your plug-in exposes the same environment variables without prefixes.

The rest of the code in the function communicates with the remote API to query for a new random image.

Back to the terminal window, run the following command to run the app:

npm run serve

This command runs the app in development mode and makes it available at port 8080. Open a browser of your choice and navigate to http://localhost:8080 and you shall see something similar to Figure 1.

Figure 1: App running
Figure 1: App running

That's it! The app communicates with the remote API supplying the API Key and the Search API URL stored in the .env file. The new Vue Env Variables plug-in successfully loads those environment variables and provides them to the app without the VUE_API_ prefix.

The source code for the plug-in is hosted on GitHub and is accessed by visiting the link https://github.com/bhaidar/vue-env-variables.

Bonus: Prepare and Publish a Plug-in to NPM

Instead of going around in circles, I'll point you in the direction of a great guide on the internet to help you prepare and publish your plug-in as an NPM package: Create and Publish Your First Vue.JS Plug-in on NPM (https://5balloons.info/create-publish-you-first-vue-plugin-on-npm-the-right-way/).

Learning more about how the Vue CLI builds your package via the vue-cli-service is recommended, and here's a good place to find out more: https://cli.vuejs.org/guide/build-targets.html#app.

I've already built and published this plug-in as an NPM package here: https://www.npmjs.com/package/vue-env-variables.

You can open any Vue 3 app and install the NPM package by running the following command:

npm i vue-env-variables

Inside the main.js file of the Vue 3 app, you start by importing the plug-in:

import VueEnvVariables from "vue-env-variables";

Then ask the app instance to make use of this plug-in:

// make use of the plugin
app.use(VueEnvVariables);

That's all! Enjoy it.

Conclusion

This is the end of this series on building Vue 3 plug-ins that support both the Options API and the Composition API. During this transitional period, it's important to keep building plug-ins that are backward compatible with the Options API.

Always look at the common functionalities in your apps and try to promote them to a plug-in. This is very useful and time effective. You may choose to benefit others in the community by sharing your plug-in with them.

Table 1: Available files

.env file namedescription
.env # loaded in all cases
.env.local # loaded in all cases, ignored by git
.env.[mode] # only loaded in the specified mode
.env.[mode].local # only loaded in the specified mode, ignored by git