226 lines
No EOL
8.1 KiB
Text
226 lines
No EOL
8.1 KiB
Text
#!/usr/bin/env nu
|
|
# HTML optimization utilities
|
|
|
|
# Minify HTML content
|
|
export def minify_html [html_content: string, config: record] {
|
|
let minified = ($html_content
|
|
# Remove HTML comments (but keep IE conditionals)
|
|
| str replace -ar '<!--(?!\s*\[if)[^>]*-->' ''
|
|
# Remove unnecessary whitespace between tags
|
|
| str replace -ar '>\s+<' '><'
|
|
# Remove extra whitespace in text content (preserve single spaces)
|
|
| str replace -ar '\s+' ' '
|
|
# Remove whitespace around block elements
|
|
| str replace -ar '\s*<(div|section|article|header|footer|main|nav)\s*' '<$1'
|
|
| str replace -ar '\s*</(div|section|article|header|footer|main|nav)>\s*' '</$1>'
|
|
# Remove optional closing tags (basic list)
|
|
| str replace -ar '</li>\s*<li>' '</li><li>'
|
|
| str replace -ar '</p>\s*<p>' '</p><p>'
|
|
# Remove unnecessary quotes around attributes (safe cases)
|
|
| str replace -ar '=([a-zA-Z0-9-_]+)(\s|>)' '=$1$2'
|
|
# Trim
|
|
| str trim
|
|
)
|
|
|
|
$minified
|
|
}
|
|
|
|
# Inline critical CSS into HTML
|
|
export def inline_critical_css [html_content: string, critical_css: string] {
|
|
# Find the head section and inject critical CSS
|
|
let css_style = $"<style>($critical_css)</style>"
|
|
|
|
let updated_html = ($html_content
|
|
| str replace '</head>' $"($css_style)\n</head>"
|
|
)
|
|
|
|
$updated_html
|
|
}
|
|
|
|
# Add async CSS loading
|
|
export def add_async_css [html_content: string, css_file: string] {
|
|
let async_css = $'<link rel="preload" href="($css_file)" as="style" onload="this.onload=null;this.rel=\'stylesheet\'">
|
|
<noscript><link rel="stylesheet" href="($css_file)"></noscript>'
|
|
|
|
let updated_html = ($html_content
|
|
| str replace '</head>' $"($async_css)\n</head>"
|
|
)
|
|
|
|
$updated_html
|
|
}
|
|
|
|
# Inline small SVGs
|
|
export def inline_small_svgs [html_content: string, svg_dir: string, max_size: int] {
|
|
# Find SVG references and inline if small enough
|
|
let svg_refs = ($html_content | parse -r '<img[^>]+src="([^"]+\.svg)"[^>]*>')
|
|
|
|
mut updated_html = $html_content
|
|
|
|
for svg_ref in $svg_refs {
|
|
let svg_path = $svg_ref.capture0
|
|
let full_path = $"($svg_dir)/($svg_path)"
|
|
|
|
if ($full_path | path exists) {
|
|
let svg_content = (open $full_path)
|
|
if ($svg_content | str length) <= $max_size {
|
|
# Inline the SVG by replacing the img tag
|
|
let img_pattern = $'<img[^>]+src="($svg_path)"[^>]*>'
|
|
$updated_html = ($updated_html | str replace -r $img_pattern $svg_content)
|
|
}
|
|
}
|
|
}
|
|
|
|
$updated_html
|
|
}
|
|
|
|
# Optimize meta tags
|
|
export def optimize_meta_tags [html_content: string] {
|
|
# Add performance and SEO meta tags
|
|
let performance_meta = '<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<meta name="theme-color" content="#00d4ff">
|
|
<meta name="description" content="Provisioning - Automatización de Infraestructura con Rust | Rust Las Palmas Meetup 2025">'
|
|
|
|
let updated_html = ($html_content
|
|
| str replace '<meta charset="utf-8">' $'<meta charset="utf-8">
|
|
($performance_meta)'
|
|
)
|
|
|
|
$updated_html
|
|
}
|
|
|
|
# Add preconnect links for external resources
|
|
export def add_preconnect_links [html_content: string] {
|
|
let preconnect_links = '<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>'
|
|
|
|
let updated_html = ($html_content
|
|
| str replace '</title>' $'</title>
|
|
($preconnect_links)'
|
|
)
|
|
|
|
$updated_html
|
|
}
|
|
|
|
# Remove unnecessary attributes
|
|
export def remove_unnecessary_attributes [html_content: string] {
|
|
let cleaned = ($html_content
|
|
# Remove type="text/css" (default for style tags)
|
|
| str replace -ar '\s+type="text/css"' ''
|
|
# Remove type="text/javascript" (default for script tags in HTML5)
|
|
| str replace -ar '\s+type="text/javascript"' ''
|
|
# Remove method="get" (default for forms)
|
|
| str replace -ar '\s+method="get"' ''
|
|
)
|
|
|
|
$cleaned
|
|
}
|
|
|
|
# Optimize images (basic attributes)
|
|
export def optimize_img_attributes [html_content: string] {
|
|
# Add loading="lazy" to images below the fold
|
|
# Add loading="lazy" to images that don't already have it
|
|
mut optimized = $html_content
|
|
|
|
# Find img tags that don't have loading attribute
|
|
let img_without_loading = ($html_content | parse -r '<img(?![^>]*loading=)[^>]*>')
|
|
|
|
for img_tag in $img_without_loading {
|
|
let original_tag = $img_tag.capture0
|
|
let lazy_tag = ($original_tag | str replace '<img' '<img loading="lazy"')
|
|
$optimized = ($optimized | str replace $original_tag $lazy_tag)
|
|
}
|
|
|
|
$optimized
|
|
}
|
|
|
|
# Generate Content Security Policy
|
|
export def generate_csp [config: record] {
|
|
let csp = "default-src 'self'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; font-src fonts.gstatic.com; img-src 'self' data:; script-src 'none';"
|
|
|
|
$'<meta http-equiv="Content-Security-Policy" content="($csp)">'
|
|
}
|
|
|
|
# Analyze HTML for optimization opportunities
|
|
export def analyze_html [html_content: string] {
|
|
let size = ($html_content | str length)
|
|
let lines = ($html_content | lines | length)
|
|
let tags = ($html_content | str replace -ar '<[^>]+>' '' | str length)
|
|
let tag_count = (($size - $tags) / 10) # Rough estimate
|
|
|
|
let external_resources = {
|
|
css_files: ($html_content | str replace -ar '<link[^>]+rel="stylesheet"[^>]*>' '' | str length),
|
|
js_files: ($html_content | str replace -ar '<script[^>]+src[^>]*>' '' | str length),
|
|
images: ($html_content | str replace -ar '<img[^>]+>' '' | str length)
|
|
}
|
|
|
|
{
|
|
size_bytes: $size,
|
|
lines: $lines,
|
|
estimated_tags: $tag_count,
|
|
external_css: (($size - $external_resources.css_files) > 0),
|
|
external_js: (($size - $external_resources.js_files) > 0),
|
|
images: (($size - $external_resources.images) > 0),
|
|
minification_potential: (($size * 0.3) | math round)
|
|
}
|
|
}
|
|
|
|
# Validate HTML structure (basic)
|
|
export def validate_html_structure [html_content: string] {
|
|
let has_doctype = (($html_content | str starts-with "<!DOCTYPE") or ($html_content | str contains "<!DOCTYPE"))
|
|
let has_html_tag = ($html_content | str contains "<html")
|
|
let has_head = ($html_content | str contains "<head>")
|
|
let has_body = ($html_content | str contains "<body")
|
|
let has_title = ($html_content | str contains "<title>")
|
|
|
|
{
|
|
valid_structure: ($has_doctype and $has_html_tag and $has_head and $has_body and $has_title),
|
|
has_doctype: $has_doctype,
|
|
has_html_tag: $has_html_tag,
|
|
has_head: $has_head,
|
|
has_body: $has_body,
|
|
has_title: $has_title
|
|
}
|
|
}
|
|
|
|
# Extract critical above-the-fold content
|
|
export def extract_critical_html [html_content: string] {
|
|
# Extract content likely to be above the fold
|
|
let critical_selectors = [
|
|
".header", ".main-title", ".title", ".ocean-tech-bg",
|
|
".top-logo", ".provisioning-logo"
|
|
]
|
|
|
|
# This is a simplified approach - extract elements with critical classes
|
|
let critical_elements = ($critical_selectors | each { |selector|
|
|
let class_name = ($selector | str replace '.' '')
|
|
$html_content | parse -r $'<[^>]*class="[^"]*($class_name)[^"]*"[^>]*>.*?</[^>]+>'
|
|
} | flatten)
|
|
|
|
# Return the original content for now (simplified)
|
|
$html_content
|
|
}
|
|
|
|
# Generate HTML performance hints
|
|
export def generate_performance_hints [html_content: string] {
|
|
mut hints = []
|
|
|
|
# Check for common performance issues
|
|
if ($html_content | str contains 'style=') {
|
|
$hints = ($hints | append "Consider moving inline styles to CSS files")
|
|
}
|
|
|
|
if ($html_content | str contains '<script') and not ($html_content | str contains 'defer') {
|
|
$hints = ($hints | append "Consider adding 'defer' to script tags")
|
|
}
|
|
|
|
if ($html_content | str contains '<img') and not ($html_content | str contains 'loading="lazy"') {
|
|
$hints = ($hints | append "Consider adding 'loading=lazy' to images below the fold")
|
|
}
|
|
|
|
if not ($html_content | str contains 'preconnect') {
|
|
$hints = ($hints | append "Consider adding preconnect links for external resources")
|
|
}
|
|
|
|
$hints
|
|
} |