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__) main = Blueprint('main', __name__)
# Number of readings to show per page in list view # Number of readings to show per page in list view
PAGE_SIZE = 50 PAGE_SIZE = 25
@main.route('/', methods=['GET']) @main.route('/', methods=['GET'])
def landing(): 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"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}BP Tracker{% endblock %}</title> <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 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"> <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> </head>
<body class="bg-gray-50 text-gray-800 font-sans antialiased"> <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" <nav id="mobile-nav"
x-data="{ isOpen: false }" @keydown.escape="isOpen = false" @click.away="isOpen = false" 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">
:class="{ 'bg-primary-900' : isOpen , 'bg-primary-800' : !isOpen}">
<!--Logo etc--> <!--Logo etc-->
<div class="flex items-center flex-shrink-0 text-white mr-6"> <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="/"> <a class="text-white no-underline hover:text-white hover:no-underline" href="/">
@@ -25,20 +20,18 @@
</div> </div>
<!--Toggle button (hidden on large screens)--> <!--Toggle button (hidden on large screens)-->
<button @click="isOpen = !isOpen" type="button" <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" class="block lg:hidden px-2 text-gray-500 hover:text-white focus:outline-none focus:text-white transition">
:class="{ 'transition transform-180': isOpen }">
<svg class="h-6 w-6 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <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" /> 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" /> 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> </svg>
</button> </button>
<!--Menu--> <!--Menu-->
<div class="w-full flex-grow lg:flex lg:items-center lg:w-auto" <div id="mobile-menu" class="w-full flex-grow lg:flex lg:items-center lg:w-auto hidden shadow-3xl">
:class="{ 'block shadow-3xl': isOpen, 'hidden': !isOpen }" x-show.transition="true">
<ul class="pt-6 lg:pt-0 list-reset lg:flex justify-end flex-1 items-center"> <ul class="pt-6 lg:pt-0 list-reset lg:flex justify-end flex-1 items-center">
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<li class="mr-3"> <li class="mr-3">
@@ -121,11 +114,11 @@
{% if messages %} {% if messages %}
<div class="fixed top-24 right-4 z-50 space-y-4"> <div class="fixed top-24 right-4 z-50 space-y-4">
{% for category, message in messages %} {% 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]" <div
x-data="{ visible: true }" x-show="visible" x-transition.duration.300ms> 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> <span class="font-medium">{{ message }}</span>
<button @click="visible = false" <button
class="text-2xl font-bold ml-4 hover:text-gray-200 transition-colors">&times;</button> class="flash-close-btn text-2xl font-bold ml-4 hover:text-gray-200 transition-colors">&times;</button>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
@@ -146,6 +139,76 @@
</div> </div>
</footer> </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> </body>
</html> </html>

View File

@@ -40,20 +40,24 @@
</div> </div>
</div> </div>
<div x-data="{ open: {{ 'true' if request.method == 'POST' else 'false' }} }" class="relative"> <div class="relative">
<!-- Compact Icon --> <!-- Compact Icon -->
<button @click="open = !open" <button id="filter-btn"
class="bg-primary-600 text-white p-3 rounded-full shadow-lg focus:outline-none hover:bg-primary-700"> 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" <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"> stroke="currentColor" class="w-6 h-6">
<path x-show="!open" stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6" /> <!-- Chevron down icon (default) -->
<path x-show="open" x-cloak stroke-linecap="round" stroke-linejoin="round" d="M18 15l-6-6-6 6" /> <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> </svg>
</button> </button>
<!-- Collapsible Filter Form --> <!-- Collapsible Filter Form -->
<div x-show="open" x-transition.duration.300ms <div id="filter-form"
class="w-full md:w-1/3 bg-white p-6 rounded-xl shadow-lg border border-gray-200"> 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"> <div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-bold text-gray-800">Filter Readings</h3> <h3 class="text-lg font-bold text-gray-800">Filter Readings</h3>
</div> </div>
@@ -84,22 +88,21 @@
</div> </div>
<div class="max-w-5xl mx-auto" x-data="{ activeView: '{{ active_view }}' }"> <div class="max-w-5xl mx-auto">
<!-- Tabs --> <!-- Tabs -->
<div class="flex border-b mb-4"> <div class="flex border-b mb-4" id="dashboard-tabs">
<button @click="activeView = 'list'" hx-get="{{ url_for('main.dashboard_list') }}" <button hx-get="{{ url_for('main.dashboard_list') }}" hx-target="#dashboard-content"
hx-target="#dashboard-content" :class="{'border-primary-600 text-primary-600': activeView === 'list'}" 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
class="px-4 py-2 text-sm font-medium border-b-2">List View</button> View</button>
<button @click="activeView = 'weekly'" hx-get="{{ url_for('main.dashboard_weekly') }}" <button hx-get="{{ url_for('main.dashboard_weekly') }}" hx-target="#dashboard-content"
hx-target="#dashboard-content" :class="{'border-primary-600 text-primary-600': activeView === 'weekly'}" 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
class="px-4 py-2 text-sm font-medium border-b-2">Weekly View</button> View</button>
<button @click="activeView = 'monthly'" hx-get="{{ url_for('main.dashboard_monthly') }}" <button hx-get="{{ url_for('main.dashboard_monthly') }}" hx-target="#dashboard-content"
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
:class="{'border-primary-600 text-primary-600': activeView === 'monthly'}" View</button>
class="px-4 py-2 text-sm font-medium border-b-2">Monthly View</button> <button hx-get="{{ url_for('main.dashboard_graph') }}" hx-target="#dashboard-content"
<button @click="activeView = 'graph'" hx-get="{{ url_for('main.dashboard_graph') }}" 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
hx-target="#dashboard-content" :class="{'border-primary-600 text-primary-600': activeView === 'graph'}" View</button>
class="px-4 py-2 text-sm font-medium border-b-2">Graph View</button>
</div> </div>
<!-- Dashboard Content Target Area for HTMX --> <!-- Dashboard Content Target Area for HTMX -->
@@ -109,4 +112,40 @@
</div> </div>
</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 %} {% 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"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
{% for reading in readings %} {% for reading in readings %}
<a href="{{ url_for('reading.edit_reading', reading_id=reading.id) }}" <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"> <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" <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"> stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" <use href="#icon-clock"></use>
d="M12 8v4l3 3m9-3a9 9 0 1 1-18 0 9 9 0 0 1 18 0z" />
</svg> </svg>
<span title="{{ reading.local_timestamp.strftime('%d %b %Y, %I:%M %p') }}"> <span title="{{ reading.local_timestamp.strftime('%d %b %Y, %I:%M %p') }}">
{{ reading.relative_timestamp }} {{ reading.relative_timestamp }}
@@ -34,7 +40,7 @@
<div class="text-gray-300"> <div class="text-gray-300">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" <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"> 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> </svg>
</div> </div>
</div> </div>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB