Cuando el source map falla: los siete modos de fallo
Diagnóstico de mapas rotos, desplazados, incompletos o ausentes, con la comprobación concreta que identifica cada caso.
Un source map roto produce una experiencia de depuración peculiarmente frustrante, porque no falla del todo: falla un poco. El fichero se abre, el código se ve, los breakpoints se ponen, y todo está desplazado dos líneas, o el panel de scope enseña nombres que no existen en el código que estás mirando, o el editor está en blanco. Como el fallo es parcial, la reacción habitual es dudar del propio código, y eso puede durar bastante. Hay siete modos de fallo, cada uno con su síntoma característico y su comprobación.
- Identificar el modo de fallo de un source map a partir de su síntoma.
- Comprobar si el problema está en el mapa o en el código con una verificación decisiva.
- Diagnosticar el desplazamiento de posiciones y sus causas habituales.
- Configurar la generación para evitar los cuatro fallos que dependen del proyecto.
La comprobación decisiva
Antes de los siete casos, la maniobra que separa un problema de mapa de un problema de código, y que hay que hacer siempre primero.
Desactiva los source maps de JavaScript en los ajustes de las DevTools. El panel pasa a mostrar el código realmente ejecutado, sin ninguna traducción.
Con eso, tres preguntas se responden de golgo: si el código que se ejecuta es el que esperabas, si el breakpoint cae donde debe, y si el comportamiento tiene sentido. Si todo cuadra con los mapas desactivados y no cuadra con ellos activados, el problema es el mapa y no hay nada que buscar en tu lógica.
Y hay una segunda comprobación complementaria que da la respuesta desde el otro lado.
// El codigo real de la funcion, sin pasar por ningun mapa
console.log(String(miFuncion));
Si lo que devuelve no se parece a lo que ves en el editor, el mapa está mintiendo.
Los siete modos
Uno: no hay mapa. El editor muestra código minificado. La causa es que falta el comentario sourceMappingURL, o que la cabecera SourceMap no se envía, o que el fichero de mapa devuelve un 404. La comprobación es mirar el panel de red filtrando por la extensión de mapa: si la petición no está, el navegador no lo pidió; si está y devuelve 404, el mapa no se desplegó.
Dos: el mapa carga y el editor está vacío. Los nombres de tus ficheros aparecen en el árbol pero al abrirlos no hay nada, o hay un mensaje de que no se pudo cargar el contenido. La causa es que sourcesContent no está incrustado y el navegador intentó descargar los ficheros originales de las rutas de sources, sin éxito. En producción es lo normal, porque esas rutas no existen en el servidor. El arreglo es activar la incrustación del contenido en la configuración del empaquetador.
Tres: las posiciones están desplazadas. El breakpoint se pone en la línea 40 y se detiene en lo que corresponde a la 42. Las causas habituales son tres: un mapa generado para una versión anterior del fichero; una transformación aplicada después de generar el mapa, como un minificador que corre al final sin actualizar la correspondencia; o una cadena de transformaciones donde alguna herramienta no compone bien los mapas intermedios.
Cuatro: el mapa es de segunda generación mal compuesta. Cuando el código pasa por varias transformaciones —de TypeScript a JavaScript, luego empaquetado, luego minificado— cada paso genera su mapa, y el mapa final tiene que ser la composición de todos. Si una herramienta de la cadena ignora el mapa de entrada y genera el suyo contra el código que recibe, el resultado apunta al fichero intermedio y no al original. El síntoma es que ves código JavaScript transpilado en vez de tu TypeScript, o código con las importaciones ya reescritas.
Cinco: faltan los nombres. El código se ve bien pero el panel de scope muestra identificadores de una letra, y al pasar el cursor sobre una variable no aparece su nombre original. La causa es que names está vacío o que los segmentos no llevan el quinto campo. Suele ser una configuración de granularidad baja.
Seis: los breakpoints no se pueden poner en algunas líneas. Pulsas en el margen y el marcador salta a otro sitio o no aparece. La causa es que esa línea del original no tiene ninguna posición mapeada, porque la granularidad del mapa no llega o porque el código de esa línea se eliminó en la compilación.
Siete: el mapa corresponde a otro fichero. Ves código que no tiene nada que ver. Ocurre con cachés desalineadas: el navegador tiene cacheado un mapa antiguo y un fichero nuevo, o al revés. La comprobación es recargar con la caché desactivada.
El caso siete es más frecuente de lo que parece en desarrollo con recarga en caliente. Cuando el servidor de desarrollo sustituye módulos sin recargar la página, la correspondencia entre el código en memoria y los mapas descargados puede desalinearse. Una recarga completa lo resuelve, y si un mapa se comporta de forma inexplicable, es lo primero que hay que probar.
Verificar un mapa desde fuera
Con el decodificador de la lección anterior, se pueden comprobar los tres fallos más comunes de forma programática.
// Comprueba los problemas habituales de un mapa
async function revisarMapa(urlMapa) {
const r = await fetch(urlMapa);
if (!r.ok) return console.error('el mapa no se puede descargar:', r.status);
const m = await r.json();
const problemas = [];
if (m.version !== 3) problemas.push('version distinta de 3');
if (!m.sources?.length) problemas.push('sin fuentes');
if (!m.sourcesContent) problemas.push('falta sourcesContent entero');
else {
const huecos = m.sources.filter((_, i) => !m.sourcesContent[i]);
if (huecos.length) problemas.push(`sin contenido: ${huecos.length} de ${m.sources.length}`);
}
if (!m.names?.length) problemas.push('sin nombres originales');
if (!m.mappings) problemas.push('mappings vacio');
console.log(problemas.length ? problemas : 'el mapa tiene buena pinta');
return m;
}
Y para saber qué mapas se han cargado en la página actual, el panel de red con el filtro adecuado los muestra todos; también se pueden listar desde la consola.
console.table(
performance.getEntriesByType('resource')
.filter(r => r.name.endsWith('.map'))
.map(r => ({ mapa: r.name.split('/').pop(), kb: Math.round(r.transferSize / 1024) }))
);
Si esa tabla está vacía, ningún mapa se ha cargado, y todo lo que ves en el editor es código real.
Cuatro decisiones que evitan la mitad de los fallos
Incrusta sourcesContent. Evita el modo dos y hace el mapa autosuficiente.
Comprueba la composición de la cadena. Si tu compilación tiene varios pasos, abre un mapa final y mira si sources contiene rutas de tus ficheros originales o de artefactos intermedios. Es un vistazo y detecta el modo cuatro.
Despliega los mapas o sírvelos con control de acceso. Un mapa que devuelve 404 en producción hace que cualquier informe de error llegue con la pila minificada. Si no quieres publicarlos, la cabecera SourceMap apuntando a una ruta protegida es la vía, o subirlos directamente a tu sistema de seguimiento de errores.
Usa granularidad alta en producción. El coste es tiempo de compilación una vez por despliegue; el beneficio es cada informe de error legible.
Merece la pena volver a la distinción del primer nivel entre hechos reportados e interpretaciones del frontend, porque el source map es el caso más puro de interpretación que existe en las DevTools y el que más confusión produce. El fichero que ves en el editor cuando depuras código transpilado no existe en ninguna parte: no está en el servidor, no está en la memoria del navegador, y no es lo que el motor de JavaScript está ejecutando. Es una reconstrucción que el frontend de las DevTools hace aplicando una tabla de correspondencias sobre el código real, y como toda reconstrucción, puede estar mal sin que nada esté roto en el motor ni en tu aplicación. La consecuencia práctica es una regla de escalada que ahorra horas: cuando el depurador te muestre algo que contradice tu modelo del código, la hipótesis por defecto no debe ser que el motor se ha vuelto loco, sino que la traducción falla. Y la verificación es siempre la misma y siempre decisiva: quita la capa de traducción y mira lo que hay debajo. Desactiva los mapas, mira el código real, comprueba dónde cae de verdad el breakpoint, evalúa String(funcion) para ver el cuerpo que se está ejecutando. En cuanto lo hagas, una de dos: o el código real explica el comportamiento, y entonces el problema era el mapa y ya sabes qué arreglar en tu compilación; o el código real también contradice tu modelo, y entonces el bug es tuyo y llevas quince minutos culpando a la herramienta equivocada. Las dos respuestas son valiosas y las dos llegan en treinta segundos. Es exactamente el mismo movimiento que bajar al valor computado cuando el panel de estilos no cuadra, o mirar la respuesta cruda cuando el objeto parseado no tiene sentido: cuando una capa de interpretación te confunde, baja un nivel hasta el hecho.