| Tabla de contenidos |
|---|
Explicación en vídeo
| Advertencia |
|---|
Este vídeo corresponde a la versión vieja del script. |
El script de creación ha cambiado desde que se grabó este vídeo, pero la forma de ejecutarlo es la misma. El proceso con el nuevo script se describe en los apartados siguientes.
| Videolink | ||||
|---|---|---|---|---|
|
Creación de proyecto
Para crear un proyecto Vue seguiremos los siguientes pasos (omitiendo los de instalar requisitos previos si ya los tenemos):
Lo primero que necesitamos tener instalado es Node.js, que trae el gestor de paquetes npm, y lo podemos descargar aquí: https://nodejs.org/es/. Debemos instalar la versión LTS (a día de hoy, 05/10/22, es la v14), pues con la última versión puede saltar un error "vue no se reconoce como un comando interno o externo", y esto no sucede con la LTS.
Una vez instalado, tenemos que instalar Vue CLI, que nos proporciona unos comandos para la creación y prototipado de proyectos de forma simple y rápida. Para instalarlo abrimos una terminal de comandos y ejecutamos lo siguiente:
| Bloque de código |
|---|
npm install -g @vue/cli |
Una vez hecho esto, tenemos dos opciones, un script que sirve de arquetipo para aplicaciones FundewebJS, o la forma nativa.
Arquetipo FundewebJS
...
Ejecutamos:
| Bloque de código |
|---|
crearProyecto.bat |
...
Migración y unificación de la UI: visión general
Antecedentes
Cuando empezamos a desarrollar esta nueva aplicación teníamos en mente una estructura de microservicios, separando tanto la UI como el API de los servicios implicados. Creímos que sería más fácil desarrollar aplicaciones en paralelo.
La realidad es que, tras varios servicios desarrollados, el API sí tiene una entidad propia mientras que la UI de éstos acaba siendo bastante simple, con pocas vistas y utilizando, en su mayoría, las mismas librerías y componentes comunes a todos los diferentes servicios.
Además, modificar algo simple, como un detalle en la cabecera, que estuviera en todas estas UI implicaba actualizar la librería de componentes común y volver a desplegar cada una de las aplicaciones. Sin contar con que cada UI tiene un pod de kubernetes independiente, con toda la infraestructura basada en nube que implica.
Esto supone mucho más gasto, tanto en infraestructura como en tiempo de desarrollo de nuevas funcionalidades.
Soluciones elegidas para el problema
- Unificar aquellas UI que no tengan suficiente entidad como para ser independientes.
- Cada equipo de desarrollo tendrá un directorio definido en el repositorio principal. Para esto, cada equipo hará un fork del repositorio y trabajará independientemente desarrollando en su propio equipo y sus entornos privados de desarrollo y preproducción para el testeo que sea pertinente. Una vez tengan las nuevas funcionalidades preparadas para ir a producción, se hará una MR al repositorio principal, se pasará una validación y se mezclará. Siempre funcionalidades completas que estén preparadas para pasar a producción.
- Aprovechando esta unificación, se comenzará la migración de la aplicación de Vue 2 a Vue 3. Se ha configurado la aplicación de tal manera que no sea necesario reescribir código sino que éste sea totalmente compatible, salvo casos muy puntuales, con lo ya desarrollado.
División de la aplicación en módulos
El primer paso ha sido unificar todos los directorios del código fuente en un único directorio llamado app. Dentro de este se ha dividido la aplicación en módulos siguiendo dos criterios:
- Los módulos comunes encargados de la funcionalidad transversal a la aplicación estarán separados sin grupo y por característica o funcionalidad. Estos son, en este momento:
- Auth, lleva la autenticación del usuario, toda la comunicación vinculada a los token de conexión y metodología de renovación de tokens.
- Shared, incluye los componentes comunes que deberán utilizar todos los módulos de los equipos de trabajo. Donde antes teníamos una librería de componentes, ahora se añadirán aquí al igual que mixins y funcionalidades comunes.
- User, datos de usuario (nombre, apellidos, correo, roles y permisos, etc), separado de la autenticación.
- Core, aún sin implementar, será también una carpeta compartida, que incluya las lógicas de negocio que realmente sean fundamentales y comunes para todos los grupos de desarrollo que toquen el proyecto.
- Aparte, cada uno de los equipos de desarrollo tendrá su propio módulo, donde podrá añadir cada uno de los servicios que tenga que añadir como submódulos o dentro del mismo módulo según crean conveniente. Es una solución de consenso para evitar los posibles problemas de mezclas en ramas entre diferentes equipos.
Una vez separados por módulos, cada uno de ellos tendrá control sobre sus propias llamadas al API (que, aunque separada por microservicios y rutas, está en el mismo dominio), estado interno de sus servicios (Con Vuex), definición de rutas y las vistas que éstas cargarán, etc.
Intentando estandarizar el proyecto, también se ha decidido separar vistas de componentes, tal y como hace la instalación por defecto de Vue con vue-cli y router o Nuxt.
Llamadas a APIs backend
Cada módulo debe tener separadas sus propias llamadas al API. Heredarán todos de una capa sobre axios que enviará los tokens automáticamente, teniendo una única configuración para toda la aplicación. Aún así, si un equipo de desarrollo necesitara hacer llamadas propias o configurar su propia versión de axios podría perfectamente.
La idea es agrupar semánticamente las APIs. Si se tiene más de una dependencia con llamadas de API se pueden crear más de un fichero de llamadas. La solución de consenso en cuanto a nomenclatura ha sido poner a todos los ficheros de api este sufijo para distinguirlos fácilmente.
Componentes
Aquí se añadirán todos aquellos componentes que no se carguen directamente desde una ruta de vue router. Siguiendo las recomendaciones de Vue School para grandes aplicaciones se darán nombres correctos, obviamente, a cada componente pero no se harán subcarpetas dentro de esta carpeta, estando todos los componentes en la raíz.
Los componentes habrán de nombrarse usando únicamente PascalCase para seguir los estándares de uso de la comunidad JavaScript.
No será obligatoria ninguna metodología de SCSS/CSS para poder aceptar las PR en el repositorio pero sí se comprobará que esté bien realizado, utilizando las variables CSS del proyecto y el uso de reglas válidas que no interfieran con el resto de grupos de desarrollo.
Igualmente, será obligatorio cumplir las reglas de los linter del proyecto, que se comprobarán antes de poder hacer push así como mientras se desarrolla. Se ayudará a los diferentes equipos de trabajo a configurar sus IDE de forma adecuada.
Vistas
Componentes que se cargan desde una ruta. Si está en la definición de las rutas del módulo se añadirá en esta carpeta. Las vistas, al fin y al cabo, siguen siendo componentes de Vue, pero, por legibilidad y estandarización del repositorio tal y como se trabaja en este tipo de proyectos, se dividen en estas dos categorías.
Obviamente, todas las reglas que se aplican a los componentes se aplicarán a las vistas. Al fin y al cabo, en Vue siguen siendo componentes.
Rutas
Todos los módulos definirán sus propias rutas en un fichero que se inyectará como dependencia en las rutas del sistema. La configuración de las rutas del sistema o de otros módulos deberán permanecer inalterables.
Como serán diferentes grupos de trabajo los que estarán envueltos, en caso de colisión de nombres se resolverá por los interesados, siempre pensando en las URL amigables de cara al usuario, no a la gestión interna del repositorio, que aún en 2022 hay que recordárselo a los desarrolladores.
Estado de la aplicación local al módulo
En lugar de almacenar el estado de la aplicación en una única store con todos los métodos, todos los módulos deberán tener sus propios estados locales a través de módulos de Vuex.
Esto añade una leve dificultad al desarrollo, ya que será necesario que se especifique el nombre al tratar tanto con el estado en sí como con mutaciones o acciones pero será mucho más fácil depurar cualquier fallo derivado del estado.
Con Vue 3 tenemos la opción de pasarnos para la gestión del estado la nueva librería Pinia pero, por el momento, necesitamos que pueda mantener más módulos y nos dé más información a la hora de los commits síncronos para depuración, ya que vamos a estar muchos grupos trabajando a la vez.
Actualización de los módulos
La actualización de Vue 2 a Vue 3 ha sido bastante sencilla. Únicamente ha habido que realizar los pasos que en la propia documentación de Vue han añadido para que el cambio sea totalmente retrocompatible.
Por temas de tiempo de desarrollo, no se va a obligar a todos los equipos a volver a escribir su código en Vue 3 puro, por lo que la retrocompatibilidad era la única opción viable. Por suerte, es inmediato y funciona sin tener que modificar nada.
Sólo hemos encontrado problemas con el componente que estábamos utilizando de calendario, que se ha sustituido por otro.
Como librería de componentes estamos usando PrimeVue. En esta nos encontramos un detalle a tener en cuenta. y es que al importar componentes que estén hechos de forma nativa en Vue 3, por el modo de retrocompatibilidad hay que especificar explícitamente que se importen como componentes Vue 3. Se hace de esta forma, al importar los componentes:
| Bloque de código | ||
|---|---|---|
| ||
import Button from 'primevue/button';
import Dialog from 'primevue/dialog';
import Dropdown from 'primevue/dropdown';
import Message from 'primevue/message';
const compatMode3 = { MODE: 3 };
Dialog.compatConfig = compatMode3;
Dropdown.compatConfig = compatMode3;
Button.compatConfig = compatMode3;
Message.compatConfig = compatMode3; |
Esto habrá que hacerlo para todos los componentes de Primevue, y para los de otras librerías que podamos utilizar que también tenga componentes Vue 3.
Migración paso a paso de la UI
Para realizar la migración de un componente en Vue 2 a Vue 3 han de realizarse los siguientes cambios:
Estructura de directorios
Se ha modificado la estructura lógica del proyecto con el fin de ayudar al desarrollo compartido por parte de múltiples grupos de trabajo. Para mantener esta estructura, cada grupo de trabajo deberá realizar sus cambios en un directorio que penderá de src/app . En él, se hará un subdirectorio por proyecto. Todos los cambios del proyecto, deberán estar contenidos en ese subdirectorio.
(Estructura directorios Vue3 FWjs)
Por otro lado, dado que la gran mayoría de elementos de la UI en Vue 2 han cambiado de ruta al migrar la UI a Vue 3, es necesario revisar las importaciones de los componentes. Se detallarán los cambios concretos más adelante.
Rutas
Muchos elementos existentes en los ficheros de rutas son comunes a todos ellos, motivo por el que esta adaptación es la que más cambios requiere.
El fichero index.js del /router debe moverse a la raíz del proyecto a un fichero con nombre module.routes.js.
En él eliminamos:
- Import de Vue
- Import de VueRouter
- Import de store
- Import de Login
- Import de NotFound
- Import de Accesibilidad
- El Vue.use(VueRouter);
- Las entradas en el objeto router que correspondan a los elementos comunes a todos los proyectos: /login, /404, /accesibilidad ... etc.
- Declaración del router
- Bucle router.afterEach
- El expoort default router.
En cuanto a la carga de las vistas, dado que todo se carga por lazy loading, se deben cambiar las llamadas que haya en los import del principio del fichero con una estructura similar a esta:
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
import nombreInterno1 from './views/Componente1.vue';
import nombreInterno2 from './views/Componente2.vue'; |
Y declararlas del siguiente modo:
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
import { defineAsyncComponent } from 'vue';
const nombreInterno1 = defineAsyncComponent(() => import(/* webpackChunkName: "nombreFragmentoEnDist1" */'./views/Componente1.vue'));
const nombreInterno2 = defineAsyncComponent(() => import(/* webpackChunkName: "nombreFragmentoEnDist2" */'./views/Componente2.vue')); |
Se debe cambiar la definición del objeto routes de modo que quede así:
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
export default [
{
ruta1
}
{
ruta2
}
] |
Además, el checkAuth se debe cambiar por:
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
meta: {
requiresAuth: true,
}, |
en todas las entradas de rutas cuyas vistas requieran estar autenticado.
De este modo, la declaración de cada segmento de la ruta pasa de estar definido así:
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
{
path: '/',
name: 'NombreRuta',
component: NombreComponente,
beforeEnter: checkAuth,
}, |
a estar definido de este modo:
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
{
path: '/',
name: 'NombreRuta',
component: NombreComponente,
meta: {
requiresAuth: true,
},
}, |
Además, como los componentes se han cambiado de ubicación, es necesario adaptar el atributo path de las rutas.
Esta sería la estructura de las rutas de un proyecto FWJS en Vue 2:
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
{
path: '/',
name: 'NombreInterno1',
component: Componente1,
meta: {
requiresAuth: true,
},
},
{
path: '/url-componente2',
name: 'NombreInterno2',
component: Componente2,
meta: {
requiresAuth: true,
}, |
Y esta la de un proyecto FWJS en Vue 3:
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
export default [
{
path: '/nombreProyecto/',
name: 'NombreInterno1',
component: Componente1,
meta: {
requiresAuth: true,
},
},
{
path: '/nombreProyecto/url-componente2',
name: 'NombreInterno2',
component: Componente2,
meta: {
requiresAuth: true,
},
},
]; |
Viene bien recordar que la ruta con path '/' del módulo no debe llamarse 'home', ya que ésta debería ser la principal de micampus.
Una vez hechos estos cambios en el module.routes.js, hay que importar este nuevo fichero en el app.routes.js e incluir AL FINAL del objeto que devuelve el router la entrada correspondiente al module.routes del proyecto.
Import del fichero:
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
import nombreRutasComponente from './nombreGrupo/nombreProyecto/nombreComponente'; |
Declaración en el objeto:
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
export default [
...PoseRoutes,
...MNCSRoutes,
...ultimoFicheroDeRutas, <--
{
path: '/:catchAll(.*)',
...
},
]; |
En la siguiente imagen se puede ver cómo ha quedado el fichero tras añadir el módulo de Mis Certificados:
(Estado app.routes.js tras enlazar con Mis Certificados)
Componentes y vistas
Para migrar un componente o una vista es necesario mover el fichero .vue antiguo a un subdirectorio llamado /components o /views que estará directamente bajo la raíz del proyecto.
En cuanto al código fuente, solo hay que modificar las importaciones. No es necesario realizar ningún otro tipo de adaptación.
Locales
Para facilitar los cambios, se recomienda que toda la internacionalización se encuentre dentro de la carpeta locales y no dentro de los componentes, pero es obligatorio internacionalizar los servicios que se realicen.
Crear una carpeta /locales en la raíz del proyecto y en ella crear un único fichero con prefijo i18n seguido del nombre del proyecto y extensión .json que contenga las cadenas para todos los idiomas. El fichero debe tener esta estructura:
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
{
"en": {
"id1": "chain1",
"id2": "chain2"
},
"es":{
"id1": "cadena1",
"id2": "cadena2"
}
} |
(Ejemplo fichero internacionalización)
Dado que las cadenas de internacionalización van a estar contenidas en el proyecto, la ruta para importar el fichero de internacionalización desde los distintos componentes tendrá la siguiente estructura:
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
<i18n src="../locales/i18nNombreProyecto.json"></i18n> |
(Llamada fichero internacionalización)
APIs
El fichero index.js del /api debe moverse a la raíz del proyecto a un fichero con nombre nombreProyecto.api.js.
En él hay que adaptar apiRequest y baseApiURL:
- apiRequest ya no se declara en los módulos. Ahora se importa del fichero general de API. Destacar que con la arroba enlazamos con '/src'.
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
import { apiRequest } from '@/api'; |
- baseApiURL hay que diferenciarlo del baseApiURL del portal. Por ello hay que renombrarlo a moduleBaseApiURL.
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
const moduleBaseApiURL = '/nombreProyecto/api/'; |
Aparte de estos cambios hay que eliminar la declaración de apiRequest y la importación de axios.
Stores
La Store se ha dividido en módulos, recomendamos ver src/app/app.store.modules.js para tener localizados los distintos módulos de la store que podamos necesitar. Aunque los distintos módulos tienen un nombre autoexplicativo, destacamos dos elementos bastante utilizados que han cambiado de ubicación.
isLogged ya no está en el módulo Login de la store, sino en auth/. La importación de isLogged queda así:
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
computed: {
...mapGetters('auth', ['isLogged']),
}, |
SET_TITLE ahora está ubicado en el módulo pages. La nueva llamada queda de este modo:
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
mounted() {
this.$store.commit('pages/SET_TITLE', 'Solicitar nuevo certificado');
...
} |
Modo de compatibilidad
Para que los componentes hechos en Vue 2 sigan funcionando sin problemas, se ha añadido un modo de compatibilidad en el proyecto que hace que se asuma por defecto que los componentes son de Vue 2. Esto, irónicamente, provoca que algunos componentes Vue 3 de librerías tengan algunos fallos en su funcionamiento. Para evitar esto, tendremos que indicar, tras importarlos, que tienen el modo de compatibilidad 3. Se hará así:
| Bloque de código | ||
|---|---|---|
| ||
import Button from 'primevue/button';
import Dialog from 'primevue/dialog';
import Dropdown from 'primevue/dropdown';
import Message from 'primevue/message';
const compatMode3 = { MODE: 3 };
Dialog.compatConfig = compatMode3;
Dropdown.compatConfig = compatMode3;
Button.compatConfig = compatMode3;
Message.compatConfig = compatMode3; |
Esto habrá que hacerlo, por ejemplo, con todos los componentes de Primevue mientras mantengamos el modo de compatibilidad.
Una vez hecho esto, ya podemos iniciar nuestro proyecto, pero para que funcione correctamente en local, hay que tener dos variables de entorno que colocaremos en un archivo .env.local en la raíz del proyecto. Estas variables son:
VUE_APP_API_ENVIRONMENT=https://micampusdesa.um.esVUE_APP_BACKEND=https://mipc.atica.um.es:8080(indicar la ruta correspondiente en nuestro equipo)
La primera de ellas será micampusdesa, pues se utilizará para proxyficar las llamadas al portal, por ejemplo para poder hacer login. La segunda será la ruta completa de nuestro backend.
Importante: cuando probemos nuestra aplicación en local, tendremos que usar nuestro dominio .atica.um.es, pues a través de localhost no nos dejará hacer login con el portal.
Forma nativa
Desde una terminal, nos situamos en el directorio en el que queramos generar el proyecto (nos podemos mover por los directorios con el comando cd) y ejecutamos el siguiente comando (el nombre que le demos al proyecto debe ser todo en minúsculas):
| Bloque de código |
|---|
vue create nombreproyecto |
A continuación, podremos elegir la configuración por defecto, o seleccionar los componentes y configuración manualmente. Si lo hacemos de forma manual, dispondremos de elementos como vuex o router, y se nos generarán directamente los archivos necesarios. También podremos guardar como presets las configuraciones manuales que hagamos, por si queremos reutilizarlas.
Una vez terminada la creación del proyecto, lo podremos abrir en VS Code desde File → Open Folder y seleccionando la carpeta generada.
Estructura de un proyecto Vue
La estructura de nuestros proyectos Vue vendrá marcada por unos directorios comunes de forma que el código quede organizado de forma similar en todos los proyectos FundeWebJs. El código lo tendremos bajo la carpeta :
- /src/assets: contendrá archivos que necesite nuestro proyecto, imágenes por ejemplo, y también el estilo corporativo mientras no esté disponible como un paquete de npm (guía de estilo corporativo).
- /src/components: componentes Vue propios.
- /src/locales: archivos de internacionalización (documentación de internacionalización).
- /src/mixins: mixins, archivos js para añadir funcionalidad a los componentes. Por ejemplo, aquí tendremos el mixin AuthRefresher para refrescar el token del usuario logeado.
- /src/router: contendrá un index.js con el router donde vendrán definidas las rutas de nuestra aplicación (documentación vue-router).
- /src/services: archivos js correspondientes a las llamadas a nuestros servicios REST (documentación de llamadas a servicios REST).
- /src/store: store de Vuex y sus módulos (documentación Vuex).
- /src/views: componentes que sean vistas, generalmente tendremos uno por ruta.
- /src/App.vue: componente inicial de la aplicación.
- /src/main.js: archivo js donde hacemos imports globales e inicializamos la instancia de Vue.
- /src/i18n.js: inicializa la internacionalización de nuestra aplicación.
Además, en la raíz del proyecto, tendremos:
- package.json: archivo que contiene las dependencias de nuestro proyecto.
- vue.config.js: archivo de configuración.
- archivos de parametrización: podemos tener diferentes archivos que contengan variables de entorno (documentación de parametrización).
Hacemos una diferenciación entre lo que serán vistas y lo que serán componentes para mantener cierta organización y que no acabemos con una carpeta poco intuitiva que contenga demasiados archivos. En ambos casos serán componentes, archivos .vue, pero las vistas serán los componentes que contengan una pantalla completa, mientras que los componentes son partes de éstas, por ejemplo una tabla, o cierta sección que queramos mostrar en nuestra pantalla, o una parte reutilizable que se utilice en diferentes vistas.
La aplicación se inicia en main.js, donde se crea la instancia de Vue. El componente inicial, el que será el padre de todos los componentes, será App.vue. En éste, generalmente tendremos las partes fijas de la aplicación, como el header, footer, o menú. Para el contenido de la página, con <router-view> se le indica que se cargue el componente (que será una vista, es decir, un componente del directorio Views) correspondiente a la ruta en la que nos encontremos. Las diferentes rutas tendremos que definirlas en src/router/index.js. Un ejemplo de App.vue podría ser este, que incluye el header del portal de servicios y el elemento <router-view>:
| Bloque de código | ||
|---|---|---|
| ||
<template>
<div id="app">
<div style="height: 56px;">
<fwjsHeader :title="$t('titulo')" :isLogged="true" style="z-index: 9"/>
</div>
<div id="contenido" style="margin-top: 15px">
<router-view></router-view>
</div>
</div>
</template>
<script>
import fwjsHeader from "@mncs/fwjs-components/fwjsHeader";
export default {
components: {
fwjsHeader,
},
};
</script>
|
Aquí vemos que se utiliza el header del paquete @mncs/fwjs-components, que es un paquete propio. Este se encuentra en Verdaccio, que es el repositorio propio de componentes que tenemos. Tenemos una página de documentación dedicada a cómo subir y descargar componentes: Documentación Verdaccio.
De forma resumida, para este caso, debemos tener el archivo .npmrc en la raíz del proyecto, que ya lo tendremos si hemos utilizado el script de creación, que contenga:
| Bloque de código |
|---|
registry = https://verdaccio.um.es/ |
Y ejecutar este comando en la terminal de VS Code:
...



