Ir al contenido
learn

Diario

  • React 19.3.0, Vite 8.3.0, TypeScript 7.0.2, Vitest 5.0.0, Testing Library y oxlint fijados en el propio package.json del curso y en su archivo de bloqueo
  • check.sh: create-vite, el servidor de desarrollo, una actualización HMR, tsc, vite build, oxlint, cada fragmento de error y cada prueba, comparados con expected/
  • CI en tres sistemas operativos: en verde desde el 2026-09-16 (ver más abajo)
  • Lección 1: un proyecto Vite
  • Lección 2: componentes y JSX
  • Lección 3: estado y renderizado
  • Lección 4: eventos y formularios
  • Lección 5: efectos

React 19.3.0, Vite 8.3.0, TypeScript 7.0.2 y Vitest 5.0.0, fijados en el archivo de bloqueo del curso. Las diez primeras filas son de la cadena de herramientas; las dos últimas, de GuitarAlchemist/ga en 8cc8c5a, y, como dice la entrada fechada, nada de eso se ha reportado a GA.

No hay tabla de experimentos: cada entrada es un descubrimiento escrito después. Las tres notas por verificar de este diario nombran lo que no se ha medido; ninguna predice lo que mostrará la medición.

Esperado Lo que pasa Dónde Medición Estado
Vite quita los tipos de TypeScript con esbuild Vite 8 empaqueta con Rolldown y transforma con Oxc; @vitejs/plugin-react 6 ya no depende de Babel, y el Fast Refresh también lo hace Oxc Vite 8.3.0, @vitejs/plugin-react 6.1.1 Leído en los paquetes instalados Por diseño, cadena de herramientas cambiada. La frase del curso de TypeScript sobre esbuild describe GuitarAlchemist/ga, que está en Vite 5, y sigue siendo cierta allí 2026-09-15
El servidor de desarrollo imprime el mismo aviso tanto si stdout es un terminal como si no Con un stdout que no es TTY, Vite omite press h + enter to show help, y un script que espera esa línea espera para siempre Vite 8.3.0, scripts/dev-start.mjs El primer script nunca vio la línea y dejó un servidor corriendo en el puerto 5199 Reproducido; el script espera ahora use --host to expose 2026-09-15
server.close() en un servidor creado con la API de JS se resuelve tras la última petición En Windows nunca termina: la primera petición de módulo arrancó el optimizador de dependencias y lo dejó en marcha Vite 8.3.0, Node.js 24.21.0, Windows, scripts/dev-module.mjs Detected unsettled top-level await, y el proceso sale con el código 13 Reproducido, no reportado; waitForRequestsIdle() antes de close() lo resuelve. Linux y macOS sin comprobar, tracker de Vite sin buscar 2026-09-15
--base /learn/react-vite/ llega a Vite tal como se escribió Git Bash reescribe como ruta de Windows cualquier argumento que empiece por / Git Bash (MSYS) en Windows, check.sh Las páginas construidas pedían /Program Files/Git/learn/react-vite/assets/… Por diseño en MSYS; MSYS_NO_PATHCONV=1 y rutas de módulos sin barra inicial lo resuelven 2026-09-15
Testing Library desmonta lo que un test renderizó, tras cada test Solo si el framework de tests expone un afterEach global; Vitest no lo hace sin globals: true @testing-library/react 16.3.3, Vitest 5.0.0 El DOM de un test se quedaba en el siguiente, y las consultas encontraban dos formularios Por diseño, y documentado; setup.ts llama a cleanup en afterEach 2026-09-15
Un reporter de tests atribuye la salida de consola al test que la produjo El reporter por defecto de Vitest agrupa la salida de consola por momento, no por test, y la agrupación cambió entre ejecuciones Vitest 5.0.0 Dos ejecuciones del mismo archivo agruparon los mismos logs de forma distinta Reproducido; un pequeño reporter propio guarda cada log con su test, y check.sh lanza un archivo por ejecución de Vitest 2026-09-15
Vitest aísla los tests, así que el estado a nivel de módulo se reinicia entre ellos Aísla archivos de test; un contador a nivel de módulo siguió contando de un test al siguiente Vitest 5.0.0, primer test de StrictMode de la lección 3 El contador se conservó Por diseño; el test pasa ahora el array modificado como prop 2026-09-15
FormEvent y FormEventHandler son los tipos de los manejadores de formularios de React @types/react 19.3.0 marca ambos como @deprecated y remite a ChangeEvent, InputEvent y SubmitEvent @types/react 19.3.0 El comentario de obsolescencia dice “FormEvent doesn’t actually exist”; ChangeEvent recibe ahora dos parámetros de tipo Por diseño; la lección 4 usa SubmitEvent<HTMLFormElement> 2026-09-15
El tipo de retorno de un componente es ReactNode Los tipos de la 19.3 aceptan componentes asíncronos, para los Server Components TypeScript 7.0.2, @types/react 19.3.0 tsc imprime Promise<ReactNode> | ReactNode en su mensaje Por diseño 2026-09-15
El doble renderizado de StrictMode al montar es invisible en el DOM Un componente impuro se renderiza dos veces y el DOM conserva la salida del segundo renderizado React 19.3.0, StrictMode Played so far: G G Por diseño, y es lo que la lección 3 usa para hacer visible una impureza 2026-09-15
Un token que puede subir código puede subir un archivo de workflow GitHub exige además el scope workflow para crear un archivo bajo .github/workflows GitHub, el push de react-vite-examples.yml El primer push fue rechazado Sorteado subiendo el código sin el workflow. El workflow llegó a main más tarde: tres ejecuciones fallaron el 2026-09-16, y luego la ejecución 35098302366, en cd98475, pasó en Ubuntu, Windows y macOS 2026-09-15
Una fila desplegada sigue desplegada cuando la lista se filtra o se refresca DynamicPanel guarda las filas desplegadas por su posición en los datos filtrados y refrescados: cualquier reordenación despliega otra fila GuitarAlchemist/ga en 8cc8c5a, DynamicPanel.tsx, líneas 76-120 Reproducido en el ExpandableList.tsx del curso; las filas del panel son unknown[], sin campo de identidad conocido Corregido upstream por #733, fusionada el 2026-09-24: las filas se recuerdan por su identidad (id, si no key, si no el campo de título) 2026-09-15, 2026-09-24
Un hijo que informa de su valor al padre desde un efecto se estabiliza tras una vuelta NotesSelector llama a onNotesChange desde un efecto que depende de él, y ScaleSelector pasa en cada renderizado un manejador nuevo que guarda un array nuevo GuitarAlchemist/ga en 8cc8c5a, NotesSelector.tsx 15-18 y ScaleSelector.tsx 18-21 La reducción del curso, en la lección 4, acaba en Maximum update depth exceeded. Latente en GA: ninguna aplicación renderiza ScaleSelector en ese commit Reproducido en el propio ScaleSelector de GA por la prueba de #733, y corregido por ella 2026-09-15, 2026-09-24
  • npm view da React y React DOM 19.3.0, publicados el 9 de septiembre de 2026, Vite 8.3.0, @vitejs/plugin-react 6.1.1, Vitest 5.0.0, jsdom 30.0.1, @testing-library/react 16.3.3, @testing-library/dom 10.4.2, @testing-library/user-event 14.6.7, oxlint 1.83.0 y create-vite 9.2.1. El curso los fija exactamente en code/react-vite/package.json, y usa el Node.js 24.21.0 y el TypeScript 7.0.2 de los demás cursos del sitio.
  • La plantilla react-ts de create-vite 9.2.1 pide TypeScript ~6.0.2, no 7.0: un rango con ~ solo admite 6.0.x. El curso usa 7.0.2, y tsc -b verifica los dos proyectos de la plantilla sin ningún cambio. Su strict no aparece, porque es el valor por defecto desde TypeScript 6.0, y su herramienta de lint es oxlint en lugar de ESLint, con una opción --eslint para recuperar ESLint.
  • Vite 8 empaqueta con Rolldown y transforma con Oxc. La lección 1 del curso de TypeScript dice que Vite elimina los tipos con esbuild: es cierto para el Vite 5 de GA, y ya no para Vite 8. @vitejs/plugin-react 6 tampoco depende de Babel; Fast Refresh lo hace Oxc.
  • El archivo de bloqueo escrito en Windows lista los paquetes nativos de todas las plataformas como dependencias opcionales: @rolldown/binding-*, @oxlint/* y @typescript/typescript-*.

2026-09-15 — Capturar el servidor de desarrollo

Sección titulada «2026-09-15 — Capturar el servidor de desarrollo»
  • El servidor de desarrollo, los módulos que sirve y el mensaje HMR se capturan con scripts, no a mano: dev-start.mjs inicia vite y lo detiene cuando se imprime la URL, dev-module.mjs solicita una URL mediante la API JavaScript de Vite, y hmr.mjs se conecta al WebSocket de HMR, edita una copia de App.tsx e imprime los mensajes.
  • Cuando la salida no es un terminal, Vite no imprime la línea press h + enter to show help. Mi primer dev-start.mjs esperaba esa línea, nunca la vio, y dejó un servidor en marcha en el puerto 5199; ahora espera use --host to expose.
  • En Windows, server.close() llamado justo después de la primera solicitud de módulo nunca terminaba: Node.js imprimió “Detected unsettled top-level await” y salió con el código 13. La solicitud había arrancado el optimizador de dependencias, que seguía en marcha. Esperar server.environments.client.waitForRequestsIdle() antes de close() lo arregló. No he comprobado si Linux o macOS se comportan igual (por verificar), ni he buscado una issue existente de Vite.
  • Git Bash en Windows reescribe los argumentos que empiezan por /: vite build --base /learn/react-vite/ generó páginas que pedían /Program Files/Git/learn/react-vite/assets/…. check.sh define MSYS_NO_PATHCONV=1, y los scripts reciben las rutas de módulo sin su barra inicial.
  • Las salidas se normalizan con normalize.mjs: duraciones, el hash ?v= de las dependencias preempaquetadas, las marcas de tiempo de HMR y los source maps en línea. Los nombres de los archivos de producción, como index-BQ_Vbf-Q.js, son hashes del contenido y se mantienen tal cual.
  • Que Fast Refresh conserve el estado no está en check.sh, que solo ve el mensaje del WebSocket. Lo comprobé a mano en un navegador: tres clics en el contador, dos en el capo, y luego una edición del encabezado en App.tsx. El encabezado cambió, el contador y el capo conservaron sus valores, y una variable definida en window antes de la edición sobrevivió, así que la página no se había recargado. Para la lección 1, también serví con vite preview el build con un error de tipo e hice clic dos veces en su botón: Count is 01, y luego Count is 011.
  • React Testing Library desmonta los componentes renderizados después de cada prueba solo cuando el framework de pruebas expone un afterEach global. Vitest no lo hace, salvo con globals: true, así que el DOM de una prueba se quedaba en la siguiente y las consultas encontraban dos formularios. src/testing/setup.ts llama a cleanup en afterEach, como describe la documentación de Testing Library.
  • El reporter por defecto de Vitest imprime la salida de consola agrupada por momento, no por prueba, y la agrupación cambiaba entre ejecuciones. El reporter.mjs del curso recoge cada log con su prueba y los imprime en orden al final, y check.sh ejecuta un archivo de prueba por ejecución de Vitest.
  • Las advertencias de desarrollo de React, la clave que falta o el input controlado, van a console.error, y el reporter las imprime con un prefijo console.error:, así que las lecciones las citan a partir de las pruebas.
  • Un contador a nivel de módulo, en una primera versión de la prueba de StrictMode de la lección 3, seguía contando de una prueba a la siguiente: Vitest aísla los archivos de prueba, no las pruebas. El componente impuro recibe ahora como prop el array que modifica.
  • tsconfig.app.json tenía al principio la lib DOM.Iterable, para expandir un NodeList en una prueba. Con TypeScript 7.0.2, DOM solo lo acepta; la configuración del curso tiene ahora el ["ES2023", "DOM"] de la plantilla.
  • @types/react 19.3.0 marca FormEvent y FormEventHandler como @deprecated, con el comentario “FormEvent doesn’t actually exist”, y remite a ChangeEvent, InputEvent y SubmitEvent. ChangeEvent tiene dos parámetros de tipo, el current target y el target. La lección 4 usa SubmitEvent<HTMLFormElement> para onSubmit.
  • El tipo de retorno de un componente, en el mensaje de tsc, es Promise<ReactNode> | ReactNode: los tipos aceptan componentes asíncronos, para los Server Components.
  • En StrictMode, React 19.3 renderiza dos veces un componente impuro al montarlo, y el DOM muestra la salida del segundo render, “Played so far: G G”.
  • react-vite-examples.yml ejecuta npm ci y bash check.sh en Ubuntu, Windows y macOS con Node.js 24.21.0. check.sh tarda unos 50 segundos en mi máquina Windows.
  • El primer push fue rechazado: el token de GitHub usado para el push no tiene el scope workflow, que GitHub exige para crear un archivo bajo .github/workflows. El código se subió sin el workflow, y la ejecución en tres sistemas operativos sigue pendiente (por verificar: diferencias entre sistemas, como el orden de la lista de archivos de create-vite o el formato de salida de oxlint).
  • Actualización del 2026-09-22: el workflow llegó a main después. Tres ejecuciones fallaron el 2026-09-16, y luego la ejecución 35098302366, en cd98475 («lint with oxlint –format agent»), pasó en Ubuntu, Windows y macOS. En qué consistieron los tres fallos no queda registrado aquí.

2026-09-15 — Lo que el curso encontró en GuitarAlchemist/ga

Sección titulada «2026-09-15 — Lo que el curso encontró en GuitarAlchemist/ga»

En el commit 8cc8c5a, en ReactComponents/ga-react-components y Apps/ga-client; Apps/ga-dashboard es una aplicación Angular 21, fuera de este curso. Nada de esto se ha comunicado a GA.

  • Filas expandidas recordadas por índice. DynamicPanel.tsx guarda sus filas expandidas en un Set<number> de posiciones en los datos filtrados y consultados periódicamente. Tras un filtro o una consulta que cambia el orden, queda expandida otra fila. La lección 3 lo reproduce en ExpandableList.tsx. Los datos del panel son unknown[]; qué campo podría servir de identidad depende de las definiciones de los paneles (por verificar).
  • Un bucle de renders entre dos componentes. NotesSelector.tsx llama a onNotesChange desde un efecto que depende de él, y ScaleSelector.tsx pasa un manejador nuevo en cada render, que guarda un array nuevo. La reducción de la lección 4 termina con “Maximum update depth exceeded”. ScaleSelector también guarda en el estado scale, derivado de las notas. Se exporta desde components/index.ts, y ninguna aplicación de GA lo renderiza en este commit, así que el bucle está latente. No he montado el propio componente de GA (por verificar).
  • Una ventana deslizante con clave por índice. DemerzelCriticOverlay.tsx dibuja las diez últimas puntuaciones con key={i}, y sus barras tienen transition: height 0.3s ease: una vez llena la ventana, cada nueva puntuación cambia y anima todas las barras. El ejercicio 3 de la lección 2 muestra la reutilización de elementos; no he observado la animación (por verificar).
  • Las reglas React de oxlint. oxlint 1.83.0 con react/no-array-index-key, react/exhaustive-deps y typescript/no-explicit-any señala, en ga-react-components/src, 85 claves por índice, 34 listas de dependencias de efectos o callbacks que no coinciden con lo que lee la función, y 11 any explícitos; en ga-client/src, 6 claves por índice. react/jsx-key y react/rules-of-hooks no señalan nada. Los recuentos son resultados de lint, no errores: la mayoría de las claves por índice están en listas que nunca cambian.
  • Efectos sin limpieza. Una búsqueda aproximada de las llamadas a useEffect que inician un temporizador, un listener o una suscripción y no devuelven limpieza encontró una: ChatWidget.tsx inicia un setTimeout de 300 ms que no se cancela si el componente se desmonta antes. La búsqueda es textual, no un parser (por verificar antes de sacar conclusiones).
  • Opciones de escena. SceneOptions.tsx también notifica a su padre desde un efecto, una vez al montarse, con la regla de dependencias desactivada. Su fusión de los parámetros de URL y las opciones guardadas ya es objeto de la pull request abierta GuitarAlchemist/ga#683, del curso de JavaScript; este curso no la corrige otra vez.
  • Build y servidor de desarrollo. El script build de ga-react-components es vite build sin tsc, y su vite.config.ts, de 3005 líneas, añade middleware para decenas de endpoints /dev-data/ detrás de un túnel de Cloudflare (lección 1). El script dev de Apps/ga-client se niega a arrancar, y dice que se use ga-react-components en su lugar. El repositorio también versiona archivos generados: tsconfig.app.tsbuildinfo, playwright-report y test-results en ga-react-components.
  • #733, fusionada el 2026-09-24, corrige los dos hallazgos, cada uno con una prueba que falla con el código antiguo.
  • DynamicPanel recuerda una fila desplegada por su identidad: el id de la fila, si no su key, si no su campo de título, con un sufijo para las repeticiones; solo una fila sin nada de eso vuelve a su posición. DynamicPanel.test.tsx despliega Beta y luego filtra con los chips o envía un sondeo con las filas invertidas. Con el código antiguo, las dos pruebas fallan: se muestra el detalle de Alpha en lugar del de Beta.
  • El bucle de renderizado se reproduce en el propio componente de GA, no solo en la reducción del curso. La prueba monta el ScaleSelector real, con sus dos componentes de diagrama simulados, y hace que el manejador del padre lance una excepción tras 20 llamadas. Con el código antiguo lanza al montar y otra vez tras escribir «C E G»; sin ese límite, la prueba se quedaba colgada más de 120 s. Los dos componentes informan ahora desde un efecto que solo depende de las notas, y guardan el último manejador en una ref. Con la corrección, el montaje hace como mucho 2 llamadas y la escritura exactamente 1, con ['C','E','G'].
  • El bucle nunca se vio en la aplicación: ninguna página muestra este ScaleSelector, y ga-client tiene el suyo.
  • El bloqueo de server.close() en Linux y macOS, y si Vite tiene una issue para ello.
  • El ScaleSelector y el NotesSelector de GA montados con Material UI, y la animación de DemerzelCriticOverlay.
  • Un campo de identidad para las filas de DynamicPanel en las definiciones de paneles de GA.