SVGO y los plugins que rompen cosas
Qué hace el preset por defecto, los seis plugins que hay que desactivar y por qué, los dos que hay que activar, y una configuración de partida que no rompe nada.
SVGO es la herramienta estándar y su configuración por defecto rompe tres cosas que casi todo proyecto necesita: el escalado responsivo, las referencias por identificador y el texto alternativo. No es un fallo de la herramienta, es que su preset se diseñó para minimizar iconos autónomos, y tu SVG casi nunca es eso. Saber exactamente qué hace cada plugin problemático convierte una caja negra en una configuración razonada.
- Explicar qué hace el preset por defecto y cuál es su criterio.
- Desactivar los seis plugins que rompen casos de uso frecuentes, sabiendo por qué.
- Activar los dos que el preset no incluye y que casi todo el mundo necesita.
- Escribir una configuración de partida y verificar su efecto.
El preset por defecto
SVGO se ejecuta con un conjunto de plugins agrupados en preset-default. La mayoría son seguros: eliminan comentarios, el prólogo XML, metadatos, atributos por defecto, elementos vacíos, y colapsan grupos sin función.
Su criterio implícito es: el SVG es un icono autónomo cuyo aspecto visual es lo único que importa. Bajo ese supuesto, todo lo que no afecte a los píxeles se puede eliminar. Y ese supuesto es falso en cuanto el SVG se usa en un sistema con CSS, JavaScript o accesibilidad.
Los seis que hay que desactivar
removeViewBox. Elimina el viewBox cuando coincide con width y height. El razonamiento: si el SVG mide 24 por 24 y el viewBox es 0 0 24 24, es redundante.
No lo es. Sin viewBox, el SVG deja de escalar: al ponerle width: 2rem desde CSS, el contenido no se ajusta al nuevo tamaño y aparece recortado o rodeado de espacio. Es el fallo número uno de SVGO y afecta a cualquier icono que se dimensione desde CSS, que son todos.
Desactívalo siempre.
cleanupIds. Minifica los identificadores (de clip0_1_43 a a) y elimina los que nadie referencia dentro del fichero.
Rompe cuatro cosas: las referencias desde fuera del fichero (use href="sprite.svg#i-x"), las referencias desde JavaScript (getElementById), las de aria-labelledby, y el ancla de una animación que apunte a un identificador concreto. La eliminación de los no referenciados es especialmente traicionera en un sprite dividido en varios ficheros, como se explicó en el nivel 19.
Desactívalo si tus identificadores son públicos, que es el caso en cualquier sprite o SVG accesible.
removeTitle. Elimina los elementos title. Destruye el nombre accesible. La razón de que esté en el preset es que las herramientas de diseño generan title con el nombre de la capa, que es basura. Pero no distingue entre esos y los que has puesto tú.
Desactívalo si usas title para accesibilidad, y limpia los generados por la herramienta en origen.
removeDesc. Igual, con las descripciones. Por defecto solo elimina las que reconoce como generadas por el editor, lo cual es más prudente, pero en modo agresivo elimina todas.
convertShapeToPath. Convierte rect, line, polyline y polygon en path. Ahorra bytes y rompe tres cosas: los selectores CSS que apuntan a esos tipos, la animación de las propiedades de geometría (width, height), y la legibilidad del fichero.
Desactívalo si tu CSS o tu JavaScript apuntan a tipos de elemento.
mergePaths. Fusiona rutas adyacentes con el mismo estilo en una sola. Ahorra bastante en iconos complejos y rompe: la posibilidad de estilar o animar cada ruta por separado, y en algunos casos el renderizado, porque unir subrutas cambia el resultado de fill-rule cuando se solapan.
Desactívalo si el SVG tiene partes que se estilan o animan por separado.
Los dos que hay que activar
Ninguno de los dos está en el preset y casi todo el mundo los necesita.
prefixIds. Prefija los identificadores con el nombre del fichero, lo que elimina de raíz la colisión entre SVG inlinados. Es la solución del problema del nivel 20.
removeDimensions. Elimina width y height del elemento raíz conservando el viewBox. Es lo contrario de removeViewBox y es lo que quieres para un icono que se dimensiona desde CSS: sin dimensiones fijas, el SVG toma el tamaño de su contenedor.
Cuidado con este último: si el CSS falla o el SVG se usa fuera de tu página, un SVG sin dimensiones toma el tamaño por defecto de 300 por 150. Actívalo solo si controlas el uso.
Una configuración de partida
// svgo.config.js
export default {
multipass: true,
js2svg: { indent: 2, pretty: false },
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false,
cleanupIds: false,
removeTitle: false,
removeDesc: false,
convertShapeToPath: false,
mergePaths: false,
},
},
},
{ name: 'prefixIds', params: { delim: '-' } },
{ name: 'removeDimensions' },
{
name: 'removeAttrs',
params: { attrs: '(data-name|class)' },
},
],
};
multipass vuelve a pasar hasta que el fichero no cambie, porque algunas optimizaciones habilitan otras. Cuesta tiempo de compilación y ahorra bytes reales.
removeAttrs con esa expresión elimina los atributos data-name de Figma y las clases de Illustrator. Ajústalo a lo que produzca tu herramienta.
Verificar el efecto
Optimizar sin verificar es apostar. Tres comprobaciones que hay que automatizar:
Uno: el fichero sigue siendo un SVG válido y se parsea. Un parseo con DOMParser y comprobar que no hay parsererror.
Dos: el viewBox sigue ahí. Una expresión regular basta.
Tres: la comparación visual. Rasterizar el original y el optimizado a un tamaño concreto y comparar píxel a píxel con una tolerancia. Es la única prueba que detecta una optimización que cambia el dibujo.
// Comprobacion minima en el build
function verificar(original, optimizado, nombre) {
if (!/viewBox=/.test(optimizado)) {
throw new Error(`${nombre}: se ha perdido el viewBox`);
}
const idsOrig = [...original.matchAll(/id="([^"]+)"/g)].map(m => m[1]);
const idsOpt = [...optimizado.matchAll(/id="([^"]+)"/g)].map(m => m[1]);
if (idsOrig.length !== idsOpt.length) {
console.warn(`${nombre}: identificadores ${idsOrig.length} -> ${idsOpt.length}`);
}
}
SVGO informa de reducciones del 40 o el 50 por ciento sobre el tamaño en bruto. Ese número no es el que llega al usuario, porque entre el fichero y el navegador hay un compresor.
La compresión de propósito general funciona eliminando redundancia, y un SVG sin optimizar está lleno de redundancia: los atributos repetidos, la indentación, los nombres de capa con el mismo prefijo, el prólogo XML. Todo eso comprime a casi nada. Lo que SVGO elimina es precisamente lo que mejor comprimía.
En medidas reales sobre conjuntos de iconos, una reducción del 45 por ciento en bruto se convierte con frecuencia en un 10 o un 15 por ciento tras Brotli. Sigue siendo una ganancia, y es un orden de magnitud menor de lo que sugiere el informe.
La excepción son los decimales. Reducir la precisión de seis decimales a dos no elimina redundancia: elimina entropía, es decir, información real. Esos bytes no los recupera ningún compresor, y por eso la precisión decimal es la única optimización cuya ganancia sobrevive intacta a la compresión. Es el tema de la lección siguiente, y es donde está el dinero de verdad.
La consecuencia práctica para decidir la configuración: si un plugin te preocupa por lo que puede romper, y su ganancia es estructural (menos atributos, menos nodos), desactívalo sin remordimiento: después de Brotli casi no aportaba. Si la ganancia es de precisión numérica, piénsalo dos veces, porque ahí sí hay bytes de verdad.
Y una regla de higiene: mide siempre después de comprimir. Un informe de optimización que no incluye el tamaño tras Brotli no está midiendo lo que el usuario descarga.
Optimiza tu conjunto de iconos con el preset por defecto y con la configuración de este artículo, y mide los cuatro tamaños: en bruto y tras Brotli, para cada configuración. Después comprueba cuántos de los iconos del preset por defecto han perdido el viewBox. Los dos números juntos justifican la configuración ante cualquiera.