Configuración Avanzada de Proyectos Vue con Vite y Vue CLI

Modernizar el flujo de desarrollo de aplicaciones Vue implica reemplazar herramientas obsoletas como vue-cli@3 por soluciones más ligeras y eficientes, como Vite. Aunque este artículo parte del contexto histórico de Vue CLI, su enfoque real es la construcción robusta de aplicaciones Vue mediante configuración modular, gestión de dependencias y arquitectura escalable — sin depender de scripts heredados ni estructuras rígidas.

  1. Alternativa Recomendada: Migrar a Vite

En lugar de instalar vue-cli globalmente (lo cual ya está desaconsejado), se recomienda usar Vite, un entorno de desarrollo extremadamente rápido basado en ESM nativo y HMR instantáneo:

npm create vite@latest my-vue-app -- --template vue
cd my-vue-app
npm install
npm run dev

Vite elimina la necesidad de webpack.config.js personalizado para casos comunes, peero mantiene total flexibilidad mediante vite.config.ts.

  1. Configuración Modular de Webpack (para entornos heredados)

Si aún se requiere soporte para webpack (por ejemplo, en proyectos existentes), una configuración limpia y mantenible se estructura así:

2.1 Archivo base: vite.config.ts (o webpack.config.js)

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
      '@assets': resolve(__dirname, 'src/assets'),
      '@components': resolve(__dirname, 'src/components')
    }
  },
  build: {
    rollupOptions: {
      external: ['vue'],
      output: {
        globals: {
          vue: 'Vue'
        }
      }
    }
  }
});

2.2 Gestión de recursos estáticos

Para integrar HTML dinámico sin html-webpack-plugin, Vite usa index.html como punto de entrada raíz. Basta colocar:

<!-- src/index.html -->

<html lang="es">
<head>
  <meta charset="UTF-8" />
  <title>App Vue</title>
</head>
<body>
  <div id="app"></div>
  <script type="module" src="/src/main.ts"></script>
</body>
</html>

  1. Transformación de Código Moderno

Para compatibilidad con navegadores antiguos, Vite utiliza @vitejs/plugin-legacy:

npm install -D @vitejs/plugin-legacy

// vite.config.ts
import legacy from '@vitejs/plugin-legacy';

export default defineConfig({
  plugins: [
    vue(),
    legacy({
      targets: ['defaults', 'not IE 11']
    })
  ]
});

  1. Integración de Bibliotecas UI

En lugar de importar Element Plus globalmente (lo que aumenta el tamaño del bundle), se aplica carga diferida y registro local:

// src/plugins/element.ts
import { App } from 'vue';
import { ElButton, ElInput, ElMessage } from 'element-plus';

export function setupElement(app: App) {
  app.component(ElButton.name, ElButton);
  app.component(ElInput.name, ElInput);
  app.config.globalProperties.$message = ElMessage;
}

Luego, en main.ts:

import { createApp } from 'vue';
import App from './App.vue';
import { setupElement } from './plugins/element';

const app = createApp(App);
setupElement(app);
app.mount('#app');

  1. Gestión Centralizada de Peticiones HTTP

Se evita vue-axios (obsoleto) y se implementa un cliennte personalizado con interceptores y tipado:

// src/utils/request.ts
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';

const apiClient = axios.create({
  baseURL: import.meta.env.VUE_APP_API_BASE || '/api',
  timeout: 10000,
  headers: { 'Content-Type': 'application/json' }
});

// Interceptor de solicitud
apiClient.interceptors.request.use(
  (config: AxiosRequestConfig) => {
    const token = localStorage.getItem('auth_token');
    if (token && config.headers) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

// Interceptor de respuesta
apiClient.interceptors.response.use(
  (response: AxiosResponse) => response,
  (error) => {
    if (error.response?.status === 401) {
      localStorage.removeItem('auth_token');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

export default apiClient;

  1. Enrutamiento con Vue Router 4

La configuración se separa claramente en rutas, guardias y layouts:

// src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import LayoutDefault from '@/layouts/LayoutDefault.vue';
import NotFound from '@/views/NotFound.vue';

const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    component: LayoutDefault,
    children: [
      { path: '', name: 'home', component: () => import('@/views/Home.vue') }
    ]
  },
  {
    path: '/user/:id',
    name: 'user-detail',
    component: () => import('@/views/UserDetail.vue'),
    props: true // Habilita props automáticos desde parámetros
  },
  { path: '/:pathMatch(.*)*', name: 'not-found', component: NotFound }
];

const router = createRouter({
  history: createWebHistory(),
  routes
});

// Guardia global de autenticación
router.beforeEach((to, from, next) => {
  const requiresAuth = to.meta.requiresAuth as boolean | undefined;
  const isAuthenticated = !!localStorage.getItem('auth_token');

  if (requiresAuth && !isAuthenticated) {
    next({ name: 'login', query: { redirect: to.fullPath } });
  } else {
    next();
  }
});

export default router;

  1. Estado Global con Pinia (reemplazo oficial de Vuex)

Pinia ofrece tipado nativo, menor verbosidad y mejor soporte para TypeScript:

npm install pinia

// src/stores/index.ts
import { createPinia } from 'pinia';

export const pinia = createPinia();

// src/stores/counter.ts
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    message: 'Hola desde Pinia'
  }),
  getters: {
    isEven(): boolean {
      return this.count % 2 === 0;
    }
  },
  actions: {
    increment() {
      this.count++;
    },
    decrement() {
      this.count--;
    }
  }
});

Uso en componente:

<script setup lang="ts">
import { useCounterStore } from '@/stores/counter';

const counter = useCounterStore();
</script>

<template>
  <div>
    <h2>Contador: {{ counter.count }}</h2>
    <p>¿Es par? {{ counter.isEven ? 'Sí' : 'No' }}</p>
    <button @click="counter.increment">+</button>
  </div>
</template>

  1. Estructura de Carpetas Recomendada

src/
├── assets/          # Imágenes, fuentes, estilos globales
├── components/      # Componentes reutilizables (sin lógica de negocio)
├── composables/     # Hooks personalizados (useFetch, useAuth, etc.)
├── layouts/         # Plantillas de página (Header + Sidebar + Main)
├── router/          # Configuración de rutas y guardias
├── stores/          # Estado global (Pinia)
├── utils/           # Funciones auxiliares (request, validators, helpers)
├── views/           # Componentes de nivel de ruta (páginas completas)
├── App.vue
└── main.ts

  1. Buenas Prácticas Clave

  • No usar vue add: Genera código inflexible y sobrescribe archivos críticos. Prefiere instalación manual y configuración explícita.
  • Evitar require o CommonJS: Usa import estático y ESM nativo para mejor tree-shaking y compatibilidad con Vite.
  • Tipado estricto: Configura tsconfig.json con "strict": true y usa defineComponent o defineSetup en composición.
  • Variables de entorno: Usa import.meta.env.VUE_APP_* (Vite) o process.env.NODE_ENV (webpack), nunca variables globales.

Etiquetas: Vite vue-router pinia TypeScript webpack

Publicado el 10-10 05:46