Carga Dinámica de Rutas en Vue 3 con Vite: Solución de Problemas Comunes

Al implementar rutas dinámicas en Vue 3 con Vite, pueden surgir ciertos inconvenientes, especialmente cuando los datos de ruta provienen de una API externa y requieren un procesamiento previo.

La importación estática de componentes usando import() funciona sin problemas:

component: () => import('@/components/ExampleComponent.vue')

Sin embargo, al intentar cargar rutas dinámicamente desde datos recibidos del back end, es necesario un paso adicional de transformación de datos si el formato no es el esperado. A continuación, se presenta un ejemplo de cómo reestructurar datos anidados para que se ajusten al formato de rutas de Vue Router:

const transformarDatosRuta = (datosApi) => {
  const rutasProcesadas = datosApi.map((elemento) => {
    // Función recursiva para procesar subrutas
    const procesarSubRutas = (subRutas) =>
      (subRutas || []).map((subElemento) => ({
        path: subElemento.path,
        name: subElemento.name,
        // Asigna 'Layout' si es un nivel superior, de lo contrario usa la ruta
        component: subElemento.nivelPermiso === 1 ? 'Layout' : subElemento.path,
        meta: {
          id: subElemento.id,
          icon: subElemento.icon || undefined,
          title: subElemento.title,
        },
        // Manejo recursivo de subniveles
        ...(subElemento.siguientesNiveles
          ? {
              children: procesarSubRutas(subElemento.siguientesNiveles),
            }
          : {}),
      }));

    // Crea un nuevo objeto para la ruta actual
    const rutaActual = {
      path: elemento.path,
      name: elemento.name,
      component: elemento.nivelPermiso === 1 ? 'Layout' : elemento.path,
      meta: {
        id: elemento.id,
        icon: elemento.icon || undefined,
        title: elemento.title,
      },
      // Procesamiento recursivo de subrutas
      ...(elemento.siguientesNiveles
        ? {
            children: procesarSubRutas(elemento.siguientesNiveles),
          }
        : {}),
    };

    // Elimina la propiedad 'children' si está vacía para evitar problemas de renderizado
    if (rutaActual.children && rutaActual.children.length === 0) {
      delete rutaActual.children;
    }
    return rutaActual;
  });
  // La adaptación a 'siguientesNiveles' se debe a nombres variables en la API
  // Si la API usa consistentemente 'children', esta parte puede simplificarse.
  return rutasProcesadas;
};

Una vez que los datos están en el formato correcto, la adición de estas rutas se maneja típicamente dentro de un guardia de ruta (router.beforeEach) en un archivo como permission.ts:

import { usePermissionStore } from '@/stores/permission';
import { useUserStore } from '@/stores/user';
import router from '@/router'; // Asume que tu router está configurado aquí

router.beforeEach(async (to, from, next) => {
  const PermissionStore = usePermissionStore();
  const UserStore = useUserStore();

  if (to.path === '/login') {
    next(); // Permitir acceso a la página de login
  } else {
    // Si las rutas aún no se han generado, las generamos
    if (!PermissionStore.routes.length) {
      try {
        // Obtener las rutas dinámicas basadas en los roles del usuario
        const dynamicRoutes = await PermissionStore.generateRoutes(UserStore.roles);
        
        // Procesar y añadir las rutas generadas al router
        await addDynamicRoutes(dynamicRoutes);
        
        // Redirigir a la ruta actual para asegurar que se carguen correctamente
        next({ ...to, replace: true });
      } catch (error) {
        console.error("Error generating routes:", error);
        next('/login'); // Redirigir al login en caso de error
      }
    } else {
      next(); // Las rutas ya existen, continuar
    }
  }
});

// Función para añadir rutas dinámicamente
const addDynamicRoutes = async (routesConfig) => {
  routesConfig.forEach((routeItem) => {
    // Si el componente es 'Layout', resuélvelo desde el directorio de layout
    if (routeItem.component === 'Layout') {
      routeItem.component = resolveComponent('layout/index.vue');
    } else {
      // De lo contrario, resuélvelo desde el directorio de vistas
      routeItem.component = resolveComponent(routeItem.path);
    }

    // Si la ruta tiene hijos, procesarlos recursivamente
    if (routeItem.children && Array.isArray(routeItem.children)) {
      addDynamicRoutes(routeItem.children);
    }
    
    // Añadir la ruta configurada al router
    router.addRoute(routeItem);
  });
};

// Función para resolver componentes de forma dinámica
const resolveComponent = (componentPath) => {
  // Mapeo de todos los archivos .vue en el directorio de vistas
  const views = import.meta.glob('/src/views/**/*.vue');
  // Mapeo de todos los archivos .vue en el directorio de layout
  const layouts = import.meta.glob('/src/layout/**/*.vue');

  let componentResolver;
  if (componentPath.startsWith('layout/')) {
    // Si la ruta comienza con 'layout/', busca en el directorio de layouts
    componentResolver = layouts[`/src/${componentPath}`];
  } else {
    // De lo contrario, busca en el directorio de vistas
    componentResolver = views[`/src/views${componentPath}.vue`];
  }

  if (!componentResolver) {
    throw new Error(`Componente no encontrado: ${componentPath}. Verifique la ruta y la extensión .vue.`);
  }
  return componentResolver;
};

Etiquetas: Vue3 Vite vue-router dynamic-routes code-splitting

Publicado el 7-31 03:01