207 lines
7.4 KiB
Text
207 lines
7.4 KiB
Text
|
|
#!/usr/bin/env nu
|
||
|
|
# JavaScript optimization utilities
|
||
|
|
|
||
|
|
# Check if external JS minifier tool is available
|
||
|
|
export def check_js_tool_available [tool_name: string, tool_path: string] {
|
||
|
|
if ($tool_path | str length) > 0 {
|
||
|
|
# Use explicit path
|
||
|
|
($tool_path | path exists)
|
||
|
|
} else {
|
||
|
|
# Check if tool is in PATH
|
||
|
|
(which $tool_name | length) > 0
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get the command to run for JS minification
|
||
|
|
export def get_js_minifier_command [tool_name: string, tool_path: string, args: list<string>] {
|
||
|
|
let tool_command = if ($tool_path | str length) > 0 {
|
||
|
|
$tool_path
|
||
|
|
} else {
|
||
|
|
$tool_name
|
||
|
|
}
|
||
|
|
|
||
|
|
# Default arguments for common tools if none provided
|
||
|
|
let default_args = match $tool_name {
|
||
|
|
"swc" => ["--minify", "--target", "es2018"],
|
||
|
|
"terser" => ["--compress", "--mangle"],
|
||
|
|
"uglifyjs" => ["--compress", "--mangle"],
|
||
|
|
"esbuild" => ["--minify", "--target=es2018"],
|
||
|
|
_ => []
|
||
|
|
}
|
||
|
|
|
||
|
|
let final_args = if ($args | length) > 0 { $args } else { $default_args }
|
||
|
|
|
||
|
|
{
|
||
|
|
command: $tool_command,
|
||
|
|
args: $final_args
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Minify JavaScript using external tool
|
||
|
|
export def minify_js_with_external_tool [js_content: string, config: record] {
|
||
|
|
let tool_name = $config.js_minifier
|
||
|
|
let tool_path = $config.js_minifier_path
|
||
|
|
let tool_args = $config.js_minifier_args
|
||
|
|
|
||
|
|
if not (check_js_tool_available $tool_name $tool_path) {
|
||
|
|
error make {msg: $"JavaScript minifier '($tool_name)' not found"}
|
||
|
|
}
|
||
|
|
|
||
|
|
let cmd_info = (get_js_minifier_command $tool_name $tool_path $tool_args)
|
||
|
|
|
||
|
|
# Create temporary files
|
||
|
|
let temp_input = $"/tmp/js_input_(random chars -l 8).js"
|
||
|
|
let temp_output = $"/tmp/js_output_(random chars -l 8).js"
|
||
|
|
|
||
|
|
try {
|
||
|
|
# Write input to temp file
|
||
|
|
$js_content | save $temp_input
|
||
|
|
|
||
|
|
# Run minification tool with tool-specific syntax
|
||
|
|
let result = if $tool_name == "swc" {
|
||
|
|
# SWC uses different syntax: swc <input> -o <output> --minify
|
||
|
|
(run-external $cmd_info.command $temp_input "-o" $temp_output ...$cmd_info.args)
|
||
|
|
} else {
|
||
|
|
# Other tools use: tool <args> <input> --output <output>
|
||
|
|
(run-external $cmd_info.command ...$cmd_info.args $temp_input "--output" $temp_output)
|
||
|
|
}
|
||
|
|
|
||
|
|
# Read result
|
||
|
|
if ($temp_output | path exists) {
|
||
|
|
let minified = (open $temp_output)
|
||
|
|
|
||
|
|
# Cleanup temp files
|
||
|
|
rm $temp_input
|
||
|
|
rm $temp_output
|
||
|
|
|
||
|
|
$minified
|
||
|
|
} else {
|
||
|
|
error make {msg: $"Minification failed: output file not created"}
|
||
|
|
}
|
||
|
|
} catch { |err|
|
||
|
|
# Cleanup temp files in case of error
|
||
|
|
if ($temp_input | path exists) { rm $temp_input }
|
||
|
|
if ($temp_output | path exists) { rm $temp_output }
|
||
|
|
|
||
|
|
error make {msg: $"External minification failed: ($err.msg)"}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Basic JavaScript minification (fallback)
|
||
|
|
export def minify_js_builtin [js_content: string, config: record] {
|
||
|
|
let minified = ($js_content
|
||
|
|
# Remove single-line comments (// ...)
|
||
|
|
| str replace -ar '//.*?$' ''
|
||
|
|
# Remove multi-line comments (/* ... */)
|
||
|
|
| str replace -ar '/\*[\s\S]*?\*/' ''
|
||
|
|
# Remove unnecessary whitespace
|
||
|
|
| str replace -ar '\s+' ' '
|
||
|
|
# Remove spaces around specific characters
|
||
|
|
| str replace -ar '\s*{\s*' '{'
|
||
|
|
| str replace -ar '\s*}\s*' '}'
|
||
|
|
| str replace -ar '\s*;\s*' ';'
|
||
|
|
| str replace -ar '\s*,\s*' ','
|
||
|
|
| str replace -ar '\s*=\s*' '='
|
||
|
|
| str replace -ar '\s*\(\s*' '('
|
||
|
|
| str replace -ar '\s*\)\s*' ')'
|
||
|
|
| str replace -ar '\s*\[\s*' '['
|
||
|
|
| str replace -ar '\s*\]\s*' ']'
|
||
|
|
# Trim
|
||
|
|
| str trim
|
||
|
|
)
|
||
|
|
|
||
|
|
$minified
|
||
|
|
}
|
||
|
|
|
||
|
|
# Main JavaScript minification function with tool selection
|
||
|
|
export def minify_js [js_content: string, config: record] {
|
||
|
|
let tool_name = $config.js_minifier
|
||
|
|
|
||
|
|
if ($tool_name | str length) > 0 {
|
||
|
|
# Try external tool first
|
||
|
|
try {
|
||
|
|
minify_js_with_external_tool $js_content $config
|
||
|
|
} catch { |err|
|
||
|
|
print $"⚠️ External tool failed: ($err.msg), falling back to built-in minifier"
|
||
|
|
minify_js_builtin $js_content $config
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
# Use built-in minifier
|
||
|
|
minify_js_builtin $js_content $config
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Combine JavaScript modules
|
||
|
|
export def combine_js_modules [modules: list<string>, js_dir: string] {
|
||
|
|
$modules | each { |module|
|
||
|
|
let module_path = $"($js_dir)/($module)"
|
||
|
|
if ($module_path | path exists) {
|
||
|
|
let content = (open $module_path)
|
||
|
|
$"/* === ($module) === */\n($content)\n"
|
||
|
|
} else {
|
||
|
|
$"/* WARNING: ($module) not found */\n"
|
||
|
|
}
|
||
|
|
} | str join "\n"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Analyze JavaScript for optimization opportunities
|
||
|
|
export def analyze_js [js_content: string] {
|
||
|
|
let lines = ($js_content | lines)
|
||
|
|
let total_lines = ($lines | length)
|
||
|
|
let comment_lines = ($lines | where ($it | str starts-with "//") or ($it | str contains "/*") | length)
|
||
|
|
let empty_lines = ($lines | where ($it | str trim) == "" | length)
|
||
|
|
let function_lines = ($lines | where ($it | str contains "function") or ($it | str contains "=>") | length)
|
||
|
|
|
||
|
|
{
|
||
|
|
total_lines: $total_lines,
|
||
|
|
comment_lines: $comment_lines,
|
||
|
|
empty_lines: $empty_lines,
|
||
|
|
function_lines: $function_lines,
|
||
|
|
content_lines: ($total_lines - $comment_lines - $empty_lines),
|
||
|
|
size_bytes: ($js_content | str length),
|
||
|
|
estimated_minified_size: (($js_content | str length) * 0.7 | math round)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# JavaScript performance metrics
|
||
|
|
export def js_performance_metrics [js_content: string] {
|
||
|
|
let functions = ($js_content | str replace -ar 'function\s+\w+\s*\([^)]*\)\s*\{[^}]*\}' '' | split row "function" | length)
|
||
|
|
let variables = ($js_content | str replace -ar '(var|let|const)\s+\w+' '' | split row " " | length)
|
||
|
|
let event_listeners = ($js_content | str replace -ar 'addEventListener\s*\(' '' | split row "addEventListener" | length)
|
||
|
|
|
||
|
|
{
|
||
|
|
estimated_functions: ($functions - 1),
|
||
|
|
estimated_variables: (if $variables > 10 { $variables - 10 } else { 0 }),
|
||
|
|
event_listeners: (if $event_listeners > 1 { $event_listeners - 1 } else { 0 }),
|
||
|
|
has_jquery: ($js_content | str contains "$" or ($js_content | str contains "jQuery")),
|
||
|
|
has_async: ($js_content | str contains "async" or ($js_content | str contains "await")),
|
||
|
|
has_modules: ($js_content | str contains "import" or ($js_content | str contains "export"))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Generate performance hints for JavaScript
|
||
|
|
export def generate_js_performance_hints [js_content: string] {
|
||
|
|
mut hints = []
|
||
|
|
|
||
|
|
if ($js_content | str contains "document.getElementById") {
|
||
|
|
$hints = ($hints | append "Consider caching DOM element selections")
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($js_content | str contains "setInterval") or ($js_content | str contains "setTimeout") {
|
||
|
|
$hints = ($hints | append "Ensure timers are properly cleared to prevent memory leaks")
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($js_content | str contains "addEventListener") and not ($js_content | str contains "removeEventListener") {
|
||
|
|
$hints = ($hints | append "Consider removing event listeners when no longer needed")
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($js_content | str length) > 50000 {
|
||
|
|
$hints = ($hints | append "Consider splitting large JavaScript files")
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($js_content | str contains "console.log") {
|
||
|
|
$hints = ($hints | append "Remove console.log statements in production")
|
||
|
|
}
|
||
|
|
|
||
|
|
$hints
|
||
|
|
}
|