Files
roi-theme/Assets/Vendor/create-icons-subset.py
FrankZamora 2f19a7c077 Fase 4.1: Bootstrap Icons subset (94% reduccion)
Optimizacion PageSpeed:
- Original: 211 KB (2050 iconos)
- Subset: 13 KB (104 iconos usados)
- Ahorro: 198 KB (94% reduccion)

Cambios:
- Creado script create-icons-subset.py para generar subsets
- Generado bootstrap-icons-subset.min.css (4.5 KB)
- Generado bootstrap-icons-subset.woff2 (8.7 KB)
- Agregado font-display:swap (elimina bloqueo de 420ms)
- Actualizado enqueue-scripts.php para usar subset

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-27 14:34:52 -06:00

282 lines
8.6 KiB
Python

#!/usr/bin/env python3
"""
Bootstrap Icons Subset Generator
Genera CSS y fuente optimizados solo con los iconos usados en roi-theme
"""
import re
import subprocess
import os
# Directorio base
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
# Iconos usados en roi-theme (extraidos con grep)
USED_ICONS = [
"bi-arrow-counterclockwise",
"bi-arrow-down-circle",
"bi-arrow-right",
"bi-arrows-angle-expand",
"bi-arrows-expand",
"bi-arrows-fullscreen",
"bi-arrows-move",
"bi-arrows-vertical",
"bi-arrow-up",
"bi-aspect-ratio",
"bi-badge-ad",
"bi-bar-chart",
"bi-bootstrap",
"bi-bounding-box",
"bi-box-arrow-up-right",
"bi-calendar3",
"bi-calendar-check",
"bi-card-image",
"bi-card-text",
"bi-c-circle",
"bi-chat-dots",
"bi-chat-dots-fill",
"bi-chat-quote",
"bi-chat-text",
"bi-check-circle",
"bi-chevron-down",
"bi-clock",
"bi-code",
"bi-code-slash",
"bi-cursor",
"bi-display",
"bi-envelope",
"bi-envelope-fill",
"bi-envelope-paper",
"bi-exclamation-octagon",
"bi-exclamation-triangle",
"bi-eye",
"bi-facebook",
"bi-file-earmark-text",
"bi-file-text",
"bi-filetype-css",
"bi-filetype-js",
"bi-folder-fill",
"bi-fonts",
"bi-gear",
"bi-geo-alt-fill",
"bi-globe",
"bi-graph-up",
"bi-grid",
"bi-grid-3x3-gap",
"bi-hand-index",
"bi-hourglass",
"bi-hourglass-split",
"bi-image",
"bi-info-circle",
"bi-input-cursor",
"bi-input-cursor-text",
"bi-instagram",
"bi-key",
"bi-layout-sidebar",
"bi-layout-text-window-reverse",
"bi-lightning",
"bi-lightning-charge-fill",
"bi-link",
"bi-link-45deg",
"bi-linkedin",
"bi-list",
"bi-list-nested",
"bi-list-ol",
"bi-list-ul",
"bi-magic",
"bi-megaphone",
"bi-megaphone-fill",
"bi-menu-button-wide",
"bi-mouse",
"bi-palette",
"bi-person",
"bi-person-lines-fill",
"bi-phone",
"bi-pin",
"bi-power",
"bi-send-fill",
"bi-share",
"bi-shield-check",
"bi-shield-lock",
"bi-shield-x",
"bi-slash-circle",
"bi-sliders",
"bi-square",
"bi-star",
"bi-star-fill",
"bi-stars",
"bi-tablet",
"bi-tag",
"bi-tags",
"bi-telephone-fill",
"bi-text-center",
"bi-text-paragraph",
"bi-three-dots",
"bi-toggle-on",
"bi-twitter-x",
"bi-type",
"bi-whatsapp",
"bi-x-circle",
]
def read_original_css():
"""Lee el CSS original de Bootstrap Icons"""
css_path = os.path.join(BASE_DIR, "bootstrap-icons.min.css")
with open(css_path, "r", encoding="utf-8") as f:
return f.read()
def extract_unicode_codes(css_content, icons):
"""Extrae los codigos unicode de los iconos usados"""
codes = {}
for icon in icons:
# Buscar .bi-nombre::before{content:"\XXXX"}
pattern = rf'\.{re.escape(icon)}::before\{{content:"\\([a-f0-9]+)"\}}'
match = re.search(pattern, css_content, re.IGNORECASE)
if match:
codes[icon] = match.group(1)
else:
print(f"WARNING: No se encontro codigo para {icon}")
return codes
def generate_subset_css(codes):
"""Genera el CSS subset con solo los iconos usados"""
css_lines = []
# Header
css_lines.append("/*!")
css_lines.append(" * Bootstrap Icons Subset - ROI Theme")
css_lines.append(f" * {len(codes)} iconos de 2050 originales ({len(codes)/2050*100:.1f}%)")
css_lines.append(" * Generado automaticamente - NO EDITAR")
css_lines.append(" */")
# @font-face con font-display: swap
css_lines.append('@font-face{')
css_lines.append(' font-display:swap;')
css_lines.append(' font-family:bootstrap-icons;')
css_lines.append(' src:url("fonts/bootstrap-icons-subset.woff2") format("woff2"),')
css_lines.append(' url("fonts/bootstrap-icons-subset.woff") format("woff")')
css_lines.append('}')
# Estilos base para .bi
css_lines.append('.bi::before,[class*=" bi-"]::before,[class^=bi-]::before{')
css_lines.append(' display:inline-block;')
css_lines.append(' font-family:bootstrap-icons!important;')
css_lines.append(' font-style:normal;')
css_lines.append(' font-weight:400!important;')
css_lines.append(' font-variant:normal;')
css_lines.append(' text-transform:none;')
css_lines.append(' line-height:1;')
css_lines.append(' vertical-align:-.125em;')
css_lines.append(' -webkit-font-smoothing:antialiased;')
css_lines.append(' -moz-osx-font-smoothing:grayscale')
css_lines.append('}')
# Reglas para cada icono
for icon, code in sorted(codes.items()):
css_lines.append(f'.{icon}::before{{content:"\\{code}"}}')
return '\n'.join(css_lines)
def generate_minified_css(codes):
"""Genera CSS minificado"""
parts = []
# @font-face
parts.append('@font-face{font-display:swap;font-family:bootstrap-icons;src:url("fonts/bootstrap-icons-subset.woff2") format("woff2"),url("fonts/bootstrap-icons-subset.woff") format("woff")}')
# Estilos base
parts.append('.bi::before,[class*=" bi-"]::before,[class^=bi-]::before{display:inline-block;font-family:bootstrap-icons!important;font-style:normal;font-weight:400!important;font-variant:normal;text-transform:none;line-height:1;vertical-align:-.125em;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}')
# Iconos
icon_rules = [f'.{icon}::before{{content:"\\{code}"}}' for icon, code in sorted(codes.items())]
parts.append(''.join(icon_rules))
return ''.join(parts)
def create_font_subset(codes):
"""Crea subset de la fuente usando pyftsubset"""
# Crear archivo con unicodes
unicodes = ','.join([f'U+{code}' for code in codes.values()])
# Paths
woff2_src = os.path.join(BASE_DIR, "fonts", "bootstrap-icons.woff2")
woff2_dst = os.path.join(BASE_DIR, "fonts", "bootstrap-icons-subset.woff2")
woff_src = os.path.join(BASE_DIR, "fonts", "bootstrap-icons.woff")
woff_dst = os.path.join(BASE_DIR, "fonts", "bootstrap-icons-subset.woff")
# Crear subset woff2
print(f"Creando subset woff2 con {len(codes)} glyphs...")
cmd_woff2 = [
"pyftsubset",
woff2_src,
f"--unicodes={unicodes}",
f"--output-file={woff2_dst}",
"--flavor=woff2"
]
subprocess.run(cmd_woff2, check=True)
# Crear subset woff
print(f"Creando subset woff con {len(codes)} glyphs...")
cmd_woff = [
"pyftsubset",
woff_src,
f"--unicodes={unicodes}",
f"--output-file={woff_dst}",
"--flavor=woff"
]
subprocess.run(cmd_woff, check=True)
# Mostrar tamanos
orig_size = os.path.getsize(woff2_src) / 1024
new_size = os.path.getsize(woff2_dst) / 1024
print(f"woff2: {orig_size:.1f} KB -> {new_size:.1f} KB ({(1-new_size/orig_size)*100:.1f}% reduccion)")
def main():
print("=== Bootstrap Icons Subset Generator ===\n")
# 1. Leer CSS original
print("1. Leyendo CSS original...")
css_content = read_original_css()
# 2. Extraer codigos unicode
print(f"2. Extrayendo codigos de {len(USED_ICONS)} iconos...")
codes = extract_unicode_codes(css_content, USED_ICONS)
print(f" Encontrados: {len(codes)} iconos")
# 3. Generar CSS subset
print("3. Generando CSS subset...")
subset_css = generate_subset_css(codes)
css_path = os.path.join(BASE_DIR, "bootstrap-icons-subset.css")
with open(css_path, "w", encoding="utf-8") as f:
f.write(subset_css)
print(f" Guardado: bootstrap-icons-subset.css")
# 4. Generar CSS minificado
print("4. Generando CSS minificado...")
min_css = generate_minified_css(codes)
min_path = os.path.join(BASE_DIR, "bootstrap-icons-subset.min.css")
with open(min_path, "w", encoding="utf-8") as f:
f.write(min_css)
orig_css_size = os.path.getsize(os.path.join(BASE_DIR, "bootstrap-icons.min.css")) / 1024
new_css_size = os.path.getsize(min_path) / 1024
print(f" CSS: {orig_css_size:.1f} KB -> {new_css_size:.1f} KB ({(1-new_css_size/orig_css_size)*100:.1f}% reduccion)")
# 5. Crear font subset
print("5. Creando font subset...")
try:
create_font_subset(codes)
except Exception as e:
print(f" ERROR: {e}")
print(" Intenta ejecutar: pip install fonttools brotli")
return
print("\n=== COMPLETADO ===")
print("Archivos generados:")
print(" - bootstrap-icons-subset.css (legible)")
print(" - bootstrap-icons-subset.min.css (minificado)")
print(" - fonts/bootstrap-icons-subset.woff2")
print(" - fonts/bootstrap-icons-subset.woff")
if __name__ == "__main__":
main()