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.
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'
}
}
}
}
})
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"
},
}
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
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.
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 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.
My first step was to create a types.ts
file in my plugin where I did 3 things:
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;
}
}
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.
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!
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!
© All rights reserved. Made with ❤️ by BitterBrains, Inc.