Skip to content
Fatih Kadir Akın edited this page Aug 5, 2019 · 3 revisions

Creating Package and Plugin Structure

This section is about creating the plugin folder and getting ready to create your awesome plugin.

1. Clone vue-plugin-boilerplate as a starter for your plugin

Let's create a todolist plugin. Why not?

git clone --depth 1 https://github.com/f/vue-plugin-boilerplate.git my-todolist-plugin
cd my-todolist-plugin

This will clone the boilerplate code into your directory. But wait, we have a small work to start working on it.

3. Run press.

There's a command called press inside the directory, it simply prepares your plugin.

./press

It will ask some questions about your plugin.

4. The plugin name

Your plugin name? (with dahshes like vue-plugin-boilerplate):

You should enter a single name or multiple names with dashes. The vue- prefix is recommended but not necessary. You can edit it later anyway. Let's say it vue-todo

Your plugin name? (with dahshes like vue-plugin-boilerplate): vue-todo

5. The class name

Your plugin class name? (pascal case like VuePlugin):

Now you'll be prompted for the class name it will create. This must be in PascalCase. Let's say VueTodo then.

Your plugin class name? (pascal case like VuePlugin): VueTodo

6. The class name

Your plugin options name? (like "yourPlugin" to be used as new Vue({ yourPlugin: {...} }))

When your users configure your plugin, they will need an object key to setup your plugin. What should it be? Let's say it todo and your users will be able to use your plugin as following:

import Vue from 'vue';
Vue.use(VueTodo)

const app = new Vue({
  todo: { /* some settings */ }
})

Looks cool, right? Writing todo:

Your plugin options name? (like "yourPlugin" to be used as new Vue({ yourPlugin: {...} }))

7. The accessor name

Your plugin accesor name? (like "helloWorld" to be used as this.$helloWorld):

You want your users to access your plugin instance from their Vue components. We should define an accessor name to make them use your plugin easily like following:

<script>
export default {
  mounted() {
    this.$todo.add('buy milk')
  }
}
</script>

So we should say it todo. The $ prefix will be added automatically but you can remove it from the source if you don't prefer.

8. The repo name

You're building an Open Source plugin (I hope), so you need a repo.

Your plugin's GitHub address? (like "f/vue-plugin-boilerplate"):

Just write the username/repo-name and the package.json file will be updated.

9. Remove leftovers

The press command made some changes in the boilerplate files, now you should remove them to start building it.

Thanks God, the press command gives you the list of the files (with remove commands) to make you start development from scratch. Run the command press gave you, it should look like following:

Pressing created some leftovers. You can run following commands to remove them now:

  rm -rf ./.git
  rm -f ./.storybook/stories.js.bak
  rm -f ./.storybook/config.js.bak
  rm -f ./README.tpl.md.bak
  rm -f ./examples/generic/index.js.bak
  rm -f ./examples/generic/App.vue.bak
  rm -f ./nuxt/index.js.bak
  rm -f ./README-BOILERPLATE.md.bak
  rm -f ./package.json.bak
  rm -f ./src/utils.js.bak
  rm -f ./src/types/vue-plugin.d.ts.bak
  rm -f ./src/vue-plugin.js.bak
  rm -f ./src/vue-plugin-component.vue.bak
  rm -rf ./resources
  rm -f ./press
  git init
  yarn install

Development of the Plugin

Now we have a plugin folder structure, only the important files were shown below:

package.json
src
|____types
| |____index.d.ts             # Type definitions for TypeScript
|____utils.js                 # Some utility functions for your plugin
|____vue-todo-component.vue   # A sample Custom Component, you can remove it if you don't want to provide one.
|____vue-todo.js              # The main file of your plugin.
.storybook
|____stories.js               # Your plugin's Storybook stories.
examples
|____generic                  # An example folder for your plugin, you can duplicate
| |____App.vue
| |____index.js

Commands

Command Description
yarn build Builds your project for production usage
yarn example:generic Runs the examples/generic application on http://localhost:4000
yarn storybook Runs storybook stories on http://localhost:{a free port}

vue-todo.js

This is the main file of your plugin, this has some preset abilities like installing itself, registering components, directives, Vuex stores, instance variables or some mixins.

You can see all the file by opening it but we'll focus on the following part of the file:

import VueTodoComponent from './vue-todo-component.vue';

export default class VueTodo {
  constructor(options = {}) {
    const defaults = {
      // This is your plugin's options. It will be accessible with this.options
      accessorName: '$todo'
    };
    this.options = { ...defaults, ...options };
  }

  // Some instance methods that you can access from this.$myPlugin
  world() {
    return 'world';
  }

  static register = (Vue, options, store) => {
    console.log('Here is the options of the component', options);
    console.log('Here is the store of the app', store);
    // You can use `this.options` property to access options.

    // Delete this line if your plug-in doesn't provide any components
    Vue.component('VueTodo', VueTodoComponent);

    // Vue.directive('your-custom-directive', customDirective);

    // registerVuexStore(store, 'counterStore', {
    //   namespaced: true,
    //   state: { counter: 0 },
    //   getters: {
    //     counter: state => state.counter
    //   },
    //   actions: {
    //     increment: ({ commit }) => commit('increment')
    //   },
    //   mutations: {
    //     increment: state => state.counter++
    //   }
    // });
  };

  // Some lifecycle hooks to add on mixin
  static mixin = () => ({
    mounted() {
      console.log('Hey! I am running on every mount, please remove me!');
      console.log(this.$store);
    }
  });

The Skeleton

The code is separated into 5 main parts:

  1. Components
  2. Options
  3. Instance methods/getters
  4. Registrations
  5. Mixins

Since the plugin is based on a class, you can convert it to your own structure.

// 1. COMPONENTS
// Here is the place for your component imports

import MyButton from './myButton.vue';
import MyInput from './myInput.vue';

export default class VueMyUILibrary {
  constructor(options = {}) {

    // 2. OPTIONS
    const defaults = {
      // This accessorName is required, you can remove it but you'll need to remove this from
      // the logic. Most of the time, plugins need an accessor like that.
      accessorName: '$myUI',
      
      // Here is the place for your another options
    };

    // Setting options with defaults.
    this.options = { ...defaults, ...options };
  }


  // 3. INSTANCE METHODS
  // You may want to have some abilities by reaching from the component using `this.$myUI.{method}`.
  // This may be handy on your plugins.
  doSomething() {
    /* */;
  }

  // 4. REGISTRATIONS
  // This is a static method to register your:
  //   - Components
  //   - Directives
  //   - Vuex Stores
  //   - Mixins
  //   - Global variables/methods.
  static register = (Vue, options, store) => {
    // This method is being called with 3 arguments, the Vue instance, the options and the global Vuex store.
    // You can use `registerVuexStore` utility to register a custom store.

    // e.g. let's register the things we've imported.
    Vue.component('my-button', MyButton);
    Vue.component('my-input', MyInput);
  };

  // 5. THE MIXIN
  // These will be registered to the components. `Vue.mixin` could be used on `register` but I recommend
  // using this since it's `beforeCreate` has some required tasks about registering plugin. 
  static mixin = () => ({
    data: () => ({
      heyIAmAStateOnEveryComponent: true
    }),
    mounted() {
      console.log('Hey! I am running on every mount, please remove me!');
    },
    /* Other things to do */
  });

1. Components

You can have many custom components in your plugin. E.g.

  • Building a Vue UI plugin that provides a UI component set,
  • A functional plugin that provides a component that makes some complex things,

You need to import them in your vue-plugin.js (your-dashed-plugin-name.js)

It is a simple import statement:

import MyButton from './myButton.vue';
import MyInput from './myInput.vue';

2. Options

You may need to allow your users customize something on your plugin. E.g.

  • The theme options if you're building an UI plugin,
  • Simple on/off switches.

You need to add some extra keys to defaults constant since you can only provide defaults. this.options will be set by boilerplate.

Note: accessorName is required on the file, you shouldn't remove it. If you remove it please change references. But I don't recommend making it static, plugin users may need to change it.

4. Registrations

If you're providing a component, directive, Vuex store or something needs to be injected into users app, you need to setup your register static function.

static register = (Vue, options, store) => {
  Vue.component('my-button', MyButton)
  Vue.component('my-input', MyInput)

  Vue.directive('focus', ...)

  Vue.mixin({ ... })
};
Injecting Vuex stores with registerVuexStore

vue-plugin-boilerplate has a registerVuexStore helper that registers a custom store to users' store.

static register = (Vue, options, store) => {
  registerVuexStore(store, 'my-awesome-store', {
    namespaced: true,
    state: { counter: 0 },
    getters: {
      counter: state => state.counter
    },
    actions: {...}
    mutations: {...}
  })
};

You can also import these stores from file:

import vuePluginStore from './vue-plugin-store';

...
static register = (Vue, options, store) => {
  registerVuexStore(store, 'my-awesome-store', vuePluginStore)
};
...

5. The Mixin

The boilerplate has a beforeCreate method to setup Vue instances. You can extend the mixin by using mixin static method of the plugin.

static mixin = () => ({
  data: () => ({
    heyIAmAStateOnEveryComponent: true
  }),
  methods: {
    hello() {
      console.log('I am a method on every component!')
    }
  }
});