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.
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.jsexport 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 buildfunction 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}`); }}