276 lines
8.4 KiB
Text
276 lines
8.4 KiB
Text
|
|
#!/usr/bin/env nu
|
||
|
|
# Main build script for web page optimization
|
||
|
|
# Optimizes CSS, SVG, and HTML for production
|
||
|
|
|
||
|
|
# Import utilities
|
||
|
|
use optimize-css.nu *
|
||
|
|
use optimize-svg.nu *
|
||
|
|
use optimize-html.nu *
|
||
|
|
use utils.nu *
|
||
|
|
|
||
|
|
# Main build function
|
||
|
|
export def build_page [
|
||
|
|
--clean(-c) # Clean dist directory before build
|
||
|
|
--dev(-d) # Development build (no minification)
|
||
|
|
--verbose(-v) # Verbose output
|
||
|
|
--analyze(-a) # Show file size analysis
|
||
|
|
] {
|
||
|
|
let config = (load_config)
|
||
|
|
|
||
|
|
if $verbose {
|
||
|
|
print "🚀 Starting page build..."
|
||
|
|
print $"Source: ($config.src_dir)"
|
||
|
|
print $"Destination: ($config.dist_dir)"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Clean dist directory if requested
|
||
|
|
if $clean {
|
||
|
|
clean_dist $config $verbose
|
||
|
|
}
|
||
|
|
|
||
|
|
# Create directory structure
|
||
|
|
setup_directories $config $verbose
|
||
|
|
|
||
|
|
# Build assets in parallel
|
||
|
|
print "📦 Building assets..."
|
||
|
|
|
||
|
|
# Process CSS (critical + non-critical)
|
||
|
|
let css_result = (build_css $config $dev $verbose)
|
||
|
|
|
||
|
|
# Process SVGs
|
||
|
|
let svg_result = (build_svgs $config $dev $verbose)
|
||
|
|
|
||
|
|
# Process HTML with inlined critical resources
|
||
|
|
let html_result = (build_html $config $dev $verbose $css_result.critical)
|
||
|
|
|
||
|
|
# Copy remaining assets
|
||
|
|
copy_assets $config $verbose
|
||
|
|
|
||
|
|
# Generate report
|
||
|
|
if $analyze {
|
||
|
|
generate_report $config $verbose
|
||
|
|
}
|
||
|
|
|
||
|
|
print "✅ Build completed successfully!"
|
||
|
|
|
||
|
|
if $verbose {
|
||
|
|
print $"📊 Build summary:"
|
||
|
|
print $" HTML: ($html_result.size) bytes"
|
||
|
|
print $" CSS: ($css_result.size) bytes"
|
||
|
|
print $" SVG: ($svg_result.size) bytes"
|
||
|
|
print $" Total: (($html_result.size + $css_result.size + $svg_result.size)) bytes"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Clean distribution directory
|
||
|
|
def clean_dist [config: record, verbose: bool] {
|
||
|
|
if $verbose { print "🧹 Cleaning dist directory..." }
|
||
|
|
|
||
|
|
if ($config.dist_dir | path exists) {
|
||
|
|
rm -rf $config.dist_dir
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Setup directory structure
|
||
|
|
def setup_directories [config: record, verbose: bool] {
|
||
|
|
if $verbose { print "📁 Creating directory structure..." }
|
||
|
|
|
||
|
|
mkdir $config.dist_dir
|
||
|
|
mkdir $config.css_dist
|
||
|
|
mkdir $config.svg_dist
|
||
|
|
}
|
||
|
|
|
||
|
|
# Build CSS (split into critical and non-critical)
|
||
|
|
def build_css [config: record, dev: bool, verbose: bool] {
|
||
|
|
if $verbose { print "🎨 Processing CSS..." }
|
||
|
|
|
||
|
|
let critical_modules = ($config.css_modules | first $config.critical_css_modules)
|
||
|
|
let non_critical_modules = ($config.css_modules | skip $config.critical_css_modules)
|
||
|
|
|
||
|
|
# Build critical CSS for inlining
|
||
|
|
let critical_css = ($critical_modules
|
||
|
|
| each { |module|
|
||
|
|
open $"($config.css_src)/($module)"
|
||
|
|
}
|
||
|
|
| str join "\n"
|
||
|
|
)
|
||
|
|
|
||
|
|
# Build non-critical CSS for external file
|
||
|
|
let non_critical_css = ($non_critical_modules
|
||
|
|
| each { |module|
|
||
|
|
open $"($config.css_src)/($module)"
|
||
|
|
}
|
||
|
|
| str join "\n"
|
||
|
|
)
|
||
|
|
|
||
|
|
# Add SVG data CSS and combine all CSS modules
|
||
|
|
let svg_data_css = (open $"($config.css_src)/svg-data.css")
|
||
|
|
let full_css = if $dev {
|
||
|
|
# In dev mode, include ALL modules for easier debugging
|
||
|
|
([$svg_data_css, $critical_css, $non_critical_css] | str join "\n")
|
||
|
|
} else {
|
||
|
|
# In production mode, only external CSS (critical will be inlined)
|
||
|
|
([$svg_data_css, $non_critical_css] | str join "\n")
|
||
|
|
}
|
||
|
|
|
||
|
|
# Minify if not dev build
|
||
|
|
let final_css = if $dev {
|
||
|
|
$full_css
|
||
|
|
} else {
|
||
|
|
minify_css $full_css $config
|
||
|
|
}
|
||
|
|
|
||
|
|
# Save external CSS
|
||
|
|
$final_css | save --force $"($config.css_dist)/($config.css_output)"
|
||
|
|
|
||
|
|
# Return results
|
||
|
|
{
|
||
|
|
critical: (if $dev { $critical_css } else { minify_css $critical_css $config }),
|
||
|
|
size: ($final_css | str length),
|
||
|
|
critical_size: ($critical_css | str length)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Build SVGs
|
||
|
|
def build_svgs [config: record, dev: bool, verbose: bool] {
|
||
|
|
if $verbose { print "🖼️ Processing SVGs..." }
|
||
|
|
|
||
|
|
# Copy symbols (needed for inline use)
|
||
|
|
cp $"($config.svg_src)/symbols.svg" $"($config.svg_dist)/symbols.svg"
|
||
|
|
|
||
|
|
# Process decorative sprites
|
||
|
|
let sprites_content = (open $"($config.svg_src)/decorative-sprites.svg")
|
||
|
|
let optimized_sprites = if $dev {
|
||
|
|
$sprites_content
|
||
|
|
} else {
|
||
|
|
optimize_svg_content $sprites_content $config
|
||
|
|
}
|
||
|
|
|
||
|
|
$optimized_sprites | save --force $"($config.svg_dist)/($config.svg_sprites)"
|
||
|
|
|
||
|
|
{
|
||
|
|
size: ($optimized_sprites | str length)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Build HTML with critical inlining
|
||
|
|
def build_html [config: record, dev: bool, verbose: bool, critical_css: string] {
|
||
|
|
if $verbose { print "📄 Processing HTML..." }
|
||
|
|
|
||
|
|
let html_content = (open $"($config.src_dir)/index.html")
|
||
|
|
|
||
|
|
# Replace CSS link with critical inline + external link
|
||
|
|
let css_replacement = if ($critical_css | str length) > $config.inline_css_max_size {
|
||
|
|
# Critical CSS too large, use external only
|
||
|
|
$'<link rel="stylesheet" href="assets/css/($config.css_output)">'
|
||
|
|
} else {
|
||
|
|
# Inline critical + external non-critical
|
||
|
|
$'<style>($critical_css)</style>
|
||
|
|
<link rel="preload" href="assets/css/($config.css_output)" as="style" onload="this.onload=null;this.rel=\'stylesheet\'">
|
||
|
|
<noscript><link rel="stylesheet" href="assets/css/($config.css_output)"></noscript>'
|
||
|
|
}
|
||
|
|
|
||
|
|
# Replace the CSS import - handle different CSS file names
|
||
|
|
let updated_html = ($html_content
|
||
|
|
| str replace '<link rel="stylesheet" href="assets/css/poster.css">' $css_replacement
|
||
|
|
| str replace '<link rel="stylesheet" href="assets/css/main.css">' $css_replacement
|
||
|
|
| str replace -r '<link rel="stylesheet" href="assets/css/[^"]*\.css">' $css_replacement
|
||
|
|
)
|
||
|
|
|
||
|
|
# Minify HTML if not dev build
|
||
|
|
let final_html = if $dev {
|
||
|
|
$updated_html
|
||
|
|
} else {
|
||
|
|
minify_html $updated_html $config
|
||
|
|
}
|
||
|
|
|
||
|
|
$final_html | save --force $"($config.dist_dir)/($config.html_output)"
|
||
|
|
|
||
|
|
{
|
||
|
|
size: ($final_html | str length)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Copy additional assets
|
||
|
|
def copy_assets [config: record, verbose: bool] {
|
||
|
|
if $verbose { print "📋 Copying additional assets..." }
|
||
|
|
|
||
|
|
# Copy README if exists
|
||
|
|
if ($"($config.src_dir)/README.md" | path exists) {
|
||
|
|
cp $"($config.src_dir)/README.md" $"($config.dist_dir)/README.md"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Generate build report
|
||
|
|
def generate_report [config: record, verbose: bool] {
|
||
|
|
print "\n📊 Build Analysis Report"
|
||
|
|
print "========================"
|
||
|
|
|
||
|
|
# Get file sizes
|
||
|
|
let files = [
|
||
|
|
{ name: "index.html", path: $"($config.dist_dir)/index.html" },
|
||
|
|
{ name: "main.min.css", path: $"($config.css_dist)/($config.css_output)" },
|
||
|
|
{ name: "sprites.svg", path: $"($config.svg_dist)/($config.svg_sprites)" },
|
||
|
|
{ name: "symbols.svg", path: $"($config.svg_dist)/symbols.svg" }
|
||
|
|
]
|
||
|
|
|
||
|
|
$files | each { |file|
|
||
|
|
if ($file.path | path exists) {
|
||
|
|
let size_raw = (ls $file.path | get size | first)
|
||
|
|
let size = ($size_raw | into int)
|
||
|
|
let size_kb = ($size / 1024 | math round -p 1)
|
||
|
|
let size_text = $"($size) bytes"
|
||
|
|
let kb_text = $"($size_kb) KB"
|
||
|
|
print $" ($file.name): ($size_text) ($kb_text)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let total_size = ($files
|
||
|
|
| each { |file|
|
||
|
|
if ($file.path | path exists) {
|
||
|
|
let size_raw = (ls $file.path | get size | first)
|
||
|
|
($size_raw | into int)
|
||
|
|
} else {
|
||
|
|
0
|
||
|
|
}
|
||
|
|
}
|
||
|
|
| math sum
|
||
|
|
)
|
||
|
|
let total_kb = ($total_size / 1024 | math round -p 1)
|
||
|
|
|
||
|
|
let total_text = $"($total_size) bytes"
|
||
|
|
let total_kb_text = $"($total_kb) KB"
|
||
|
|
print $"\nTotal size: ($total_text) ($total_kb_text)"
|
||
|
|
|
||
|
|
# Estimate gzipped size (rough approximation)
|
||
|
|
let estimated_gzip = ($total_size * 0.3 | math round)
|
||
|
|
let estimated_gzip_kb = ($estimated_gzip / 1024 | math round -p 1)
|
||
|
|
let gzip_text = $"($estimated_gzip) bytes"
|
||
|
|
let gzip_kb_text = $"($estimated_gzip_kb) KB"
|
||
|
|
print $"Estimated gzipped: ($gzip_text) ($gzip_kb_text)"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Main entry point for command line usage
|
||
|
|
def main [
|
||
|
|
--clean(-c) # Clean dist directory before build
|
||
|
|
--dev(-d) # Development build (no minification)
|
||
|
|
--verbose(-v) # Verbose output
|
||
|
|
--analyze(-a) # Show file size analysis
|
||
|
|
] {
|
||
|
|
build_page --clean=$clean --dev=$dev --verbose=$verbose --analyze=$analyze
|
||
|
|
}
|
||
|
|
|
||
|
|
# Quick build command
|
||
|
|
def "build quick" [] {
|
||
|
|
build_page --clean --verbose
|
||
|
|
}
|
||
|
|
|
||
|
|
# Development build command
|
||
|
|
def "build dev" [] {
|
||
|
|
build_page --dev --verbose
|
||
|
|
}
|
||
|
|
|
||
|
|
# Production build with analysis
|
||
|
|
def "build prod" [] {
|
||
|
|
build_page --clean --verbose --analyze
|
||
|
|
}
|