El fichero index.html generado por Angular

Análisis del HTML inicial creado por el framework de Angular

Alejandro Cuba Ruiz
ngconf

--

Durante la ceremonia de apertura de los Juegos Olímpicos de Londres 2012, Tim Berners-Lee hizo una aparición simbólica ilustrando su innovadora propuesta de 1989. Tres décadas y media después de que introdujera los documentos de hipertexto basados en el Lenguaje Estándar Generalizado de Marcado (SGML), las etiquetas HTML continúan utilizando corchetes angulares para distinguir claramente el contenido del código que lo estructura. Esta decisión influyó posteriormente en la etimología del framework de Angular.

Berners-Lee desarrolló esta tecnología mientras trabajaba en el CERN en Ginebra, Suiza, para facilitar el intercambio de documentos científicos a través de Internet, a la cual denominó WorldWideWeb.

Tim Berners-Lee during the 2012 Olympics ceremony
Sir Tim Berners-Lee con un NeXT Cube durante la ceremonia de apertura de los Juegos Olímpicos de 2012. Crédito de la foto: http://compmuseum.org/

Además del HTML, este científico de la computación desarrolló otras tecnologías esenciales para la Web que conocemos hoy en día: el URI, el protocolo de la capa de aplicación HTTP, el primer servidor web y el primer navegador web.

Hoy en día, contamos con potentes herramientas como el Angular CLI para generar la estructura de una aplicación web y navegadores modernos que interpretan desde el código HTML inicial hasta los recursos multimedia vinculados para mostrar o narrar contenido a nuestros usuarios.”

Un vistazo a la secuencia de inicio

La secuencia de carga típica de una aplicación de Angular sigue estos pasos, simplificados para ilustrar el proceso:

  1. El navegador envía una solicitud inicial al servidor web para recuperar el contenido web.
  2. El servidor responde con un fichero HTML.
  3. Al descargar y analizar este fichero HTML, el navegador descubre la necesidad de buscar recursos adicionales como tipografías, imágenes y código JavaScript necesario para ejecutar la lógica declarada en Angular y permitirle el recorrido al usuario por la aplicación.
  4. El navegador envía solicitudes para estos recursos adicionales al servidor, que puede o no responder con los ficheros solicitados.
  5. Mientras tanto, el navegador crea y mantiene en memoria el DOM, una estructura de árbol donde cada nodo representa un elemento en el documento HTML resultante. Entre ellos está el <app-root>, que se convierte en el elemento padre para los demás componentes en la aplicación de Angular.
  6. Una vez construida la versión inicial del árbol DOM, el navegador lo utiliza para crear otros modelos, como el Árbol de Accesibilidad, el CSSOM y el Árbol de Representación, que finalmente calcula, basado en la información de contenido y estilo, la disposición de los elementos visuales antes de que sean dibujados en pantalla.
  7. A medida que el usuario interactúa con la aplicación, Angular maneja la lógica y actualiza la interfaz de usuario según sea necesario.

Al final todo esto comienza con la solicitud del siguiente fichero de texto alojado en un servidor web:

El fichero index.html por defecto generado por el Angular CLI al crear un nuevo proyecto

Ten en cuenta que el contenido del fichero se muestra en tu editor de código utilizando tipografía legible y resaltado de colores para mejorar la legibilidad para los humanos. Cuando estos datos se procesan de máquina a máquina, la representación visual de estos caracteres es irrelevante, ya que las computadoras solo interpretan el código subyacente, no los símbolos latinos en cuestión. Abordaremos más sobre este tema en la sección sobre codificación de caracteres.

Examen línea por línea

Vamos a examinar más de cerca el código HTML predeterminado generado por el Angular CLI después de ejecutar el comando ng new.

Línea #1: El Doctype

<!doctype html>

Esta línea requerida con la declaración del tipo de documento es la primera instrucción que reciben los navegadores web, indicando si el documento está escrito en una versión específica de HTML o XML. Esto asegura la consistencia entre diferentes navegadores y plataformas. Sin el doctype, los navegadores antiguos pueden llegar a aplicar modos de representación incorrectos, generando inconsistencias en el diseño y el estilo de la aplicación web.

¿Recuerdas las complejas declaraciones <!doctype … > en HTML 4.01 y XHTML? Cuando el W3C anunció oficialmente el primer borrador de HTML5 en enero de 2008, a muchos de nosotros nos complació descubrir que el nuevo doctype lo habían simplificado a una sola línea de código insensible a mayúsculas y minúsculas, haciendo que el contenido del fichero sea más limpio y legible desde entonces.

Línea #2: El elemento HTML

El elemento <html> representa la raíz del documento HTML, donde el resto de los elementos deben ser sus descendientes.

Los desarrolladores necesitamos declarar el idioma del contenido proporcionando el atributo lang con una etiqueta de idioma válida, como define el RFC 5646. Esto ayuda a las máquinas, como los motores de búsqueda y los lectores de pantalla, a identificar correctamente el idioma utilizado en el contenido y metadatos del documento. Sin especificar el idioma, las herramientas de accesibilidad podrían adoptar la configuración predeterminada de idioma del sistema operativo, lo que podría llevar a pronunciaciones incorrectas.

Es importante señalar que el atributo lang no está limitado al elemento raíz. Como atributo global, también se puede aplicar a cualquier otro elemento, facilitando que las máquinas analicen contenido multilingüe.

Los elementos HTML también pueden contener el atributo dir para indicar la dirección del texto. Para contenidos escritos en idiomas como el árabe, farsi, kurdo y el hebreo, que utilizan un guión de derecha a izquierda, el atributo dir se puede establecer en dir="rtl" para garantizar una visualización adecuada.

Línea #3: El encabezado del documento

Es dentro del elemento <head> de un fichero HTML donde debemos incluir los metadatos necesarios para asegurar que un documento web funcione correctamente y que los motores de búsqueda puedan entender su contenido. Esto incluye las definiciones de estilo, scripts y enlaces a recursos externos requeridos.

Línea #4. El conjunto de caracteres de la máquina

Para mostrar cualquier documento web, el navegador necesita saber cómo convertir los bytes recibidos del servidor web en contenido que los usuarios puedan entender. La descripción de charset le indica al navegador cómo decodificar los datos descargados en caracteres legibles y frases audibles, incluso si el servidor no proporciona la información correcta en las cabeceras HTTP.

La codificación de caracteres es el método que permite convertir un carácter de un lenguaje natural (como el de un alfabeto o silabario) en un símbolo de otro sistema de representación, como un número o una secuencia de pulsos electrónicos en un sistema electrónico aplicando normas o reglas de codificación.
- Definición de la Wikipedia

UTF-8, que significa Formato de Transformación Unicode de 8 bits, es la codificación de caracteres más utilizada en la web. Este formato fue propuesto inicialmente por Ken Thompson y Rob Pike en 1992 como una manera de representar todos los caracteres posibles de la mayoría de los lenguajes naturales. Una década después, fue estandarizado por la IETF. Aunque el <meta charset="utf-8"> se coloca típicamente como el primer hijo del elemento <head>, puede ser necesario incluir metadatos adicionales o rastreadores de análisis antes de esta línea. Solo ten en cuenta que debes declarar la definición de charset dentro de los primeros 1024 bytes del fichero HTML para prevenir problemas de rendimiento web. De lo contrario, el navegador gastará milisegundos preciados intentando adivinar el charset utilizando otras técnicas durante la carga del documento.

Línea #5: El título

Angular permite que en la lógica de enrutamiento de la aplicación declaremos el título para cada URI de manera separada, en lugar de tener un texto estático codificado en el elemento <title> del fichero index.html. Hay múltiples razones por las que siempre deberíamos escribir un título unívoco por ruta:

  • UX: Ayuda a los usuarios a entender en qué página o sección se encuentran, especialmente cuando tienen múltiples pestañas, ventanas, o espacios del navegador abiertos.
  • SMO y marcadores: Si los usuarios deciden guardar o compartir el enlace correspondiente a la ruta actual, un título descriptivo puede ayudar a comprender rápidamente el contenido.
  • SEO y análisis del documento: Los motores de búsqueda priorizan el elemento <title> durante la indexación de páginas, por lo que cuando alguien realiza una búsqueda relacionada con tu contenido web, una coincidencia de palabras clave en el título de la página puede ayudarte a clasificar más alto en los resultados de búsqueda. Además, el elemento del título se utiliza comúnmente como el encabezado donde se puede hacer clic.
  • Accesibilidad: Los usuarios con discapacidades visuales, o aquellos con dispositivos carentes de una pantalla, confían en el título del documento web para entender rápidamente el contenido. Un título claro puede mejorar significativamente la experiencia para cualquier persona que use un lector de pantalla o tecnologías similares.

Línea #6: La raíz base

El elemento <base> nos permite especificar la URL base para todas las rutas relativas a una aplicación web. Por defecto, Angular CLI establece su propiedad href en /, lo que significa que el enrutador de Angular tratará la raíz de la aplicación como la raíz del dominio. Esta configuración facilita la resolución de URLs relativas para imágenes, estilos, scripts y cualquier otro recurso vinculado.

Cuando una aplicación web se despliega en un subdirectorio o en un dominio diferente, puede que resulte necesario actualizar la URL base para evitar que la lógica de ejecución de Angular falle al localizar los recursos requeridos debido a una etiqueta <base> mal configurada.

Línea #7: El puerto de visualización del navegador

En el contexto del desarrollo web, el término en inglés “viewport” es cautivador porque “view” se refiere al área visible de una interfaz web, y “port” puede interpretarse como el puerto a través del cual se entrega información visual al usuario.

Imagen generada usando el modelo de IA DreamShaper v8

La forma en que podemos instruir a los navegadores sobre cómo gestionar el diseño y la escala de una página web en diferentes tamaños de pantalla es estableciendo valores específicos en la etiqueta meta correspondiente. Esto es particularmente útil para cumplir con los requisitos de diseño adaptativo.

Esta etiqueta HTML tiene un atributo content para especificar los parámetros del viewport:

  • width y height controlan las dimensiones del viewport.
  • interactive-widget especifica el comportamiento de los elementos superpuestos en la interfaz de usuario, como teclados visuales, extensiones de navegador y herramientas de accesibilidad.
  • initial-scale, minimum-scale, y maximum-scale establecen el nivel de zoom inicial y restringen el zoom adicional.
  • user-scalable=no evita el zoom por parte del usuario, lo cual obstaculiza algunos de los principios perceptibles de las directivas WCAG.

Línea #8: El icono

El icónico favicon.ico, persistente en la web desde la década de 1990, allanó el camino para la moderna visualización de iconos que ayudan a identificar visualmente un sitio web desde una variedad de dispositivos y plataformas. Hoy en día, a pesar del del amplio soporte del WebP y las características vectoriales del SVG para crear iconos adaptativos, el PNG y el JPG continúan siendo los formatos de imagen más utilizados en la web.

Si decides mejorar el favicon.ico predeterminado incluido en cada nuevo proyecto de Angular utilizando un formato de mapa de bits fijo, que sacrifica píxeles y calidad de imagen al redimensionarse, es una buena idea depender de los tamaños de píxel tradicionales 16x16, 32x32, 192x192 y 512x512 para crear la ilusión de un icono fluido a través de diferentes condiciones de los medios de representación. Sin embargo, esta técnica no es muy eficiente debido a problemas potenciales con el tamaño del fichero, el número de solicitudes HTTP y la calidad de imagen percibida.

Un mejor enfoque para escalar el icono predeterminado de Angular implica implementar una sencilla solución gracias al SVG. Este método adaptativo utiliza la técnica tradicional de mejora progresiva, permitiendo que los navegadores antiguos carguen el ICO o cualquier fichero PNG de la colección predefinida de tamaños si no pueden leer el formato SVG.

<link rel="icon" type="image/x-icon" href="favicon.ico"> <!-- icono de reserva -->
<link rel="icon" type="image/svg+xml" href="favicon.svg"> <!-- icono adaptativo -->

Líneas #9, #12, #13: Etiquetas de cierre

Las etiquetas HTML son delimitadoras: le indican al navegador dónde comienza un elemento y dónde termina. Sin ellas, la definición del documento sería un gran desorden. Al cerrar una etiqueta con la sintaxis de barra inclinada aseguramos que cualquier elemento anidado se asocie correctamente con su elemento padre.

<!-- las etiquetas que envuelven contenido interno necesitan ser cerradas -->
<elemento attributo1="valor1">Contenido</elemento>
<elemento attributo1="valor1"><hijo>Subcontenido</hijo></elemento>

Los elementos que contienen solo valores de atributos y no tienen contenido interno no requieren una etiqueta de cierre. En la versión 15.1.0, Angular introdujo etiquetas auto-cerrables para elementos personalizados sin proyección de contenido, reduciendo el código repetitivo en las plantillas HTML.

<!-- etiqueta auto-cerrable -->
<elemento attributo1="valor1" attributo2="valor2" />

Línea #10: El cuerpo del documento

Este elemento HTML requerido rodea el contenido principal de un documento. Debe ser el segundo hijo de la etiqueta <html>.

Al igual que los otros elementos dentro del DOM, la interfaz HTMLBodyElement correspondiente es una representación orientada a objetos de la etiqueta <body>. Tiene varias propiedades y métodos que se pueden usar para manipular y controlar el elemento que estructura el diseño de la página web.

Línea #11: El elemento raíz de la aplicación de Angular

El último elemento en esta lista corresponde a la etiqueta <app-root>. Es el marcador de posición para el elemento raíz, en el cual los procesos de tiempo de ejecución de Angular insertarán o reemplazarán el HTML resultante del componente principal después de que la aplicación complete la fase de arranque. Ten en cuenta que puedes renombrar esta etiqueta en la declaración del decorador del componente principal para que coincida con tu sistema de nombres preferido.

Más allá de los elementos predeterminados

Puede que te sea necesario agregar metadatos para SEO y SMO, así como código de seguimiento para el análisis y monitoreo del rendimiento web. A menos que necesites incrustar código inline dentro de la declaración HTML, configura los estilos y scripts externos en el fichero de configuración de Angular para asegurar que estos recursos se carguen de manera eficiente.

Además, considera incluir propiedades semánticas de Schema, que pueden ser validadas a través de la herramienta Google Structured Data Testing Tool, e incorporar el elemento <noscript> para manejar escenarios poco comunes hoy en día donde JavaScript esté deshabilitado.

Además, recuerda que a los motores de búsqueda les gusta la etiqueta robots y su fichero acompañante robots.txt; a los creadores web el humans.txt, y a los departamentos legales, el license.txt. Todos estos ficheros deben estar ubicados en el directorio raíz.

Si tienes planificado convertir tu aplicación de Angular a una PWA, el Angular CLI actualiza automáticamente el fichero index.html para incluir la declaración de metadatos de color del tema visual y un enlace al fichero manifest.webmanifest luego de ejecutar el comando ng add @angular/pwa.

Trata de mantener el contenido HTML al mínimo, incluyendo solo los elementos esenciales para que Angular funcione de manera eficiente, acorde a tu estrategia de producto.

También es importante asegurar que el index.html y cualquier plantilla HTML subsiguiente contengan HTML válido, a pesar de que los navegadores han sido históricamente bastante tolerantes con los errores de sintaxis HTML. Por suerte, el compilador de Angular puede ayudar a prevenir muchos errores, y herramientas como el Angular Language Service y el ESLint pueden proporcionar comentarios adicionales durante la creación de las plantillas. Esto ayuda a poder entregar HTML de calidad que funcione en diversas plataformas y tipos de navegadores web.

Listos para la distribución

Antes de concluir, toma un par de minutos para ejecutar el comando ng build y examinar el resultado del fichero index.html ubicado en el directorio /dist. Este fichero contiene la versión final del código HTML que el navegador del usuario obtendrá de un servidor web.

El fichero index.html por defecto generado por el constructor de Angular

Observa las adiciones que se han hecho al fichero HTML del código fuente. Por ejemplo, el atributo data-critters-container es un subproducto de la biblioteca Critters, que inserta los estilos críticos de la aplicación dentro de la estructura HTML para optimizar la estabilidad visual CLS.

Después de transpilar el código TypeScript hacia la versión de JavaScript especificada y procesar la sintaxis de estilo elegida a CSS directamente, el sistema de construcción basado en Webpack o esbuild minimiza y agrupa los ficheros configurados en el angular.json.

Por defecto, Angular crea las referencias a estos ficheros con hashes de ruptura de caché agregados a cada nombre de fichero:

  • main.js contiene la lógica de la aplicación, incluido el código de tiempo de ejecución responsable de iniciar la aplicación de Angular.
  • styles.css contiene estilos personalizados que definen la apariencia general de la aplicación y sus componentes.
  • polyfill.js, un fichero opcional que facilita la carga de código extra en el navegador. Desde los inicios del framework, este fichero era esencial para asegurar que instrucciones modernas de JavaScript fuesen interpretadas correctamente en navegadores antiguos, pero con Angular dejando de apoyar dichas versiones, el contenido predeterminado de este fichero sólo incluye actualmente el código de la biblioteca Zone.js. A partir de Angular 18, las aplicaciones que experimenten con una arquitectura Zoneless ya no requieren esta dependencia para ejecutar la detección de cambios, excepto cuando hay una necesidad de apoyar temporalmente un mecanismo híbrido.

Basado en la configuración del fichero angular.json y tu estrategia de carga diferida en el enrutador, podrían producirse más ficheros declarados inicialmente o cargados bajo demanda.

En aplicaciones SSR representadas del lado del servidor, donde el constructor de Angular renderiza el estado HTML inicial de los componentes de Angular en el servidor, se genera una versión precompilada del index.html en el directorio de distribución /browser, junto con la versión renderizada del lado del cliente, el fichero index.csr.html.

Adicionalmente, en el directorio de distribución del servidor, el fichero index.server.html contiene un script para gestionar los despachos de eventos y las interacciones de la interfaz gráfica. En SSR, el incrustado inline de CSS crítico se realiza durante la representación del lado del servidor, por lo que Critters no marca el elemento <html> en este fichero, ya que su procesamiento del lado del cliente no va a suceder.

Distinct versions of the index.html file generated by the Angular builder for SSR apps

Es posible que te preguntes por qué el resultado no está minificado. Esta técnica, que reduce el tamaño del fichero HTML eliminando caracteres innecesarios como espacios en blanco y saltos de línea, fue oficialmente descontinuada en Angular 6. Ahora hay un interés limitado en reinstalarla en el proceso de construcción, ya que las ganancias de rendimiento son tan pequeñas que no equilibran el trabajo adicional que habría que realizar, incluyendo además dependencias adicionales. Además, este tema es particularmente relevante en SSR, donde el código HTML debe permanecer intacto, incluso al punto de mantener los nodos de comentarios, ya que el mecanismo de hidratación depende del conservar el markup original.

Aún así, existen más optimizaciones ocurriendo sobre el fichero index.html, como el soporte CORS y la incrustación de referencia a tipografías. Además, al usar la directiva NgOptimizedImage, sugerencias de carga especulativa son adicionadas al elemento <head> de encabezado del documento para preconectar con dominios de carga personalizados o de terceros, o incluso para indicar, en modo SSR, que una imagen prioritaria candidata a convertirse en el elemento LCP, deba precargarse en el navegador para acelerar la obtención de recursos críticos. Para descubrir más ajustes automáticos en imágenes optimizadas, consulta este artículo:

Conclusión

Entender cada detalle del (o los múltiples ficheros en modo SSR) index.html automáticamente generado en el momento de la construcción, revela algunas de las decisiones técnicas y esfuerzos de optimización realizados por el equipo central y los contribuyentes al framework de Angular.

Inspectores como Chrome DevTools o Firefox Developer Tools pueden ayudarnos a examinar la estructura del DOM resultante en varias etapas del ciclo de vida de los componentes de Angular, el estado de la aplicación y las interacciones de usuario para optimizar nuestras aplicaciones con vistas a una mejor usabilidad, velocidad, y posibilidad de ser descubiertas por los motores de búsqueda y accesibles desde las redes sociales.

--

--

Alejandro Cuba Ruiz
ngconf

<front-end web engineer />, Angular GDE, traveler, reader, writer, human being.