Proyectos en Javascript#
Puesto que Javascript es un lenguaje “agnóstico” y muy flexible, programar un frontend puede ser una tarea muy libre. Esto da mucho poder para hacer cualquier cosa, pero exige una gran responsabilidad por parte de l@s programador@s. En este capítulo vamos a recopilar algunos consejos para enfocar un proyecto grande con Javascript.
Consejos generales#
Escribir Código Fácil de Leer#
El código se lee muchas más veces de las que se escribe. Si te conviertes en un ‘hacker’ demasiado sofisticado, tendrás que encargarte del proyecto tú solo porque los demás no entenderán tu código. Incluso tú mismo olvidarás cómo has hecho tu código con el tiempo.

Fig. 8 https://workchronicles.substack.com/p/comic-as-the-years-go-by#
Los comentarios son esenciales para explicar por qué se hace algo, no solo cómo. Sin embargo, los comentarios no pueden reemplazar un código que ya sea legible por sí mismo. Usa comentarios para agregar contexto o explicar decisiones complejas, pero asegúrate de que el código sea lo más claro y autoexplicativo posible.
Guía de Estilo#
Seguir una guía de estilo es fundamental para mantener la coherencia y legibilidad del código en un equipo de trabajo. Algunas de las guías de estilo más recomendadas son:
Guía de Estilo de AirBnb: AirBnb JavaScript Style Guide
Guía de Estilo de Google: Google JavaScript Style Guide
Es importante leer varias guías y llegar a un consenso en el equipo de trabajo sobre cuál seguir. También puedes consultar la guía de código limpio para JavaScript de devictoribero.
Si queremos que la guía de estilo nos condicione permanentemente, podemos usar un linter como EsLint
.
Evitar el Código Espagueti#
El código espagueti es difícil de leer, entender y mantener. Aquí hay algunas prácticas para evitarlo:
Refactoriza pronto y con frecuencia: No esperes a que el código se vuelva inmanejable. Realiza pequeños ajustes continuamente.
Separa la vista del estado: Mantén la lógica de la interfaz de usuario separada de la lógica del estado de la aplicación.
Utiliza módulos: Divide tu código en módulos para mantenerlo organizado y reutilizable.
Exporta pocas cosas: Los módulos deben exportar solo lo necesario para mantener una interfaz clara y concisa.
Estado de la aplicación: Mantener el estado centralizado es una de las tareas más complejas e importantes en el frontend. Se recomienda usar alguna solución reactiva como RxJS o Redux o al menos una única variable global immutable.
Deuda Técnica#
La deuda técnica es un concepto del desarrollo de software que se refiere a la cantidad de trabajo adicional que es necesario realizar para modificar un programa debido a la elección de soluciones fáciles o rápidas en lugar de las adecuadas o más sostenibles a largo plazo. En otras palabras, es el costo acumulado de tomar atajos o soluciones temporales que, si bien pueden permitir un desarrollo más rápido inicialmente, generan una mayor carga de trabajo en el futuro.
La deuda técnica es una realidad inevitable en el desarrollo de software. Factores como las presiones de tiempo, los cambios en los requisitos y las limitaciones de recursos pueden llevar a la acumulación de compromisos que impactan la calidad del código. Existen distintos tipos de deuda técnica, desde la intencional, cuando se prioriza la velocidad sobre la calidad con conocimiento de sus consecuencias, hasta la inadvertida, que surge por falta de experiencia o desconocimiento. También se puede clasificar según su impacto en la arquitectura del software, la calidad del código o la ausencia de pruebas adecuadas. Si no se gestiona correctamente, la deuda técnica ralentiza el desarrollo, encarece el mantenimiento y aumenta el riesgo de errores, dificultando la evolución del producto.
Para mitigar estos efectos, es fundamental adoptar estrategias de gestión como La identificación, monitoreo y refactorización temprano, con herramientas como linters y revisiones de código. La priorización es clave, ya que no toda la deuda necesita ser resuelta de inmediato. Lo mínimo que un proyecto debe tener son tests.
Arquitectura y estructura#
Principios de arquitectura#
Encapsular el código#
Encapsular el código implica restringir el acceso a ciertos componentes del código y sólo exponer lo que es necesario. Si se utilizan módulos correctamente, ya hay una encapsulación básica. Sin embargo, hay más técnicas y prácticas que puedes aplicar para mejorar aún más esta encapsulación. Estos son algunos de los motivos por los que buscar la encapsulación:
Una buena encapsulación evita la contaminación del ámbito global, reduciendo conflictos de nombres y mejorando la seguridad del código. Además, facilita el mantenimiento, permitiendo modificaciones internas sin afectar otras partes mientras la interfaz pública se mantiene estable. También promueve la modularidad, haciendo que los componentes sean reutilizables y fáciles de probar, además de mejorar la claridad y legibilidad del código. Finalmente, al limitar el acceso a elementos internos, se minimizan riesgos de manipulación no deseada, lo que refuerza la seguridad del sistema.
Formas de encapsular en Javascript:
Encapsulación con IIFE (Immediately Invoked Function Expression): Una técnica eficaz para encapsular el código es utilizar una IIFE (Immediately Invoked Function Expression). Una IIFE es una función que se define y se ejecuta inmediatamente. Esto se hace para crear un ámbito local y evitar la contaminación del ámbito global.
Módulos ES6: Exportando lo necesario, se puede encapsular muy eficientemente. Además, mantienen un estado si es necesario.
Closures: Tećnica muy utilizada antes de los módulos, puede ser útil a nivel de funciones en técnicas como el
Curring
.Espacios de Nombres: se trata de crear objetos literales donde añadir atributos.
Clases ES6: Mediante clases se pueden encapsular partes del código. Luego con closures o atributos privados se pueden ocultar cosas al exterior.
Acoplamiento#
Acoplamiento se refiere a la interdependencia entre diferentes módulos o unidades de software. Un alto grado de acoplamiento implica que los cambios en un módulo pueden afectar a otros, lo que dificulta la modificación y el mantenimiento del sistema. En general, se busca un bajo acoplamiento para mejorar la modularidad y flexibilidad del código.
Ejemplo de Alto Acoplamiento:
// Módulo A
function obtenerDatos() {
return fetch('https://api.example.com/datos')
.then(response => response.json());
}
// Módulo B
function procesarDatos() {
obtenerDatos().then(datos => {
// Procesa los datos
console.log(datos);
});
}
En este ejemplo, el módulo B depende directamente del módulo A para obtener los datos, creando un alto acoplamiento.
Ejemplo de Bajo Acoplamiento:
// Módulo A
function obtenerDatos(apiUrl) {
return fetch(apiUrl)
.then(response => response.json());
}
// Módulo B
function procesarDatos(apiUrl) {
obtenerDatos(apiUrl).then(datos => {
// Procesa los datos
console.log(datos);
});
}
Aquí, el módulo B no depende directamente del módulo A, sino que solo necesita una URL de API. Esto reduce el acoplamiento.
Cohesión#
Cohesión se refiere a cuán estrechamente relacionados y enfocados están los elementos dentro de un módulo. Un módulo altamente cohesivo tiene elementos que están fuertemente relacionados entre sí y juntos realizan una única tarea. Una alta cohesión es deseable porque facilita la comprensión, reutilización y mantenimiento del código.
Ejemplo de Baja Cohesión:
// Módulo con baja cohesión
function modulo() {
// Calcula algo
function calcular() {
// ...
}
// Procesa datos
function procesar() {
// ...
}
// Gestiona la interfaz de usuario
function gestionarUI() {
// ...
}
}
Este módulo realiza múltiples tareas no relacionadas, lo que indica baja cohesión.
Ejemplo de Alta Cohesión:
// Módulo con alta cohesión
function moduloCalculo() {
// Calcula algo
function calcular() {
// ...
}
return {
calcular
};
}
function moduloProcesamiento() {
// Procesa datos
function procesar() {
// ...
}
return {
procesar
};
}
function moduloUI() {
// Gestiona la interfaz de usuario
function gestionarUI() {
// ...
}
return {
gestionarUI
};
}
En este ejemplo, cada módulo tiene una única responsabilidad claramente definida, indicando alta cohesión.
Estructura#
Una estructura bien organizada facilita la navegación y el mantenimiento del código. Aquí hay una estructura propuesta (el nombre de las carpetas es opcional):
Carpeta
models
: Contiene los modelos de datos.Carpeta
pages
: Contiene las vistas organizadas por URL.Carpeta
views
: Contiene los componentes de la vista.Carpeta
helpers
: Contiene utilidades reutilizables.Carpeta
forms
: Contiene los formularios.Archivo
app.js
: Archivo principal de la aplicación.Archivo
router.js
: Contiene la lógica de enrutamiento.Carpeta
libraries
: Contiene bibliotecas externas como jQuery.Archivo
templates.js
: Contiene plantillas de código.
Esta estructura no es rígida y puede ajustarse según las necesidades del proyecto, pero proporciona un buen punto de partida.
Arquitectura#
La arquitectura de una aplicación frontend se basa en la separación de responsabilidades para lograr un código más limpio, mantenible y escalable. Esta separación permite que los diferentes componentes de la aplicación interactúen de manera organizada. A continuación, se describen los elementos básicos que deben separarse y su función:
Interfaz de Usuario (Vista y Controlador)
Vista: Es la parte de la aplicación que interactúa directamente con el usuario, mostrando datos y capturando entradas.
Controlador: Gestiona la lógica de la interfaz de usuario, facilitando la comunicación entre la vista y el modelo.
Estado de la Aplicación (Modelo)
Modelo: Representa y gestiona los datos y la lógica de negocio de la aplicación.
Lógica (Controlador y Servicios)
Controlador: Implementa la lógica de la aplicación, conectando la vista con el modelo.
Servicios: Gestionan la comunicación con el servidor y otras fuentes de datos externas.
Comunicación HTTP (Modelo y Servicios)
Servicios: Encargados de realizar solicitudes HTTP para obtener y enviar datos al servidor.
Rutas (Router)
Router: Gestiona la navegación y las rutas de la aplicación.
Elementos#
Utilización de Modelos#
Siempre hay que separar el Document Object Model (DOM) de los datos de la aplicación. Entre los datos recibidos en formato JSON y los mostrados en HTML, es necesario tener objetos que gestionen esos datos, conocidos como modelos.
Funcionalidad de los Modelos:
Validan la integridad de los datos.
Obtienen y modifican datos del servidor.
Limitaciones de los Modelos:
No deben manipular el DOM.
No deben manejar eventos.
Vistas#
Las vistas se encargan de la interacción con el DOM. Permiten modificar toda la vista sin alterar los modelos ni el servidor y gestionar eventos sin interferir con los modelos. Muchos frameworks separan las vistas de los modelos y, en algunos casos, también los modelos de los controladores.
Método render(): Las vistas suelen tener un método
render()
que muestra la vista en las plantillas correspondientes y las subvistas si es necesario.Plantillas: Pueden ser
<template>
, literales o funciones de template literals etiquetados.Comunicación con el Controlador: Puede ser a través de eventos, eventos personalizados o observables.
Controlador#
El controlador conecta inicialmente la vista y el modelo, gestionando la interacción del usuario y modificando el modelo. En el patrón Model-View-Presenter (MVP), el presentador desacopla totalmente la vista del modelo y actúa como intermediario.
Funcionalidad del Controlador:
Gestiona la interacción del usuario y modifica el modelo.
Servicios (Opcionales)#
Los servicios se encargan de obtener y enviar datos al servidor o al almacenamiento local, permitiendo que los modelos se mantengan manejables y que la lógica de comunicación no afecte al modelo.
Funcionalidad de los Servicios:
Facilitan la comunicación con el servidor.
Permiten cambiar de servidor, protocolos o APIs sin afectar al modelo.
Pueden gestionar el estado de la aplicación.
Ventajas:
Mantienen los modelos ligeros y enfocados en la gestión de datos locales.
Desacoplan la lógica de negocio de la lógica de comunicación.
Rutas (Routers) en Aplicaciones SPA#
Las Single Page Applications (SPA) presentan un problema importante: no tienen rutas reales en la URL. Esto implica varios problemas:
No se puede volver atrás mediante el botón de retroceso del navegador.
No se pueden crear enlaces a partes específicas de la aplicación.
Es necesario crear rutas artificiales que nuestro código JavaScript pueda entender para mostrar diferentes contenidos.
Para solucionar esto, se implementan routers que pueden o no modificar la URL, pero que igualmente deben proporcionar un sistema para navegar hacia atrás.
Navegación Fluida: Los routers permiten una navegación fluida y natural dentro de una SPA, simulando la navegación de una aplicación multipágina.
URL Amigables: Facilitan la creación de URLs amigables que los usuarios pueden compartir y que los motores de búsqueda pueden indexar.
Gestión del Estado: Permiten gestionar el estado de la aplicación a través de la URL, haciendo posible volver a estados previos o específicos.
Modularidad: Ayudan a mantener el código modular y organizado, separando claramente la lógica de navegación de la lógica de presentación y negocio.
El siguiente ejemplo no es la mejor opción por motivos que iremos desgranando más adelante, pero es muy fácil de implementar y de entender inicialmente:
// Establecer una ruta inicial
window.location.hash = "#/login";
// Llamar al router cuando se establece la ruta inicial
router(window.location.hash);
// Agregar un listener para cambios en el hash de la URL
window.addEventListener("hashchange", () => {
router(window.location.hash);
});
// Definir la función del router
const router = (route) => {
console.log(route);
// Rutas con expresiones regulares
if (/#\/city\/[0-9]+/.test(route)) {
let cityId = route.split("/")[2];
CityDetails(cityId);
} else if (/#\/building\/[0-9]+/.test(route)) {
let buildingId = route.split("/")[2];
BuildingDetails(buildingId);
} else if (/#\/survivor\/[0-9]+/.test(route)) {
let survivorId = route.split("/")[2];
SurvivorDetails(survivorId);
}
// Rutas a páginas específicas
else {
switch (route) {
case "#/":
let home = new Home();
home.renderHome();
break;
case "#/survivors":
let survivors = new SurvivorsPage();
survivors.render(app.container);
break;
// Añadir más rutas según sea necesario
default:
let notFound = new NotFoundPage();
notFound.render(app.container);
}
}
};
Explicación del Código
Establecer una Ruta Inicial:
window.location.hash = "#/login";
Se define una ruta inicial
#/login
que será la primera vista cuando se cargue la aplicación.Llamar al Router con la Ruta Inicial:
router(window.location.hash);
Se llama a la función
router
con la ruta inicial para que la aplicación muestre el contenido correspondiente a esa ruta.Listener para Cambios en el Hash:
window.addEventListener("hashchange", () => { router(window.location.hash); });
Se añade un event listener para escuchar los cambios en el hash de la URL. Cada vez que cambia el hash, se llama a la función
router
con el nuevo valor del hash.Definición del Router:
const router = (route) => { console.log(route); // Rutas con expresiones regulares if (/#\/city\/[0-9]+/.test(route)) { let cityId = route.split("/")[2]; CityDetails(cityId); } else if (/#\/building\/[0-9]+/.test(route)) { let buildingId = route.split("/")[2]; BuildingDetails(buildingId); } else if (/#\/survivor\/[0-9]+/.test(route)) { let survivorId = route.split("/")[2]; SurvivorDetails(survivorId); } // Rutas a páginas específicas else { switch (route) { case "#/": let home = new Home(); home.renderHome(); break; case "#/survivors": let survivors = new SurvivorsPage(); survivors.render(app.container); break; // Añadir más rutas según sea necesario default: let notFound = new NotFoundPage(); notFound.render(app.container); } } };
La función
router
toma una ruta como parámetro y determina qué contenido mostrar basándose en esa ruta.Rutas con Expresiones Regulares:
if (/#\/city\/[0-9]+/.test(route)) { let cityId = route.split("/")[2]; CityDetails(cityId); }
Este bloque de código utiliza expresiones regulares para identificar rutas dinámicas, como
#/city/123
. Extrae el ID de la ciudad de la ruta y llama a una funciónCityDetails
con ese ID.Rutas a Páginas Específicas:
else { switch (route) { case "#/": let home = new Home(); home.renderHome(); break; case "#/survivors": let survivors = new SurvivorsPage(); survivors.render(app.container); break; default: let notFound = new NotFoundPage(); notFound.render(app.container); } }
Este bloque maneja rutas estáticas, como la página de inicio
#/
y la página de sobrevivientes#/survivors
. Dependiendo de la ruta, se instancia y se renderiza la vista correspondiente.
Mejoras en el router
Para mejorar este router podemo usar el objeto URL
y no usar rutas con hash
.
Si usamos rutas sin hash y queremos desplegar en Apache o otros servidores hay que configurarlos específicamente para que no busquen esas rutas en los directorios. Con el
npm run dev
o enLive Server
en tiempo de desarrollo no tendremos ese problema.
const routeURL = new URL(route); // Objeto URL
const path = routeURL.pathname; // Lo que está detrás de la /
const search = new URLSearchParams(routeURL.search); // Lo que está detrás de ? en una estructura parecida al Map()
const id = search.get('id'); // Se obtiene id si la ruta tenía ?id=
Al usar tanto paths
como parámetros con ? y & (search
), ya no vale el haschange
, ahora hay que atender a popstate
:
window.addEventListener("popstate", () => {
router(window.location);
});
Componentes Web#
En el desarrollo de aplicaciones web modernas, la idea de componentes ha ganado una gran popularidad. Los componentes, o widgets, son elementos que encapsulan tanto la vista como la lógica de una parte específica de la aplicación. Son la base de algunos frameworks modernos.
Los componentes web son un conjunto de tecnologías que permiten crear elementos personalizados reutilizables en aplicaciones web, con su propia funcionalidad y estilo encapsulados. Esto significa que puedes definir nuevos elementos HTML, con comportamiento y apariencia específicos, que se pueden utilizar como cualquier otro elemento estándar de HTML. Los componentes, bien hechos, pueden proporcionar los siguientes beneficios:
Encapsulamiento: Los componentes web permiten encapsular completamente el HTML, CSS y JavaScript necesarios para un elemento, evitando conflictos con el resto del código.
Reutilización: Una vez creados, los componentes pueden ser reutilizados en diferentes partes de una aplicación o en múltiples aplicaciones, lo que ahorra tiempo y esfuerzo.
Mantenibilidad: Al tener una lógica encapsulada, los componentes facilitan el mantenimiento del código, ya que los cambios en un componente no afectan al resto de la aplicación.
Modularidad: Permiten dividir la aplicación en partes más pequeñas y manejables, mejorando la organización y estructura del código.
Los componentes web se basan en cuatro tecnologías principales:
Custom Elements: Permiten definir nuevos tipos de elementos HTML.
Shadow DOM: Proporciona encapsulamiento de estilo y estructura para evitar colisiones en el DOM global.
HTML Templates: Permiten definir fragmentos de HTML que se pueden reutilizar.
ES Modules: Proporcionan una manera de organizar y cargar código JavaScript de manera modular.
En el capítulo de Web Component se explican de forma técnica.
Estado de la Aplicación#
El estado de la aplicación es un concepto complicado en el desarrollo de aplicaciones web modernas. Controlar y gestionar correctamente el estado puede hacer la diferencia entre una aplicación mantenible y una que sea difícil de manejar y propensa a errores.
Dónde está el estado#
Si estamos separando las páginas y, dentro de estas, hacemos componentes independientes, estos pueden tener su propio estado. Pueden ser autocontenidos, lo cual puede llevar a varios problemas:
Estados compartidos: Puede que varios componentes tengan un estado que se ha de actualizar a la vez. En ese caso, es mejor unirlos en una única variable.
Contradicciones en el estado: Puede que el estado está estructurado de forma que se contradiga.
Redundancias: Puede que un componente padre tenga una información y también la tengan los hijos y al mismo tiempo esté en otro sitio. El problema es actualizar a la vez todos esos datos.
Estados anidados: Puede que unos componentes padres tengan parte del estado y los hijos mantengan otra parte, si es muy profundo ese estado jerárquico es mejor “aplanarlo”.
Pasar datos profundamente: Pasar datos de unos componentes a otros mediante propiedades, atributos… puede ser conveniente. El problema es cuando para por muchos niveles que no hacen nada con los datos a parte de enviarlos a sus componentes hijos. Este anti patrón se llama
Prop Drilling
y hay que evitarlo. En React, por ejemplo, lo evitan con el concepto decontext
, un objeto que tiene el estado y es accesible desde todos los componentes. Leer: https://plainvanillaweb.com/blog/articles/2024-10-07-needs-more-context/
Separación del Estado#
Es importante separar el estado en dos categorías:
Estado del Servidor: Datos que se obtienen y almacenan en el servidor, como información de usuarios, productos, etc.
Estado del Cliente: Datos que solo existen en el cliente, como la interfaz de usuario, configuraciones temporales, credenciales…
Inmutabilidad#
Trabajar con datos inmutables significa que, una vez creados, los datos no pueden cambiar. En lugar de modificar un objeto o array existente, se crea una nueva versión del mismo con las modificaciones requeridas. Este enfoque ofrece varios beneficios:
Facilidad de Depuración: Si los datos no cambian, es más fácil rastrear de dónde provienen los errores.
Predictibilidad: Las funciones puras y los datos inmutables hacen que el comportamiento de la aplicación sea más predecible.
Evitar Efectos Colaterales: Al no modificar directamente los objetos, se evitan efectos colaterales que puedan afectar otras partes de la aplicación.
El estado debe tener métodos para acceder (getters) y modificar (setters) sus valores. No se debe modificar el estado directamente, ya que esto puede llevar a inconsistencias y errores difíciles de rastrear.
Centralización del Control del Estado#
Centralizar el control del estado significa tener un único lugar donde se maneja el estado de la aplicación. Esto facilita su gestión y mejora la coherencia en cómo se accede y modifica el estado. Algunos enfoques para centralizar el estado incluyen:
Espacio de Nombres: Utilizar un objeto global para guardar el estado. Aunque es una solución simple, puede volverse inmanejable a medida que la aplicación crece. Este espacio de nombres puede estar en un
Subject
deRxJS
, por ejemplo, para conseguir reactividad.Servicios: Los servicios centralizan la gestión del estado y pueden ser accesible mediante funciones importadas. Para no hacer que los componentes estén demasiado acoplados a los servicios se puede usar el patrón de
Inyección de dependencias
.Redux: Una biblioteca para manejar el estado de manera predecible en aplicaciones JavaScript.
Beneficios del Uso de Redux#
Redux es una biblioteca popular para gestionar el estado de aplicaciones JavaScript, especialmente en el contexto de aplicaciones de React. Algunos de sus beneficios incluyen:
Estado Centralizado: Todo el estado de la aplicación se almacena en un solo objeto.
Inmutabilidad: Se asegura que el estado sea inmutable.
Debugging: Herramientas como Redux DevTools facilitan la depuración.
Middleware: Facilita la integración de lógica asíncrona y otras funcionalidades adicionales.
A continuación se muestra un ejemplo básico de cómo usar Redux para gestionar el estado de una aplicación de tareas.
import { createStore } from 'redux';
// Estado inicial
const initialState = {
tasks: [],
user: null
};
// Reducer
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'SET_USER':
return {
...state,
user: action.payload
};
case 'ADD_TASK':
return {
...state,
tasks: [...state.tasks, action.payload]
};
default:
return state;
}
};
// Crear el store
const store = createStore(reducer);
// Acciones
const setUser = (user) => ({ type: 'SET_USER', payload: user });
const addTask = (task) => ({ type: 'ADD_TASK', payload: task });
// Usar el store
store.dispatch(setUser({ id: 1, name: 'John Doe' }));
store.dispatch(addTask({ id: 1, title: 'Learn JavaScript', completed: false }));
console.log(store.getState());
I18N (Internacionalización)#
La internacionalización, comúnmente abreviada como I18N (debido a las 18 letras que hay entre la “I” inicial y la “N” final en inglés), es el proceso de diseñar una aplicación de software para que pueda ser adaptada a diferentes idiomas y regiones sin necesidad de realizar cambios en el código base.
Uso de Librerías para I18N#
Existen muchas librerías que facilitan el proceso de internacionalización en aplicaciones web. Algunas de las más populares incluyen:
i18next: Una potente librería de JavaScript que proporciona una solución completa para la internacionalización.
react-i18next: Una extensión de i18next para aplicaciones React.
Polyglot.js: Una librería ligera para manejar la internacionalización en aplicaciones JavaScript.
Intl: Una API nativa de JavaScript para la internacionalización.
Ejemplo con i18next#
A continuación se muestra un ejemplo básico de cómo utilizar i18next para internacionalizar una aplicación web:
Instalación:
npm install i18next
Configuración:
import i18next from 'i18next'; i18next.init({ lng: 'en', // Idioma por defecto debug: true, resources: { en: { translation: { "welcome": "Welcome to our application" } }, es: { translation: { "welcome": "Bienvenido a nuestra aplicación" } } } }); document.getElementById('output').innerText = i18next.t('welcome');
HTML:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Internacionalización</title> </head> <body> <div id="output"></div> <button onclick="changeLanguage('en')">Inglés</button> <button onclick="changeLanguage('es')">Español</button> <script src="path/to/your/js/file.js"></script> </body> </html>
Función para cambiar el idioma:
function changeLanguage(language) { i18next.changeLanguage(language, () => { document.getElementById('output').innerText = i18next.t('welcome'); }); }
Aunque existen múltiples librerías que facilitan este proceso, también es posible implementar soluciones personalizadas utilizando variables y funciones en JavaScript. Independientemente del enfoque elegido, la clave está en separar los textos del código y proporcionar mecanismos para cambiar fácilmente entre diferentes idiomas.
Lazy Loading#
Lazy Loading es una técnica utilizada en el desarrollo de software para retrasar la carga o la inicialización de un recurso hasta el momento en que realmente se necesita. Esta técnica es especialmente útil en el desarrollo web, ya que permite optimizar el rendimiento de las aplicaciones, reduciendo el tiempo de carga inicial y la cantidad de recursos consumidos.
En JavaScript, se puede implementar Lazy Loading para cargar módulos de manera dinámica cuando se necesiten. Esto es particularmente beneficioso para aplicaciones grandes y complejas donde no es necesario cargar todos los módulos en el momento de iniciar la aplicación.
Lazy Loading en Vanilla JavaScript#
En JavaScript Vanilla, se pueden cargar módulos de manera dinámica utilizando la sintaxis import()
. Esta sintaxis devuelve una promesa que se resuelve con el módulo cargado, permitiendo así cargar solo los módulos necesarios en el momento adecuado.
Ejemplo de Lazy Loading en Vanilla JavaScript#
Supongamos que tenemos un módulo que queremos cargar de manera dinámica:
export class WelcomeModule {
constructor() {
console.log("WelcomeModule loaded");
}
greet() {
console.log("Hello from WelcomeModule");
}
}
document.getElementById('loadModuleBtn').addEventListener('click', async () => {
const { WelcomeModule } = await import('./welcome/ModuleImpl.js');
const module = new WelcomeModule();
module.greet();
});
En este ejemplo, el módulo WelcomeModule
se carga solo cuando el usuario hace clic en el botón “Load Module”. Esto significa que el código del módulo no se incluye en la carga inicial de la página, lo que puede mejorar significativamente el tiempo de carga y el rendimiento de la aplicación.
Tanto en JavaScript Vanilla como en frameworks modernos, implementar Lazy Loading es una práctica recomendada para gestionar aplicaciones complejas y de gran escala.