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>
This commit is contained in:
281
Assets/Vendor/create-icons-subset.py
vendored
Normal file
281
Assets/Vendor/create-icons-subset.py
vendored
Normal file
@@ -0,0 +1,281 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user