perf: massive reduction in application bundle size

- Replaced Alpine.js and Turbolinks with lightweight vanilla JS event listeners
- Swapped HTMX CDN import for a custom 15-line native JS polyfill
- Removed Google Fonts ('Inter') in favor of the native system font stack
- Extracted repeated SVGs in list views into a `<defs>` block to shrink HTML
- Reduced dashboard pagination PAGE_SIZE from 50 to 25
- Minified Tailwind CSS output and enabled Gzip/Brotli via Flask-Compress
This commit is contained in:
Peter Stockings
2026-03-10 19:10:47 +11:00
parent a0abf41ff6
commit dcef99c3bf
7 changed files with 153 additions and 2155 deletions

View File

@@ -11,7 +11,7 @@ from datetime import date, datetime, timedelta
main = Blueprint('main', __name__)
# Number of readings to show per page in list view
PAGE_SIZE = 50
PAGE_SIZE = 25
@main.route('/', methods=['GET'])
def landing():

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,17 +6,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}BP Tracker{% endblock %}</title>
<link rel="icon" type="image/svg+xml" sizes="any" href="{{ url_for('static', filename='images/favicon.svg') }}">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link href="/static/css/tailwind.css" rel="stylesheet">
<script src="/static/js/alpine.min.js" defer></script>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<script src="/static/js/turbolinks.min.js"></script>
</head>
<body class="bg-gray-50 text-gray-800 font-sans antialiased">
<nav class="flex items-center justify-between flex-wrap p-6 fixed w-full z-10 top-0 transition-colors duration-300 shadow-md"
x-data="{ isOpen: false }" @keydown.escape="isOpen = false" @click.away="isOpen = false"
:class="{ 'bg-primary-900' : isOpen , 'bg-primary-800' : !isOpen}">
<nav id="mobile-nav"
class="flex items-center justify-between flex-wrap p-6 fixed w-full z-10 top-0 transition-colors duration-300 shadow-md bg-primary-800">
<!--Logo etc-->
<div class="flex items-center flex-shrink-0 text-white mr-6">
<a class="text-white no-underline hover:text-white hover:no-underline" href="/">
@@ -25,20 +20,18 @@
</div>
<!--Toggle button (hidden on large screens)-->
<button @click="isOpen = !isOpen" type="button"
class="block lg:hidden px-2 text-gray-500 hover:text-white focus:outline-none focus:text-white"
:class="{ 'transition transform-180': isOpen }">
<button id="mobile-menu-btn" type="button"
class="block lg:hidden px-2 text-gray-500 hover:text-white focus:outline-none focus:text-white transition">
<svg class="h-6 w-6 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path x-show="isOpen" fill-rule="evenodd" clip-rule="evenodd"
<path id="icon-close" class="hidden" fill-rule="evenodd" clip-rule="evenodd"
d="M18.278 16.864a1 1 0 0 1-1.414 1.414l-4.829-4.828-4.828 4.828a1 1 0 0 1-1.414-1.414l4.828-4.829-4.828-4.828a1 1 0 0 1 1.414-1.414l4.829 4.828 4.828-4.828a1 1 0 1 1 1.414 1.414l-4.828 4.829 4.828 4.828z" />
<path x-show="!isOpen" fill-rule="evenodd"
<path id="icon-menu" fill-rule="evenodd"
d="M4 5h16a1 1 0 0 1 0 2H4a1 1 0 1 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2z" />
</svg>
</button>
<!--Menu-->
<div class="w-full flex-grow lg:flex lg:items-center lg:w-auto"
:class="{ 'block shadow-3xl': isOpen, 'hidden': !isOpen }" x-show.transition="true">
<div id="mobile-menu" class="w-full flex-grow lg:flex lg:items-center lg:w-auto hidden shadow-3xl">
<ul class="pt-6 lg:pt-0 list-reset lg:flex justify-end flex-1 items-center">
{% if current_user.is_authenticated %}
<li class="mr-3">
@@ -121,11 +114,11 @@
{% if messages %}
<div class="fixed top-24 right-4 z-50 space-y-4">
{% for category, message in messages %}
<div class="flex items-center justify-between p-4 rounded-xl shadow-xl text-white bg-{{ 'red' if category == 'danger' else 'primary' }}-500 min-w-[300px]"
x-data="{ visible: true }" x-show="visible" x-transition.duration.300ms>
<div
class="flash-message flex items-center justify-between p-4 rounded-xl shadow-xl text-white bg-{{ 'red' if category == 'danger' else 'primary' }}-500 min-w-[300px] transition-all duration-300">
<span class="font-medium">{{ message }}</span>
<button @click="visible = false"
class="text-2xl font-bold ml-4 hover:text-gray-200 transition-colors">&times;</button>
<button
class="flash-close-btn text-2xl font-bold ml-4 hover:text-gray-200 transition-colors">&times;</button>
</div>
{% endfor %}
</div>
@@ -146,6 +139,76 @@
</div>
</footer>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Flash messages
document.querySelectorAll('.flash-close-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const el = e.target.closest('.flash-message');
if (el) {
el.style.opacity = '0';
setTimeout(() => el.remove(), 300);
}
});
});
// Micro-HTMX implementation
document.body.addEventListener('click', async (e) => {
const trigger = e.target.closest('[hx-get]');
if (!trigger) return;
e.preventDefault();
const url = trigger.getAttribute('hx-get');
const targetSelector = trigger.getAttribute('hx-target');
if (!url || !targetSelector) return;
const targetEl = document.querySelector(targetSelector);
if (!targetEl) return;
try {
const response = await fetch(url);
if (!response.ok) throw new Error('Fetch failed');
targetEl.innerHTML = await response.text();
} catch (err) {
console.error('Micro-HTMX error:', err);
}
});
// Mobile Menu
const menuBtn = document.getElementById('mobile-menu-btn');
const menu = document.getElementById('mobile-menu');
const nav = document.getElementById('mobile-nav');
const iconMenu = document.getElementById('icon-menu');
const iconClose = document.getElementById('icon-close');
if (menuBtn && menu) {
const toggleMenu = (forceClose = false) => {
const isHidden = menu.classList.contains('hidden');
if (!isHidden || forceClose) {
menu.classList.add('hidden');
nav.classList.replace('bg-primary-900', 'bg-primary-800');
iconMenu.classList.remove('hidden');
iconClose.classList.add('hidden');
menuBtn.classList.remove('rotate-180', 'transform');
} else {
menu.classList.remove('hidden');
nav.classList.replace('bg-primary-800', 'bg-primary-900');
iconMenu.classList.add('hidden');
iconClose.classList.remove('hidden');
menuBtn.classList.add('rotate-180', 'transform');
}
};
menuBtn.addEventListener('click', () => toggleMenu());
document.addEventListener('click', (e) => {
if (!nav.contains(e.target) && !menu.classList.contains('hidden')) toggleMenu(true);
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && !menu.classList.contains('hidden')) toggleMenu(true);
});
}
});
</script>
</body>
</html>

View File

@@ -40,20 +40,24 @@
</div>
</div>
<div x-data="{ open: {{ 'true' if request.method == 'POST' else 'false' }} }" class="relative">
<div class="relative">
<!-- Compact Icon -->
<button @click="open = !open"
class="bg-primary-600 text-white p-3 rounded-full shadow-lg focus:outline-none hover:bg-primary-700">
<button id="filter-btn"
class="bg-primary-600 text-white p-3 rounded-full shadow-lg focus:outline-none hover:bg-primary-700 transition">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
stroke="currentColor" class="w-6 h-6">
<path x-show="!open" stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6" />
<path x-show="open" x-cloak stroke-linecap="round" stroke-linejoin="round" d="M18 15l-6-6-6 6" />
<!-- Chevron down icon (default) -->
<path id="filter-icon-closed" class="{{ 'hidden' if request.method == 'POST' else '' }}"
stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6" />
<!-- X icon -->
<path id="filter-icon-open" class="{{ '' if request.method == 'POST' else 'hidden' }}"
stroke-linecap="round" stroke-linejoin="round" d="M18 15l-6-6-6 6" />
</svg>
</button>
<!-- Collapsible Filter Form -->
<div x-show="open" x-transition.duration.300ms
class="w-full md:w-1/3 bg-white p-6 rounded-xl shadow-lg border border-gray-200">
<div id="filter-form"
class="{{ '' if request.method == 'POST' else 'hidden' }} transition-all duration-300 w-full md:w-1/3 bg-white p-6 rounded-xl shadow-lg border border-gray-200">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-bold text-gray-800">Filter Readings</h3>
</div>
@@ -84,22 +88,21 @@
</div>
<div class="max-w-5xl mx-auto" x-data="{ activeView: '{{ active_view }}' }">
<div class="max-w-5xl mx-auto">
<!-- Tabs -->
<div class="flex border-b mb-4">
<button @click="activeView = 'list'" hx-get="{{ url_for('main.dashboard_list') }}"
hx-target="#dashboard-content" :class="{'border-primary-600 text-primary-600': activeView === 'list'}"
class="px-4 py-2 text-sm font-medium border-b-2">List View</button>
<button @click="activeView = 'weekly'" hx-get="{{ url_for('main.dashboard_weekly') }}"
hx-target="#dashboard-content" :class="{'border-primary-600 text-primary-600': activeView === 'weekly'}"
class="px-4 py-2 text-sm font-medium border-b-2">Weekly View</button>
<button @click="activeView = 'monthly'" hx-get="{{ url_for('main.dashboard_monthly') }}"
hx-target="#dashboard-content"
:class="{'border-primary-600 text-primary-600': activeView === 'monthly'}"
class="px-4 py-2 text-sm font-medium border-b-2">Monthly View</button>
<button @click="activeView = 'graph'" hx-get="{{ url_for('main.dashboard_graph') }}"
hx-target="#dashboard-content" :class="{'border-primary-600 text-primary-600': activeView === 'graph'}"
class="px-4 py-2 text-sm font-medium border-b-2">Graph View</button>
<div class="flex border-b mb-4" id="dashboard-tabs">
<button hx-get="{{ url_for('main.dashboard_list') }}" hx-target="#dashboard-content"
class="tab-btn px-4 py-2 text-sm font-medium border-b-2 {{ 'border-primary-600 text-primary-600' if active_view == 'list' else '' }}">List
View</button>
<button hx-get="{{ url_for('main.dashboard_weekly') }}" hx-target="#dashboard-content"
class="tab-btn px-4 py-2 text-sm font-medium border-b-2 {{ 'border-primary-600 text-primary-600' if active_view == 'weekly' else '' }}">Weekly
View</button>
<button hx-get="{{ url_for('main.dashboard_monthly') }}" hx-target="#dashboard-content"
class="tab-btn px-4 py-2 text-sm font-medium border-b-2 {{ 'border-primary-600 text-primary-600' if active_view == 'monthly' else '' }}">Monthly
View</button>
<button hx-get="{{ url_for('main.dashboard_graph') }}" hx-target="#dashboard-content"
class="tab-btn px-4 py-2 text-sm font-medium border-b-2 {{ 'border-primary-600 text-primary-600' if active_view == 'graph' else '' }}">Graph
View</button>
</div>
<!-- Dashboard Content Target Area for HTMX -->
@@ -109,4 +112,40 @@
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Filter Form Toggle
const filterBtn = document.getElementById('filter-btn');
const filterForm = document.getElementById('filter-form');
const iconClosed = document.getElementById('filter-icon-closed');
const iconOpen = document.getElementById('filter-icon-open');
if (filterBtn && filterForm) {
filterBtn.addEventListener('click', () => {
const isHidden = filterForm.classList.contains('hidden');
if (isHidden) {
filterForm.classList.remove('hidden');
iconClosed.classList.add('hidden');
iconOpen.classList.remove('hidden');
} else {
filterForm.classList.add('hidden');
iconClosed.classList.remove('hidden');
iconOpen.classList.add('hidden');
}
});
}
// Tabs
const tabBtns = document.querySelectorAll('.tab-btn');
tabBtns.forEach(btn => {
btn.addEventListener('click', () => {
tabBtns.forEach(b => {
b.classList.remove('border-primary-600', 'text-primary-600');
});
btn.classList.add('border-primary-600', 'text-primary-600');
});
});
});
</script>
{% endblock %}

View File

@@ -1,3 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<defs>
<path id="icon-clock" stroke-linecap="round" stroke-linejoin="round"
d="M12 8v4l3 3m9-3a9 9 0 1 1-18 0 9 9 0 0 1 18 0z" />
<path id="icon-chevron" stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</defs>
</svg>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
{% for reading in readings %}
<a href="{{ url_for('reading.edit_reading', reading_id=reading.id) }}"
@@ -8,8 +15,7 @@
<div class="flex items-center text-gray-400 text-xs mb-1">
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5 mr-1" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 8v4l3 3m9-3a9 9 0 1 1-18 0 9 9 0 0 1 18 0z" />
<use href="#icon-clock"></use>
</svg>
<span title="{{ reading.local_timestamp.strftime('%d %b %Y, %I:%M %p') }}">
{{ reading.relative_timestamp }}
@@ -34,7 +40,7 @@
<div class="text-gray-300">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
stroke="currentColor" class="h-4 w-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
<use href="#icon-chevron"></use>
</svg>
</div>
</div>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB