using System.Text; using System.Text.RegularExpressions; public static class HtmlMinifier { public static string MinifyHtml(string html) { var htmlMinifier = new WebMarkupMin.Core.HtmlMinifier(); var result = htmlMinifier.Minify(html, generateStatistics: true); return result.MinifiedContent; if (string.IsNullOrEmpty(html)) return html; // Вырезаем чувствительные теги var placeholders = new Dictionary(); html = ExtractTag(html, "pre", placeholders); html = ExtractTag(html, "code", placeholders); html = ExtractTag(html, "textarea", placeholders); // Минификация CSS и JS html = MinifyCssInHtml(html); html = MinifyJavaScriptInHtml(html); // Удаление HTML комментариев html = Regex.Replace(html, @"", "", RegexOptions.Singleline | RegexOptions.Compiled); // Удаление лишних пробелов между тегами html = Regex.Replace(html, @">\s+<", "><"); // Collapse whitespace html = Regex.Replace(html, @"\s{2,}", " "); // Возвращаем чувствительные блоки foreach (var kv in placeholders) html = html.Replace(kv.Key, kv.Value); return html.Trim(); } private static string ExtractTag(string html, string tag, Dictionary dict) { return Regex.Replace(html, $@"<{tag}[^>]*>[\s\S]*?<\/{tag}>", m => { var key = $"__PLACEHOLDER_{dict.Count}__"; dict[key] = m.Value; return key; }, RegexOptions.IgnoreCase | RegexOptions.Compiled); } private static string MinifyCssInHtml(string html) { // Находим все теги ", RegexOptions.Compiled); return styleRegex.Replace(html, match => { var css = match.Groups[1].Value; var minifiedCss = MinifyCss(css); return $""; }); } public static string MinifyCss(string css) { if (string.IsNullOrEmpty(css)) return css; // 1. Удаление комментариев css = Regex.Replace(css, @"/\*[\s\S]*?\*/", "", RegexOptions.Compiled); // 2. Удаление лишних пробелов и переносов css = Regex.Replace(css, @"\s+", " ", RegexOptions.Compiled); css = Regex.Replace(css, @"\s*{\s*", "{", RegexOptions.Compiled); css = Regex.Replace(css, @"\s*}\s*", "}", RegexOptions.Compiled); css = Regex.Replace(css, @"\s*:\s*", ":", RegexOptions.Compiled); css = Regex.Replace(css, @"\s*;\s*", ";", RegexOptions.Compiled); css = Regex.Replace(css, @"\s*,\s*", ",", RegexOptions.Compiled); // 3. Удаление последней точки с запятой перед } css = Regex.Replace(css, @";}", "}", RegexOptions.Compiled); // 4. Удаление пробелов вокруг селекторов css = Regex.Replace(css, @"\s*>\s*", ">", RegexOptions.Compiled); css = Regex.Replace(css, @"\s*\+\s*", "+", RegexOptions.Compiled); css = Regex.Replace(css, @"\s*~\s*", "~", RegexOptions.Compiled); // 5. Удаление пробелов в значениях css = Regex.Replace(css, @"(\d)\s+(px|em|rem|%|pt|pc|in|cm|mm|ex|ch|vw|vh|vmin|vmax)", "$1$2", RegexOptions.Compiled); // 6. Удаление ведущих нулей css = Regex.Replace(css, @"(?<=[ :\(,])0?\.(\d+)", ".$1", RegexOptions.Compiled); css = Regex.Replace(css, @"url\(\s*(.*?)\s*\)", "url($1)"); return css.Trim(); } private static string MinifyJavaScriptInHtml(string html) { // Находим все теги ", RegexOptions.Compiled); return scriptRegex.Replace(html, match => { var scriptContent = match.Groups[1].Value; // Пропускаем script с type="application/json" или src var tag = match.Value; if (tag.Contains("type=\"application/json\"") || tag.Contains("src=") || scriptContent.Trim().Length == 0) return match.Value; var minifiedJs = MinifyJavaScript(scriptContent); if (tag.Contains("type=\"module\"")) return $""; else return $""; }); } public static string MinifyJavaScript(string js) { if (string.IsNullOrEmpty(js)) return js; var sb = new StringBuilder(js.Length); int i = 0; int len = js.Length; bool inSingle = false; bool inDouble = false; bool inTemplate = false; bool inLineComment = false; bool inBlockComment = false; while (i < len) { char c = js[i]; char next = i + 1 < len ? js[i + 1] : '\0'; // ----------------------------- // LINE COMMENT // // ----------------------------- if (inLineComment) { if (c == '\n' || c == '\r') { inLineComment = false; sb.Append(' '); } i++; continue; } // ----------------------------- // BLOCK COMMENT /* ... */ // // ----------------------------- if (inBlockComment) { if (c == '*' && next == '/') { inBlockComment = false; i += 2; } else { i++; } continue; } // ----------------------------- // STRING: '...' // // ----------------------------- if (inSingle) { sb.Append(c); if (c == '\\') { if (i + 1 < len) sb.Append(js[i + 1]); i += 2; continue; } if (c == '\'') inSingle = false; i++; continue; } // ----------------------------- // STRING: "..." // // ----------------------------- if (inDouble) { sb.Append(c); if (c == '\\') { if (i + 1 < len) sb.Append(js[i + 1]); i += 2; continue; } if (c == '"') inDouble = false; i++; continue; } // ----------------------------- // TEMPLATE: `...` // // ----------------------------- if (inTemplate) { sb.Append(c); if (c == '\\') { if (i + 1 < len) sb.Append(js[i + 1]); i += 2; continue; } if (c == '`') inTemplate = false; i++; continue; } // ----------------------------- // NORMAL CODE // // ----------------------------- // Start of line comment if (c == '/' && next == '/') { inLineComment = true; i += 2; continue; } // Start of block comment if (c == '/' && next == '*') { inBlockComment = true; i += 2; continue; } // Start of strings if (c == '\'') { inSingle = true; sb.Append(c); i++; continue; } if (c == '"') { inDouble = true; sb.Append(c); i++; continue; } if (c == '`') { inTemplate = true; sb.Append(c); i++; continue; } // Collapse whitespace in code if (char.IsWhiteSpace(c)) { sb.Append(' '); i++; continue; } sb.Append(c); i++; } // ----------------------------- // FINAL WHITESPACE MINIFICATION // ----------------------------- var result = sb.ToString(); // Удаляем повторяющиеся пробелы result = Regex.Replace(result, @"\s+", " "); // Убираем пробелы вокруг операторов result = Regex.Replace(result, @"\s*([=+\-*/%&|^<>!?:,;{}()\[\]])\s*", "$1"); return result.Trim(); } public static string CompressJson(string json) { if (string.IsNullOrEmpty(json)) return json; // Удаление пробелов и переносов из JSON json = Regex.Replace(json, @"(""[^""\\]*(?:\\.[^""\\]*)*"")|\s+", match => match.Groups[1].Success ? match.Groups[1].Value : "", RegexOptions.Compiled); return json; } }