DEV Community

Juan Pablo Pérez lantes
Juan Pablo Pérez lantes

Posted on

Crear librería Vue3 TS

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"
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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',
      },
    },
  },
})
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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
....
}
Enter fullscreen mode Exit fullscreen mode

5.- Hora de compilar la libreria

npm run build
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Una vez que tenemos esto, es hora de publicar nuestra libreria en NPM

1.- Conectarce a NPM

npm login
Enter fullscreen mode Exit fullscreen mode

2.- Actualizo la versión en el package.json

npm version patch
Enter fullscreen mode Exit fullscreen mode

salida

v0.0.1
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Repositorio GIT: https://github.com/jplantes/v-jp-button
Repositorio NPM: https://www.npmjs.com/package/v-jp-button

Top comments (0)