How to create a plugin in Vue.js

Published Dec 16, 2017Last updated Feb 17, 2018
How to create a plugin in Vue.js

In this tutorial, I will be showing you how you can create a plugin in Vue.js. It is vital that you have some prior experience with Javascript and/or frontend development before diving into this tutorial.

If you don't have any idea what Vue.js is, you can find out more here. TLDR; It is a progressive Javascript framework that can be used to build highly interactive web applications. Vue.js is lightweight, performant, and extremely well maintained and documented, making it a good choice for building sophisticated modern web applications in 2017 and beyond.

Plugins

Vue.js allows us to create plugins to add reusable functionality to an application. A plugin will usually contain one or more of the following:

  1. Global assets - like directives, filters, transitions etc.
  2. Global properties or methods
  3. Instance methods
  4. Component options (e.g a component mixin overriding the mounted() method)

When a plugin is applied on a Vue.js app, we are able to access the plugin's assets from within all the components in the application. Plugins also give us, as developers, a great way to package and share our code and cool functionality with other developers in the ecosystem.

Plugin Syntax

A plugin in Vue.js is simply a Javascript object that exposes an install method. Vue.js looks for this method when we call Vue.use(Plugin).

FieldFormatter.install = (Vue, options) => {
  /* Plugin functionality */
}

The install() method takes the Vue object as first parameter and an options object as the second parameter. We are able to provide these options when we call Vue.use():

Vue.use(Plugin, {
  somePluginOption: true,
  someOtherPluginOption: false,
})

Using options, we are able to customize plugin usage to fit specific user needs.

In the example below, we are going to create a directive that basically limits the number of characters within a text node (i.e <p>, <div>, <h3> etc.) to a set value. This directive will simply replace the text in the node with a shortened version and append a link at the end that allows us see the rest of the content when we click it. We can see similar functionality in social media applications that allow us progressively reveal content of a long comment for instance.

As you can imagine, we would probably want to have access to this directive across all our components and possibly package this in a nice way with other rendering related code. This is a good example of when using plugins in your project would be helpful.

We would do so as shown below:

// - src/plugins/fieldFormatter.js
const FieldFormatter = {
  install(Vue, options) {
    Vue.directive('limit-characters', {
      bind(el, binding, vnode, oldVnode) {
        const limit = binding.value || 100;
        const original = el.innerHTML;
        const summary = original.slice(0, limit);
        const expandLink = document.createElement('a');
        const collapseLink = document.createElement('a');
                
        expandLink.innerHTML = 'Read more...';
        collapseLink.innerHTML = 'Read less...';
                
        expandLink.style.textDecoration = collapseLink.style.textDecoration = 'underline';
        expandLink.style.cursor = collapseLink.style.cursor = 'pointer';
                
        const expandNode = () => {
          el.innerHTML = `${original} `;
          el.appendChild(collapseLink);
        }
        
        const collapseNode = () => {
          el.innerHTML = `${summary}... `;
          el.appendChild(expandLink);
        }

        expandLink.addEventListener('click', expandNode);
        collapseLink.addEventListener('click', collapseNode)

        collapseNode();
      }
    })
  },
};

export default FieldFormatter;

Here we can see a simple plugin with one directive, limit-characters that will limit the number of characters in a string. We will set the value of this limit on the text-node using v-limit-characters="characterLimit". characterLimit is a variable in our data object but we can also choose to use a literal number in place of it.

Note that this example is not "production" ready. It will break if limit is not set to a number.

To install our newly created plugin, in your entry file (this is usually src/main.js when you initialize your project using vue-cli), install the plugin using Vue.use()

// - src/main.js
import Vue from 'vue';
import FieldFormatter from './plugins/fieldFormatter';

...
Vue.use(FieldFormatter);
...

Now we can make use of this directive within our components like this:

// - src/components/Report.vue
<template>
  <div class="report">
    <div class="report-summary" v-for="report in reports" key="report.id">
      <h1>{{ report.title }}</h1>
      <p v-limit-characters="defaultCharacterLimit">{{ report.description }}</p>
    </div>
    </div>
</template>

<script>
export default {
  data() {
    return {
      reports: [/* Array of Reports */],
      defaultCharacterLimit: 100,
    };
  },
};
</script>

Extending our example

Let's expand on our earlier example and create simple instance method that can filter an array of objects based on an object query parameter. This method will simply loop through and compare to see which objects match the pattern of the query object.

// - src/plugins/fieldFormatter.js
const FieldFormatter = {
  install(Vue, options) {
    ...
    Vue.prototype.$filter = (dataset, query) => {
      return dataset.filter(item => {
        const queryKeys = Object.keys(query);
        for(let i = 0; i < queryKeys.length; i++) {
          const key = queryKeys[i];
          if(query[key]!== '' && !(key in item)) {
            return false;
          }
          if (item[key] !== query[key]) {
            return false;
          }
        }
        return true;
      });
    };
    ...
  }
}

export default FieldFormatter;

We may then use this instance method within our component like so:

// - src/components/FilteredTableDisplay.vue
<template>
  <div class="filtered-table-display">
    <div class="filters">
      <input type="text" v-mode="filter.param1"/>
      <input type="text" v-mode="filter.param2"/>
      <input type="text" v-mode="filter.param3"/>
    </div>
    <table border="1" cellspacing="0" cellpadding="0">
      <thead>
        <tr>
          <th>param1</th>
          <th>param2</th>
          <th>param3</th>
        </tr>
      </thead>
      <tr v-for="row in filteredData">
        <td>{{ row.param1 }}</td>
        <td>{{ row.param2 }}</td>
        <td>{{ row.param3 }}</td>
      </tr>
    </table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      filter: {
        param1: 'a',
        param2: 'b',
        param3: 'c'
      },
      rawData: [
        {
          param1: 'c',
          param2: 'd',
          param3: 'e',
        },
        {
          param1: 'c',
          param2: 'a',
          param3: 'd',
        },
        {
          param1: 'e',
          param2: 'a',
          param3: 'c',
        }
      ],
    }
  },
  computed: {
    filteredData() {
      return this.$filter(this.rawData, this.filter);
    },
  },
}
</script>

Here, we are using the $filter() method we created within our plugin as if we were calling any regular component method. We are able to use our method within a computed property to filter the data based on the this.filter object.

Summary

Plugins provide a great way to share code across your components and more importantly share code with other developers. There are several Vue plugins you might have already used and you can find even more great ones here. I tend to use a lot of plugins and so I usually appreciate how different plugin authors architect their code to make for a seamless developer experience.

I hope this can also inspire you even more to create your own plugins and use this in your upcoming Vue.js projects.

See the Vue.js documentation for more information on plugins.

Discover and read more posts from Chidiebere Nnadi
get started