Home / Blog / How to Package and Distribute a Vue.js 3 Plugin on NPM
How to Package and Distribute a Vue.js 3 Plugin on NPM

How to Package and Distribute a Vue.js 3 Plugin on NPM

Daniel Kelly
Daniel Kelly
October 19th 2022

Perhaps you’ve create a Vue.js 3 plugin that adds some awesome functionality to a Vue app but how do you get that plugin into the hands of other developers? Let’s breakdown the steps in this article.

Bundle the Vue Plugin with Vite

How to Configure Vite

In a previous article we built this simple tooltip plugin for Vue.js 3. We can use Vite to generate JavaScript files ready for use with both common JS or as ES modules. (This assumes you’ve developed your plugin with Vite.) When it comes to build time you can configure the plugin as a library with the build.lib setting in vite.config.js.

// vite.config.js

import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    lib: {
      // the entry file that is loaded whenever someone imports
      // your plugin in their app
      entry: resolve(__dirname, 'lib/main.js'),

            // the exposed global variable
      // is required when formats includes 'umd' or 'iife'
      name: 'SweetVueTooltip',

      // the proper extensions will be added, ie:
         // name.js (es module)
         // name.umd.cjs) (common js module)
      // default fileName is the name option of package.json
      fileName: 'sweet-vue-tooltip'
    },
    rollupOptions: {

      // make sure to externalize deps that shouldn't be bundled
      // into your library
      external: ['vue'],
      output: {
        // Provide global variables to use in the UMD build
        // for externalized deps
        globals: {
          vue: 'Vue'
        }
      }
    }
  }
})

How to Configure package.json

Once Vite is configured you’ll need to configure a few things in package.json.

First, we should specify that all .js files are ES modules. Vite will pick up on this and generate the file extensions accordingly.

"type": "module"

Next, since Vite will generate built files in a dist directory, then we can specify that only these files should be included in the distributed pacakge.

"files": ["dist"],

For common JS and ES modules to target the proper files you should provide the main, module, and exports options. (These files in the values will be generated by Vite based on the build.lib.fileName option in vite.config.js.)

"main": "./dist/sweet-vue-tooltip.umd.cjs",
"module": "./dist/sweet-vue-tooltip.js",
"exports": {
  ".": {
    "import": "./dist/sweet-vue-tooltip.js",
    "require": "./dist/sweet-vue-tooltip.umd.cjs"
  },
},

Also if your plugin includes any styles like our tooltip plugin did, you’ll need to export those as well with the exports option.

// with this, plugin end users can import styles like:
// import "sweet-vue-tooltip/style.css"; 
// and that will import the styles.css file from the dist directory
"exports": {
  //...
  "./style.css": "./dist/style.css"
}

Here’s all the additions necessy for package.json mentioned above so you can copy and paste them if you’d like.

// package.json
{
  //...
  "type": "module",
  "files": [
    "dist"
  ],
  "main": "./dist/sweet-vue-tooltip.umd.cjs",
  "module": "./dist/sweet-vue-tooltip.js",
  "exports": {
    ".": {
      "import": "./dist/sweet-vue-tooltip.js",
      "require": "./dist/sweet-vue-tooltip.umd.cjs"
    },
    "./style.css": "./dist/style.css"
  },
}

Build the Files to Distribute

With the proper configurations made to both vite.config.js and package.json, you should now be able to run npm run build (or vite build) and get a dist directory generated.

In this dist directory you’ll find entry points for both common js and es modules, as well as a JavaScript file for your actual plugin, and plain JavaScript files for any single file components that might have been included by your plugin. Your compiled styles from any components will exist here as well.

/dist
  - index.f5907655 // the plugin file
  - style.css // the compiled styles
  - sweet-vue-tooltip.js // es modules entry point
  - sweet-vue-tooltip.umd.cjs // common js entry point
  - ToolTip.92754567.js // compiled .vue file

Publish to NPM 🎉

Technically at this point, you can publish to npm. If you already have an npm account and are logged in to npm via the command line, you can run

npm publish

I would recommend though, before publishing that you test out the plugin in a seperate Vue.js project using npm link and browse through all the configuration options for package.json to ensure you’ve provided other important metadata like a license, a place to report bugs, etc.

Generate Type Definitions for Optimal DX

Even though we’ve been able to distribute a working plugin, without the TypeScript declaration files, it will certainly be lacking. Why? Because with TypeScript we can provide plugin users with the best error detection and with extremely accurate and focused auto-complete options.

This applies to our plugin’s options, any global app properties or methods we add, and even the props and events for any Vue components included by the plugin.

Screenshot of auto-complete options for plugin options when .d.ts files are generated

Screenshot of auto-complete options for plugin options when .d.ts files are generated

Screenshot of component’s prop types when plugin user hovers over an instance of the component

Screenshot of component’s prop types when plugin user hovers over an instance of the component

So how do we generate and distribute this valuable information in our package? I will admit, it was a bit tricky for me at first but here is what worked for me.

Consolidate all Types to a Single Entry Point

My first step was to create a types.ts file in my plugin where I did 3 things:

  1. Defined and exported custom types
  2. Imported and re-exported everything from my main plugin file
  3. and told Vue about my global components and properties

That file ended up looking like this.

import type { HideAll, Props } from "tippy.js";
import type ToolTip from "./ToolTip.vue";

export type PluginOptions = Partial<Props>;
export * from "./index";

declare module "vue" {
  // tells Vue about a custom global property/method 
  interface ComponentCustomProperties {
    $hideAllTooltips: HideAll;
  }
  // tells Vue about a custom component registered globally in the plugin
  interface GlobalComponents {
    ToolTip: typeof ToolTip;
  }
}

Generate .d.ts files with vue-tsc

Next, I needed a way to generate the .d.ts files in the dist directory along with my generated plugin files. There was no way to do this with Vite alone, so I installed vue-tsc and added the following script to my package.json file.

"generate:types": "vue-tsc --declaration --emitDeclarationOnly --outdir ./dist",

You might wonder why I used vue-tsc and not the official tsc CLI directly. It was important that I use vue-tsc since tsc doesn’t know how to handle the .vue files.

Next I updated the build script to first run vite build and then run the newly defined generate:types command.

"build": "vite build && npm run generate:types",

After running the updated build command I got .d.ts files for my main plugin file, my plugins custom component, and a types.d.ts file that pulled the types from both the previous and exported them all from that single file (along with the other definitions provided therein).

Lastly, I had to point to types.d.ts in package.json using the types option.

"types": "./dist/types.d.ts"

This was all that I needed to get the great DX boost!

If you’d like to see the complete codebase with the actual plugin included, checkout the github repo.

Conclusion

I know developing Vue plugins can be exciting but when it comes down to distributing them, tweaking build settings, and figuring out just how to get types working properly, it can be a pain (and honeslty kind of boring 🙄). I hope this article can help make the process as painless as possible 😀.

If you’d like to dive deeper into building and distributing a Vue.js 3 plugin then definitely checkout our course Custom Vue.js 3 Plugins!

Start learning Vue.js for free

Daniel Kelly
Daniel Kelly
Daniel is the lead instructor at Vue School and enjoys helping other developers reach their full potential. He has 10+ years of developer experience using technologies including Vue.js, Nuxt.js, and Laravel.

Comments

Latest Vue School Articles

From Vue.js Options API to Composition API: Is it Worth it?

From Vue.js Options API to Composition API: Is it Worth it?

Explore the technicalities of transitioning from Options API to Composition API in Vue.js. Discover if migrating your app is worth the effort in our detailed guide
Mostafa Said
Mostafa Said
What’s New in Nuxt 4

What’s New in Nuxt 4

Have anxiety about a new major version of Nuxt coming out? Worried about a big migration project? Don’t worry about it, a peaceful and easy upgrade is literally one of the features of Nuxt version 4.
Daniel Kelly
Daniel Kelly

Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.

More than 200.000 users have already joined us. You are welcome too!

Follow us on Social

© All rights reserved. Made with ❤️ by BitterBrains, Inc.