Básicamente lo que voy a hacer es un apunte de como generar una librería de de Vue3 con typeScript.
Lo voy a hacer lo mas genérico posible ya que cualquier componente que quieran pasar a una librería lo deberían poder hacer.
Vamos a comenzar con la creación de un proyecto en Vue3-TypeScript
npm create vite
> npx
> cva
│
◇ Project name:
│ v-jp-button
│
◇ Select a framework:
│ Vue
│
◇ Select a variant:
│ Official Vue Starter ↗
> npx
> create-vue v-jp-button
┌ Vue.js - The Progressive JavaScript Framework
│
◇ Select features to include in your project: (↑/↓ to navigate, space to select, a to toggle all, enter to confirm)
│ TypeScript, ESLint (error prevention), Prettier (code formatting)
│
◇ Select experimental features to include in your project: (↑/↓ to navigate, space to select, a to toggle all, enter to
confirm)
│ Oxlint (experimental), rolldown-vite (experimental)
│
◇ Skip all example code and start with a blank Vue project?
│ No
Scaffolding project in /Users/user/Documents/codigo/proyectos/v-jp-button...
│
└ Done. Now run:
cd v-jp-button
npm install
npm run format
npm run dev
| Optional: Initialize Git in your project directory with:
git init && git add -A && git commit -m "initial commit"
Ahora vamos a crear un componente "button" poco sofisticado pero publicable.
Y quedaría algo así:
<template>
<button class="button" :class="color" @click="handleClick()">{{ text }}</button>
</template>
<script setup lang="ts">
type ButtonColor = 'button-primary' | 'button-secondary' | 'button-success' | 'button-danger' | 'button-warning' | 'button-info'
interface Props {
text: string
color?: ButtonColor
}
withDefaults(defineProps<Props>(), {
color: 'button-primary'
})
const emit = defineEmits(['clickBtn'])
const handleClick = () => emit('clickBtn')
</script>
<style scoped>
.button {
display: inline-block;
padding: 0.6rem 1.2rem;
font-size: 1rem;
font-weight: 500;
text-align: center;
text-decoration: none;
color: #000;
background-color: transparent;
border: none;
border-radius: 0.375rem;
cursor: pointer;
transition: all 0.2s ease-in-out;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
}
.button:hover {
background-color: transparent;
box-shadow: 0 6px 8px -1px rgba(0, 0, 0, 0.15), 0 3px 6px -2px rgba(0, 0, 0, 0.2);
}
.button:active {
transform: scale(0.98);
box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
}
.button-primary {
color: #fff;
background-color: #0d6efd;
}
.button-primary:hover {
color: #fff;
background-color: #0b5ed7;
}
.button-secondary {
color: #fff;
background-color: #6c757d;
}
.button-secondary:hover {
color: #fff;
background-color: #5a6268;
}
.button-success {
color: #fff;
background-color: #198754;
}
.button-success:hover {
color: #fff;
background-color: #157347;
}
.button-danger {
color: #fff;
background-color: #dc3545;
}
.button-danger:hover {
color: #fff;
background-color: #b02a37;
}
.button-warning {
background-color: #ffc107;
color: #fff;
}
.button-warning:hover {
color: #fff;
background-color: #e0a800;
}
.button-info {
background-color: #0dcaf0;
}
.button-info:hover {
background-color: #0993bd;
}
.button:disabled,
.button.disabled {
background-color: #6c757d;
cursor: not-allowed;
opacity: 0.65;
box-shadow: none;
}
</style>
Y localmente lo podemos invocar de la siguiente manera:
<template>
<ButtonComponent @click-btn="hangleClick()" color="button-warning" text="Haceme Click" />
</template>
<script setup lang="ts">
import ButtonComponent from './components/ButtonComponent.vue';
const hangleClick = () => {
console.log('Me hicieron click')
}
</script>
Ya con esto funcionando vamos con las configuraciones para publicarlo como librería.
Actualmente lo que tenemos es un proyecto en Vue3+TypeScript que luce de la siguiente manera:
├── README.md
├── env.d.ts
├── eslint.config.ts
├── index.html
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.svg
│ ├── components
│ │ └── ButtonComponent.vue
│ └── main.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
Empecemos, lo primero que voy a hacer es crear un directorio, llamado "lib", con un archivo index que nos sirva de punto de acceso a nuestro componente y deberiamos estar en este punto:
├── README.md
├── env.d.ts
├── eslint.config.ts
├── index.html
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.svg
│ ├── components
│ │ └── ButtonComponent.vue
│ ├── lib
│ │ └── index.ts <- nuestro punto de acceso
│ └── main.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
Y en el mismo vamos a exportar nuestro componente. ¿Qué va a tener este archivo?... lo siguiente:
export { default as Button } from '../components/ButtonComponent.vue'
Ahora que terminamos con los sencillo vamos a lo que nos importa, la configuración de todos los archivos para que cuando hagamos el npm build
nos cree nuestra bendita librería.
1.- Instalar el paquete para que nos genere el *.d.ts de nuestra libreria
npm install -D vite-plugin-dts
2.- Una vez instalado este paquete vamos por el archivo vite.config.ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// Importo el paquete vite-plugin-dts
import dts from 'vite-plugin-dts'
import path from 'path'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
/**
* Genera archivo .d.ts
*/
dts({
insertTypesEntry: true,
tsconfigPath: './tsconfig.app.json',
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
/**
* Configuración para el empaquetamiento de la libreria
*/
publicDir: 'v-jp-button',
build: {
cssCodeSplit: true,
target: 'esnext',
lib: {
entry: path.resolve(__dirname, './src/lib/index.ts'),
name: 'VJPButton',
fileName: (format) => `v-jp-button.${format}.js`,
},
rollupOptions: {
external: ['vue'],
output: {
globals: {
vue: 'Vue',
},
exports: 'named',
},
},
},
})
3.- Configuración del archivo tsconfig.app.json
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"paths": {
"@/*": ["./src/*"]
},
/**
* Configuración para el empaquetamiento
*/
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "./dist",
"rootDir": "./src",
"module": "node18",
"target": "es2015",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "nodenext"
}
}
4.- Configuración en archivo package.json
{
"name": "v-jp-button",
"version": "0.0.0",
"private": false, // Set to true if you don't want to publish this package
"description": "A customizable Vue button component with loading state and icon support.",
"keywords": ["vue", "button", "component", "ui", "loading", "icon"],
"type": "module",
"skipLibCheck": true, // Skip type checking of declaration files
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
// Export settings for the package
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/v-jp-datepicker.es.js",
"require": "./dist/v-jp-datepicker.umd.js"
}
},
// Files to include in the package
"files": [
"dist"
],
// Resto de la configuración
....
}
5.- Hora de compilar la libreria
npm run build
salida
> v-jp-button@0.0.0 build
> run-p type-check "build-only {@}" --
> v-jp-button@0.0.0 type-check
> vue-tsc --build
> v-jp-button@0.0.0 build-only
> vite build
rolldown-vite v7.1.12 building for production...
✓ 5 modules transformed.
[vite:dts] Start generate declaration files...
[vite:dts] Declaration files built in 2239ms.
dist/index.css 1.51 kB │ gzip: 0.48 kB
dist/v-jp-button.es.js 0.71 kB │ gzip: 0.43 kB
✓ 5 modules transformed.
dist/v-jp-button.umd.js 2.79 kB │ gzip: 1.19 kB
✓ built in 3.85s
Y tendría que generar la directorio "dist" con algo asi:
├── App.vue.d.ts
├── components
│ └── ButtonComponent.vue.d.ts
├── index.css
├── index.d.ts
├── lib
│ └── index.d.ts
├── main.d.ts
├── v-jp-button.es.js
└── v-jp-button.umd.js
Una vez que tenemos esto, es hora de publicar nuestra libreria en NPM
1.- Conectarce a NPM
npm login
2.- Actualizo la versión en el package.json
npm version patch
salida
v0.0.1
npm version patch: Incrementa la versión de parche (ej. 1.0.0 -> 1.0.1).
npm version minor: Incrementa la versión menor (ej. 1.0.0 -> 1.1.0).
npm version major: Incrementa la versión principal (ej. 1.0.0 -> 2.0.0).
npm version X.Y.Z: Puedes especificar una versión concreta (ej. npm version 1.2.3).
3.- Publicamos nuestra libreria
npm publish
salida
npm notice
npm notice 📦 v-jp-button@0.0.1
npm notice Tarball Contents
npm notice 926B README.md
npm notice 334B dist/App.vue.d.ts
npm notice 657B dist/components/ButtonComponent.vue.d.ts
npm notice 1.5kB dist/index.css
npm notice 38B dist/index.d.ts
npm notice 71B dist/lib/index.d.ts
npm notice 11B dist/main.d.ts
npm notice 716B dist/v-jp-button.es.js
npm notice 2.8kB dist/v-jp-button.umd.js
npm notice 1.6kB package.json
npm notice Tarball Details
npm notice name: v-jp-button
npm notice version: 0.0.1
npm notice filename: v-jp-button-0.0.1.tgz
npm notice package size: 3.3 kB
npm notice unpacked size: 8.7 kB
npm notice shasum: 7a1487c1885d373f416ef80b5edf2c206c1aaf7c
npm notice integrity: sha512-liIjZ7AdJuVke[...]Pr5IQWdyLXp7g==
npm notice total files: 10
npm notice
npm notice Publishing to https://registry.npmjs.org/ with tag latest and default access
+ v-jp-button@0.0.1
y listo, publicamos una libreria en NPM, la cual ahora podemos instalar en nuestro próximo proyecto mediante npm install <nombre-paquete>
npm i v-jp-button
Repositorio GIT: https://github.com/jplantes/v-jp-button
Repositorio NPM: https://www.npmjs.com/package/v-jp-button
Top comments (0)