This is a lazy (but up to date) tutorial, made while standing on the shoulders of giants, serving as a spiritual successor to these wonderful resources:
- The original FreeCodeCamp tutorial that no longer works
- The updated FreeCodeCamp tutorial that still didn't work for me
- A slightly more advanced tutorial
- More into deprecated Rollup territory and Vue plugins
- An actually working tutorial by @peshanghiwa, who's only ever written two DEV articles—both accurate, but a bit too detailed
- Example component library
With that out of the way, let's get started on this amazing journey that I'm sure you'll enjoy. For context, this tutorial was run on:
- Node
v22.14.0
- Vue
v3.5.17
- Vite
v7.0.4
This is to avoid any discrepancies future versions might cause.
Vite (pronounced "wheat")
We start our day with this magical Vite command:
npm create vite@latest
Running this command will give you a few options. Make sure to choose Vue and TypeScript. Although TypeScript is optional for a Vue package, it's kinda uncool in 2025 not to have typings for your components. People might get irritated and fork your component library just to add types, and then someone will write another DEV tutorial.
Vite will work its magic and create a bunch of files that should look like this:
Install packages
Very simple, but even the pros forget sometimes.
npm install
Also, install Node typings before your code editor starts screaming:
npm install @types/node --save-dev
Delete some files
Because this is a lazy tutorial, we're going to get rid of some unnecessary files. If you ever need assets or stylesheets, you can always add them back.
The component
Now, in our src/components/
folder, we're going to add the component that, well, we want to share with the whole world. For this tutorial, I'll create a fancy-looking button that links to this DEV article.
<!-- src/components/CoolButton.vue --> <script setup lang="ts"> defineProps<{ text?: string; }>(); </script> <template> <button class="cool-button"> <a href="https://dev.to/khalby786/how-to-create-and-publish-your-own-vue-component-libraries-actually-works-i-swear-1o5k" > {{ text }} </a> </button> </template> <style scoped> /* looks absolutely terrible btw */ .cool-button { background-color: #42b983; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; font-size: 16px; } .cool-button a { color: white; text-decoration: none; } .cool-button:hover { background-color: #367c6b; } </style>
Exporting the component
Rename the src/main.ts
to src/index.ts
, and delete all the content in the file. Then expose your component like so:
// src/index.ts import CoolButton from './components/CoolButton.vue'; export { CoolButton };
Configuring Vite (pronounced "wheat")
To give you a quick summary, Vite lets you build in library mode, which makes it easier to share your amazing component with the whole world on NPM. All that configuration goes in the vite.config.ts
file!
// vite.config.ts import { resolve } from "path"; import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; // https://vite.dev/config/ export default defineConfig({ plugins: [vue()], build: { lib: { entry: resolve(__dirname, "src/index.ts"), name: "CoolButton", fileName: "vue-cool-button", }, rollupOptions: { external: ["vue"], output: { globals: { vue: "Vue", }, }, }, }, });
⚠️ If you encounter
cannot find path
orcannot find __dirname
errors, make sure to go back to the Install packages step and install@types/node
.
Three's a crowd (tsconfig)
You'll notice we have THREE tsconfig.*
files. Because I'm lazy, we're going to merge it all into a single file and call it a day.
Delete tsconfig.app.json
and tsconfig.node.json
, and paste the following code into tsconfig.json
.
tsconfig.json
{ "compilerOptions": { /* Types */ "declaration": true, "declarationDir": "dist/types", "emitDeclarationOnly": true, "outDir": "dist/types", /* General */ "target": "ES2020", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], "skipLibCheck": true, /* Bundler mode */ "moduleResolution": "node", "allowImportingTsExtensions": true, "isolatedModules": true, "moduleDetection": "force", "jsx": "preserve", "sourceMap": true, "resolveJsonModule": true, "esModuleInterop": true, "baseUrl": ".", "paths": { "@/*": ["src/*"] }, /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], "exclude": ["node_modules", "dist"] }
Update package.json
We'll need to make changes in our package.json
file so that it points to the right built files. Make sure to add in the following lines:
{ ... "files": [ "dist" ], "main": "dist/vue-cool-button.umd.cjs", "module": "dist/vue-cool-button.js", "types": "dist/types/index.d.ts", ... }
Make sure to also change the build
script to the following, so that TypeScript emits type declaration files too.
{ ... "scripts": { ... "build": "vite build && vue-tsc --emitDeclarationOnly", ... } ... }
Build
npm run build
This will generate your built files in dist/
. It'll include JS files for both UMD and ESM, and any CSS files used.
Testing
A lazy way to taste is to start a fresh Vue project in a different folder. Run npm link
in your library's folder, and run npm link vue-cool-button
in the test app folder so that the library will be usable in your test app.
Somewhere in your test app, import your component:
<script setup> import { CoolButton } from 'vue-cool-button'; import 'vue-cool-button/vue-cool-button.css'; </script> <template> <CoolButton text="hello world" /> </template>
Publishing to NPM
# sign into npm npm adduser # publish npm publish
That's it.
Conclusion
Some core Vue contributor is probably reading this tutorial, writhing in pain at the war crimes we’ve committed along the way. This might not be the most proper way to create and publish a Vue component library, but it’s fast and, most importantly, it works. It’s a lazy tutorial, after all.
I wrote this after making vue-utterances, documenting the problems I faced along the way. I hope this tutorial helps out anyone who's on a similar journey!
Top comments (0)