Tema representativo de entrevista

Entrevista de Frontend: Diseñar Shadow DOM declarativo con hidratación progresiva

FrontendDifícil
Equipo editorial de Offer.ccPublicado Actualizado

Pregunta

Una tarjeta de producto renderizada en el servidor debe funcionar antes de que cargue JavaScript, encapsular estilos y volverse interactiva después de la hidratación. Diseñe un enfoque de Shadow DOM declarativo y explique raíces open frente a closed, proyección de slots, marcado de fallback, mejoras (upgrades) de custom elements, streaming y pruebas.

Prompt y alcance

La página transmite (streams) tarjetas de producto desde el servidor. Cada tarjeta debe renderizar contenido significativo sin JavaScript, mantener los estilos del componente aislados y adjuntar comportamiento cuando llegue el bundle del cliente. Utilice un shadow root declarativo y, a continuación, describa la mejora progresiva para los navegadores que no analizan sintácticamente (parse) el atributo y para los custom elements que realizan el upgrade después de que el HTML esté presente.

La habilidad principal es el renderizado del navegador, los límites de componentes y la corrección de la hidratación, por lo que esto pertenece a frontend.

Qué evalúan los entrevistadores

Primero, ¿sabe que una plantilla que lleva shadowrootmode="open" o "closed" es convertida por el parser de HTML en un shadow root cuando es compatible?

Segundo, ¿puede explicar que solo se adjunta el primer shadow root declarativo para un host; los posteriores permanecen como plantillas y necesitan una estrategia de fallback intencional?

Tercero, ¿puede preservar slots, estilos, accesibilidad y comportamiento de formularios a través del HTML del servidor y el upgrade del cliente?

Cuarto, ¿puede distinguir la inspección abierta de la encapsulación cerrada? El modo closed oculta la referencia a shadowRoot; no es un límite de seguridad.

Quinto, ¿puede prevenir el doble renderizado, listeners duplicados y eventos perdidos durante la hidratación o el streaming?

Preguntas para aclarar primero

  • ¿Qué navegadores y modos de respuesta renderizados en el servidor son compatibles?
  • ¿El host se convierte en un custom element de inmediato o puede actualizarse (upgrade) más tarde?
  • ¿Qué elementos secundarios van en slots y los consumidores deben aplicarles estilos con ::slotted?
  • ¿Se requiere el modo open para pruebas e integración, o el modo closed es una restricción del producto?
  • ¿La página transmite componentes anidados por streaming o envía la raíz completa en una sola respuesta?
  • ¿Cuál es el fallback cuando el parsing declarativo no está disponible?

Estructura de respuesta de 30 segundos

“Renderizaría en el servidor el componente con un shadow root declarativo y un fallback semántico en el light-DOM, y luego dejaría que el upgrade del custom element adjunte el comportamiento sin recrear la raíz ya analizada. Usaría slots para el contenido público, mantendría los estilos dentro de la raíz, elegiría el modo open o closed como una decisión de integración en lugar de un control de seguridad, y detectaría las características con shadowRootMode. La hidratación es idempotente, los eventos se delegan o se vinculan una sola vez, y las pruebas cubren navegadores no compatibles, orden de streaming, slots, accesibilidad y el momento del upgrade.”

Respuesta paso a paso

Paso 1: Renderizar una raíz semántica

El servidor emite un host con una plantilla declarativa. Mantenga el texto y los controles significativos para que la respuesta siga siendo útil antes de JavaScript. Use open cuando las herramientas o la integración necesiten inspección; use closed solo cuando el contrato del componente oculte intencionalmente la referencia.

html
<product-card>
  <template shadowrootmode="open">
    <style>:host { display: block }</style>
    <article><slot name="title"></slot><button>Buy</button></article>
  </template>
  <span slot="title">Keyboard</span>
</product-card>

La asignación de slots es parte del contrato público. No duplique el título dentro del shadow tree y del contenido de fallback a menos que también controle la semántica de accesibilidad.

Paso 2: Definir el contrato de upgrade

Cuando se define la clase del custom element, su ciclo de vida debe detectar un shadow root existente en lugar de llamar a attachShadow nuevamente. Inicialice el estado una vez, vincule los listeners una vez y deje los nodos provistos por el servidor en su lugar. Si el navegador no creó una raíz, el elemento puede crear una a partir de una plantilla o renderizar un fallback compatible en el light-DOM.

Paso 3: Detectar características y usar fallback

Compruebe la compatibilidad con una pequeña prueba del parser o la propiedad de plantilla correspondiente antes de confiar en el comportamiento declarativo. Los navegadores no compatibles aún deben recibir contenido semántico en el light-DOM; una mejora del lado del cliente puede adjuntar un shadow root más tarde, pero no debe ocultar contenido durante la transición.

Paso 4: Preservar slots y estilos

Los slots proyectan elementos secundarios de light-DOM en el shadow tree. Documente los slots con nombre, el comportamiento del slot predeterminado y los límites de estilo como ::slotted. Mantenga los estilos del componente en la raíz y exponga propiedades personalizadas o parts deliberadas en lugar de depender de selectores que no pueden cruzar el límite.

Paso 5: Manejar streaming y anidamiento

El streaming puede entregar un host antes que los elementos secundarios en slots anidados o antes de la definición del custom element. Trate el orden de parsing como un estado esperado: el slot debe actualizarse cuando lleguen los elementos secundarios, y el upgrade debe ser seguro antes o después de que se complete el stream. Evite reemplazar el host con un segundo árbol.

Paso 6: Hidratar el comportamiento sin trabajo duplicado

Adjunte los manejadores de eventos una sola vez, preferiblemente a través de un delegado a nivel de raíz para tarjetas repetidas. Marque la inicialización en un campo privado o weak map, no en un atributo público que los consumidores puedan sobrescribir. Preserve el estado del formulario y el foco cuando se adjunte el comportamiento.

Paso 7: Probar límites de navegadores y de accesibilidad

Pruebe parsers compatibles y no compatibles, modos open y closed, una frente a múltiples raíces declarativas, slots que llegan tarde, upgrade de custom elements antes y después del parsing, foco de teclado, etiquetas, envío de formularios e hidratación después de una interrupción de red. Asegúrese de que haya un solo árbol de control interactivo y que no haya eventos duplicados.

Respuesta modelo

“Transmitiría por streaming un host semántico más un shadow root declarativo, usaría slots con nombre para el contenido público y mantendría los estilos dentro de la raíz. El upgrade del custom element primero verifica si ya existe una raíz, de modo que la hidratación mejore el marcado del servidor en lugar de reemplazarlo. El modo open admite inspección; el modo closed solo oculta la referencia y no es un límite de seguridad.

Detectaría la compatibilidad del parser y conservaría el contenido de fallback en el light-DOM. El streaming y los upgrades tardíos son estados normales, por lo que la proyección de slots y la inicialización deben ser idempotentes. Las pruebas cubren navegadores no compatibles, raíces anidadas, slots tardíos, foco, formularios, accesibilidad y prevención de eventos duplicados.”

Errores comunes

  • Llamar a attachShadow durante cada upgrade → se pierden las raíces existentes o el estado → reutilice la raíz analizada.
  • Tratar el modo closed como seguridad → los llamadores aún pueden interactuar a través del comportamiento expuesto → documéntelo solo como encapsulación.
  • Duplicar el contenido de fallback y del shadow → los lectores de pantalla pueden anunciarlo dos veces → defina una sola fuente accesible.
  • Asumir que los slots llegan antes de que finalice el parsing → los elementos secundarios en streaming desaparecen del modelo mental → pruebe la proyección tardía.
  • Dar estilo al contenido en slots con selectores internos → las reglas no cruzan el límite → utilice ::slotted, parts o propiedades personalizadas.
  • Vincular listeners en cada renderizado → los clics se disparan varias veces → haga que la hidratación sea idempotente.
  • Descartar el fallback de light-DOM → los navegadores no compatibles muestran tarjetas en blanco → mantenga el marcado semántico del servidor.

Preguntas de seguimiento

Pregunta de seguimiento 1: ¿Siempre se adjunta un shadow root declarativo?

No. Depende del soporte del parser y de un modo válido. Un navegador no compatible deja la plantilla como contenido ordinario, por lo que se requiere un fallback o un upgrade del cliente.

Pregunta de seguimiento 2: ¿Por qué solo una raíz por host?

El parser adjunta el primer shadow root declarativo para ese host; las plantillas posteriores permanecen disponibles para un manejo intencional en lugar de crear raíces en conflicto.

Pregunta de seguimiento 3: ¿Cuándo elegir el modo open?

Elija el modo open cuando las pruebas, la integración o las extensiones controladas necesiten la referencia a la raíz. No otorga ni elimina privilegios de seguridad.

Pregunta de seguimiento 4: ¿Cómo funcionan los slots durante el streaming?

El host puede existir antes que los elementos secundarios en slots; una vez que llegan los elementos secundarios, la asignación de slots se actualiza. Las pruebas deben cubrir ambos órdenes de llegada.

Pregunta de seguimiento 5: ¿Cómo se evita el desajuste (mismatch) de hidratación?

Mantenga estables los contratos de servidor y cliente, reutilice los nodos existentes y haga que la inicialización sea idempotente en lugar de renderizar un segundo árbol.

Pregunta de seguimiento 6: ¿Cómo se prueban las raíces closed?

Pruebe el comportamiento visible para el usuario, el foco, los eventos y la accesibilidad a través del contrato público; no dependa de leer shadowRoot directamente.

Fuentes públicas

Preguntas relacionadas