Add minify to web app index.html to save space (#1712)

This commit is contained in:
Leendert de Borst
2026-02-15 21:32:42 +01:00
committed by Leendert de Borst
parent 9e4b75e723
commit 4fd6378aab
2 changed files with 85 additions and 38 deletions

View File

@@ -35,14 +35,83 @@
<OutputFile ParameterType="System.String" Required="true" />
<CacheBuster ParameterType="System.String" Required="true" />
<BuildVersion ParameterType="System.String" Required="true" />
<Minify ParameterType="System.Boolean" Required="false" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
<![CDATA[
string content = File.ReadAllText(InputFile);
content = content.Replace("@CacheBuster", CacheBuster).Replace("@BuildVersion", BuildVersion);
if (Minify)
{
// Extract and preserve script/style blocks
var scriptBlocks = new System.Collections.Generic.List<string>();
var styleBlocks = new System.Collections.Generic.List<string>();
content = System.Text.RegularExpressions.Regex.Replace(content, @"(<script[^>]*>)(.*?)(</script>)",
m => { scriptBlocks.Add(m.Groups[2].Value); return m.Groups[1].Value + "___SCRIPT_" + (scriptBlocks.Count - 1) + "___" + m.Groups[3].Value; },
System.Text.RegularExpressions.RegexOptions.Singleline);
content = System.Text.RegularExpressions.Regex.Replace(content, @"(<style[^>]*>)(.*?)(</style>)",
m => { styleBlocks.Add(m.Groups[2].Value); return m.Groups[1].Value + "___STYLE_" + (styleBlocks.Count - 1) + "___" + m.Groups[3].Value; },
System.Text.RegularExpressions.RegexOptions.Singleline);
// Extract and preserve the header comment (contains ASCII art and license)
var headerMatch = System.Text.RegularExpressions.Regex.Match(content, @"(<!--\s*╔[\s\S]*?Build:[\s\S]*?-->)");
string headerComment = headerMatch.Success ? headerMatch.Groups[1].Value : "";
if (headerMatch.Success)
{
content = content.Replace(headerComment, "___HEADER_COMMENT___");
}
// Remove all other HTML comments
content = System.Text.RegularExpressions.Regex.Replace(content, @"<!--.*?-->", "", System.Text.RegularExpressions.RegexOptions.Singleline);
// Collapse whitespace between tags
content = System.Text.RegularExpressions.Regex.Replace(content, @">\s+<", "><");
// Remove leading/trailing whitespace from lines and collapse newlines
content = System.Text.RegularExpressions.Regex.Replace(content, @"^\s+", "", System.Text.RegularExpressions.RegexOptions.Multiline);
content = System.Text.RegularExpressions.Regex.Replace(content, @"\s+$", "", System.Text.RegularExpressions.RegexOptions.Multiline);
content = System.Text.RegularExpressions.Regex.Replace(content, @"\r?\n", "");
content = System.Text.RegularExpressions.Regex.Replace(content, @" +", " ");
// Restore script blocks (minify each one)
for (int i = 0; i < scriptBlocks.Count; i++)
{
var script = scriptBlocks[i];
// Remove JS single-line comments (but not URLs with //)
script = System.Text.RegularExpressions.Regex.Replace(script, @"(?<!:)//[^\n]*", "");
// Remove JS multi-line comments
script = System.Text.RegularExpressions.Regex.Replace(script, @"/\*.*?\*/", "", System.Text.RegularExpressions.RegexOptions.Singleline);
// Collapse all whitespace including newlines
script = System.Text.RegularExpressions.Regex.Replace(script, @"\s+", " ");
script = script.Trim();
content = content.Replace("___SCRIPT_" + i + "___", script);
}
// Restore style blocks (minify each one)
for (int i = 0; i < styleBlocks.Count; i++)
{
var style = styleBlocks[i];
// Remove CSS comments
style = System.Text.RegularExpressions.Regex.Replace(style, @"/\*.*?\*/", "", System.Text.RegularExpressions.RegexOptions.Singleline);
style = System.Text.RegularExpressions.Regex.Replace(style, @"\s+", " ");
style = System.Text.RegularExpressions.Regex.Replace(style, @"\s*([{};:,>+~])\s*", "$1");
style = style.Trim();
content = content.Replace("___STYLE_" + i + "___", style);
}
// Restore header comment at the end (preserving its formatting)
if (headerMatch.Success)
{
content = content.Replace("___HEADER_COMMENT___", "\n" + headerComment + "\n");
}
}
File.WriteAllText(OutputFile, content);
Log.LogMessage(MessageImportance.High, "Replaced content in " + OutputFile);
Log.LogMessage(MessageImportance.High, (Minify ? "Minified and replaced" : "Replaced") + " content in " + OutputFile);
]]>
</Code>
</Task>
@@ -57,7 +126,7 @@
</Target>
<Target Name="GenerateCacheBustedIndexHtml" BeforeTargets="Build">
<ReplaceText InputFile="wwwroot/index.template.html" OutputFile="wwwroot/index.html" CacheBuster="$(CacheBuster)" BuildVersion="$(BuildVersion)" />
<ReplaceText InputFile="wwwroot/index.template.html" OutputFile="wwwroot/index.html" CacheBuster="$(CacheBuster)" BuildVersion="$(BuildVersion)" Minify="true" />
</Target>
<ItemGroup>

View File

@@ -1,26 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!--
==========================================================================================================
db 88 88 8b d8 88
d88b 88 "" `8b d8' 88 ,d
d8'`8b 88 `8b d8' 88 88
d8' `8b 88 88 ,adPPYYba, ,adPPYba, `8b d8' ,adPPYYba, 88 88 88 MM88MMM
d8YaaaaY8b 88 88 "" `Y8 I8[ "" `8b d8' "" `Y8 88 88 88 88
d8""""""""8b 88 88 ,adPPPPP88 `"Y8ba, `8b d8' ,adPPPPP88 88 88 88 88
d8' `8b 88 88 88, ,88 aa ]8I `888' 88, ,88 "8a, ,a88 88 88,
d8' `8b 88 88 `"8bbdP"Y8 `"YbbdP"' `8' `"8bbdP"Y8 `"YbbdP'Y8 88 "Y888
==========================================================================================================
AliasVault - Privacy-first password manager with built-in email aliasing. Fully encrypted and self-hostable.
Build (UTC): @BuildVersion
Source code: https://github.com/aliasvault/aliasvault
License: AGPLv3
-->
<!--
╔═════════════════════════════════════════════════╗
║ _ _ _ __ __ _ _ ║
║ / \ | (_) __ _ __\ \ / /_ _ _ _| | |_
║ / _ \ | | |/ _` / __\ \ / / _` | | | | | __| ║
║ / ___ \| | | (_| \__ \\ V / (_| | |_| | | |_ ║
║ /_/ \_\_|_|\__,_|___/ \_/ \__,_|\__,_|_|\__| ║
╚═════════════════════════════════════════════════╝
AliasVault - Privacy-first password manager
https://github.com/aliasvault/aliasvault | AGPLv3
Build: @BuildVersion
-->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"/>
<title>AliasVault</title>
@@ -157,7 +150,6 @@
if (response.ok) {
translations = await response.json();
} else {
// Fallback to English if the language file is not found
const fallbackResponse = await fetch('/locales/en.json');
translations = await fallbackResponse.json();
}
@@ -200,7 +192,6 @@
}
function localizeContent() {
// Localize loading screen
const localizations = [
{ id: 'loading-title', key: 'loading.title' },
{ id: 'loading-message', key: 'loading.message' },
@@ -217,7 +208,6 @@
}
});
// Set random security quote
const securityQuotes = getTranslation('quotes.security');
if (securityQuotes && securityQuotes.length > 0) {
const quoteElement = document.getElementById('security-quote');
@@ -245,7 +235,7 @@
const minDisplayTime = 1000;
const checkInterval = 500;
const refreshButtonTimeout = 300000;
const scriptsCheckTimeout = 5000; // Check for blocked scripts after 5 seconds
const scriptsCheckTimeout = 5000;
const appElement = document.getElementById('app');
const refreshButton = document.getElementById('refresh-button');
@@ -294,8 +284,6 @@
if (progressText) progressText.classList.add('hidden');
};
// Detect Blazor boot failures (e.g., after .NET version upgrades)
// This handles transitions between major .NET versions where boot format changes
let bootFailureHandled = false;
async function handleBlazorBootFailure() {
if (bootFailureHandled) return;
@@ -304,20 +292,14 @@
console.info('AliasVault: Blazor boot failure detected. Refreshing cached files...');
try {
// Clear service worker caches
if ('caches' in window) {
const cacheKeys = await caches.keys();
await Promise.all(cacheKeys.map(key => caches.delete(key)));
}
// Unregister service workers
if ('serviceWorker' in navigator) {
const registrations = await navigator.serviceWorker.getRegistrations();
await Promise.all(registrations.map(r => r.unregister()));
}
// Force browser to fetch fresh versions of critical files
// cache: 'reload' bypasses HTTP cache and updates it with fresh content
const criticalFiles = [
'/',
'/_framework/blazor.webassembly.js',
@@ -329,8 +311,6 @@
} catch (e) {
console.error('AliasVault: Cache refresh failed:', e);
}
// Navigate to cache-busted URL to force complete reload with fresh resources
const url = new URL(window.location.href);
url.searchParams.set('_r', Date.now().toString());
window.location.replace(url.toString());
@@ -347,7 +327,6 @@
if (errorMessage.includes('WebAssembly')) {
showError(getTranslation('errors.webAssemblyError'));
}
// Detect Blazor boot failures and runtime errors that require a reload
else if (
errorMessage.includes('blazor.boot.json') ||
errorMessage.includes('Failed to load config file') ||
@@ -387,5 +366,4 @@
})
</script>
</body>
</html>