add minifying, cache busting, and auto prod and dev mode

This commit is contained in:
2025-07-04 22:24:29 +02:00
parent 3231a5d221
commit f894e4a1fe
9 changed files with 388 additions and 7 deletions

View File

@ -9,6 +9,13 @@ RUN pip install --no-cache-dir -r requirements.txt
# Copy all application files # Copy all application files
COPY . . COPY . .
# Build assets for production
RUN python build_assets.py
# Set production environment
ENV FLASK_ENV=production
ENV FLASK_DEBUG=0
# Ensure proper permissions # Ensure proper permissions
RUN chmod +x app.py RUN chmod +x app.py

50
Makefile Normal file
View File

@ -0,0 +1,50 @@
.PHONY: help build clean install run test deploy
help: ## Show this help message
@echo "Available commands:"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
install: ## Install Python dependencies
pip install -r requirements.txt
build: ## Build minified assets with cache busting
python build_assets.py
clean: ## Clean old cache-busted assets
python build_assets.py clean
run: ## Run the Flask development server
export FLASK_ENV=development && export FLASK_DEBUG=1 && python app.py
run-prod: ## Run the Flask production server
export FLASK_ENV=production && export FLASK_DEBUG=0 && python app.py
test: ## Run tests (placeholder)
@echo "No tests configured yet"
deploy: clean build ## Deploy: clean old assets and build new ones
@echo "Deployment completed!"
docker-build: ## Build Docker image
docker build -t kobelly .
docker-run: ## Run Docker container (production)
docker-compose up -d
docker-run-dev: ## Run Docker container (development)
docker-compose -f docker-compose.dev.yml up -d
docker-stop: ## Stop Docker container
docker-compose down
docker-stop-dev: ## Stop Docker container (development)
docker-compose -f docker-compose.dev.yml down
docker-logs: ## Show Docker logs
docker-compose logs -f
docker-logs-dev: ## Show Docker logs (development)
docker-compose -f docker-compose.dev.yml logs -f
dev-setup: install build ## Setup development environment
@echo "Development environment ready!"

116
README.md
View File

@ -8,8 +8,9 @@ A professional Flask-based website for promoting small business web development
- 📱 **Responsive Design**: Modern, mobile-friendly interface - 📱 **Responsive Design**: Modern, mobile-friendly interface
- 🎨 **Professional UI**: Clean, modern design with Bootstrap 5 - 🎨 **Professional UI**: Clean, modern design with Bootstrap 5
- 🐳 **Docker Support**: Easy containerization and deployment - 🐳 **Docker Support**: Easy containerization and deployment
-**Fast Performance**: Optimized for speed and performance -**Fast Performance**: Optimized for speed and performance with minified assets
- 🔧 **No Database Required**: Simple, lightweight setup - 🔧 **No Database Required**: Simple, lightweight setup
- 🚀 **Asset Optimization**: CSS/JS minification with cache busting
## Pages ## Pages
@ -27,16 +28,45 @@ A professional Flask-based website for promoting small business web development
pip install -r requirements.txt pip install -r requirements.txt
``` ```
2. **Run the application:** 2. **Build assets (optional, for production-like experience):**
```bash
make build
```
3. **Run the application:**
```bash ```bash
python app.py python app.py
``` ```
3. **Access the website:** 4. **Access the website:**
Open your browser and go to `http://localhost:5000` Open your browser and go to `http://localhost:5000`
### Using Make Commands
The project includes a Makefile for common tasks:
```bash
make help # Show all available commands
make install # Install dependencies
make build # Build minified assets
make clean # Clean old assets
make run # Run development server
make run-prod # Run production server
make deploy # Clean and build assets
make dev-setup # Setup development environment
# Docker commands
make docker-run # Run production Docker container
make docker-run-dev # Run development Docker container
make docker-stop # Stop production container
make docker-stop-dev # Stop development container
make docker-logs # View production logs
make docker-logs-dev # View development logs
```
### Docker Deployment ### Docker Deployment
#### Production Mode (Default)
1. **Build and run with Docker Compose:** 1. **Build and run with Docker Compose:**
```bash ```bash
docker-compose up --build docker-compose up --build
@ -48,6 +78,25 @@ A professional Flask-based website for promoting small business web development
docker run -p 5000:5000 kobelly-website docker run -p 5000:5000 kobelly-website
``` ```
#### Development Mode
For local development with Docker (with live reload and debug mode):
1. **Build and run development container:**
```bash
docker-compose -f docker-compose.dev.yml up --build
```
2. **Or use Make commands:**
```bash
make docker-run-dev # Start development container
make docker-logs-dev # View development logs
make docker-stop-dev # Stop development container
```
**Development vs Production:**
- **Development**: Debug mode enabled, live code reloading, unminified assets
- **Production**: Debug disabled, minified assets, optimized performance
## Project Structure ## Project Structure
``` ```
@ -56,7 +105,17 @@ Kobelly/
├── requirements.txt # Python dependencies ├── requirements.txt # Python dependencies
├── Dockerfile # Docker configuration ├── Dockerfile # Docker configuration
├── docker-compose.yml # Docker Compose configuration ├── docker-compose.yml # Docker Compose configuration
├── Makefile # Build and deployment commands
├── build_assets.py # Asset minification script
├── babel.cfg # Babel configuration for translations ├── babel.cfg # Babel configuration for translations
├── static/ # Static assets
│ ├── css/ # CSS files
│ │ ├── main.css # Main stylesheet
│ │ └── main.min.css # Minified CSS (generated)
│ ├── js/ # JavaScript files
│ │ ├── main.js # Main script
│ │ └── main.min.js # Minified JS (generated)
│ └── images/ # Images and icons
├── templates/ # HTML templates ├── templates/ # HTML templates
│ ├── base.html # Base template with navigation │ ├── base.html # Base template with navigation
│ ├── index.html # Homepage │ ├── index.html # Homepage
@ -111,12 +170,62 @@ Users can switch languages using the language selector in the navigation bar. Th
pybabel compile -d translations pybabel compile -d translations
``` ```
## Asset Optimization
The project includes automatic CSS and JavaScript minification with cache busting for optimal performance.
### Asset Building
**Development Mode:**
- Uses unminified assets for easier debugging
- Assets are served directly from source files
**Production Mode:**
- Automatically serves minified assets
- Cache busting prevents browser caching issues
- Assets are optimized for faster loading
### Building Assets
```bash
# Build minified assets
make build
# Clean old cache-busted assets
make clean
# Full deployment (clean + build)
make deploy
```
### Asset Files
- **CSS**: `static/css/main.css` → `static/css/main.min.css`
- **JavaScript**: `static/js/main.js` → `static/js/main.min.js`
The build process creates cache-busted versions with MD5 hashes in the filename to ensure browsers always load the latest version.
## Configuration ## Configuration
### Environment Variables ### Environment Variables
- `SECRET_KEY`: Secret key for Flask sessions (default: 'your-secret-key-change-in-production') - `SECRET_KEY`: Secret key for Flask sessions (default: 'your-secret-key-change-in-production')
- `FLASK_ENV`: Flask environment (development/production) - `FLASK_ENV`: Flask environment (development/production)
- `FLASK_DEBUG`: Debug mode (0=disabled, 1=enabled, defaults to 1 in development)
**Environment Modes:**
**Development (Local):**
```bash
FLASK_ENV=development
FLASK_DEBUG=1
```
**Production (Docker):**
```bash
FLASK_ENV=production
FLASK_DEBUG=0
```
### Production Deployment ### Production Deployment
@ -135,6 +244,7 @@ For production deployment:
- **Internationalization**: Flask-Babel - **Internationalization**: Flask-Babel
- **Containerization**: Docker, Docker Compose - **Containerization**: Docker, Docker Compose
- **Styling**: Custom CSS with CSS variables - **Styling**: Custom CSS with CSS variables
- **Asset Management**: Flask-Assets with CSS/JS minification
## Browser Support ## Browser Support

42
app.py
View File

@ -1,4 +1,5 @@
from flask import Flask, render_template, request, session, redirect, url_for, make_response from flask import Flask, render_template, request, session, redirect, url_for, make_response
from flask_assets import Environment, Bundle
from translation_manager import init_app, translate, get_current_language, create_language_selector, set_language from translation_manager import init_app, translate, get_current_language, create_language_selector, set_language
import os import os
from datetime import datetime from datetime import datetime
@ -7,6 +8,29 @@ from xml.etree import ElementTree as ET
app = Flask(__name__) app = Flask(__name__)
app.secret_key = os.environ.get('SECRET_KEY', 'your-secret-key-change-in-production') app.secret_key = os.environ.get('SECRET_KEY', 'your-secret-key-change-in-production')
# Initialize Flask-Assets
assets = Environment(app)
assets.url = app.static_url_path
# Configure assets for production
if not app.debug:
assets.auto_build = False
assets.cache = True
assets.manifest = 'file'
# Register asset bundles
css_bundle = assets.register('css_all', Bundle(
'css/main.css',
filters='cssmin',
output='css/main.min.css'
))
js_bundle = assets.register('js_all', Bundle(
'js/main.js',
filters='jsmin',
output='js/main.min.js'
))
# Initialize the translation system # Initialize the translation system
init_app(app) init_app(app)
@ -90,5 +114,21 @@ def set_language_route(language):
return redirect(request.referrer or url_for('index')) return redirect(request.referrer or url_for('index'))
return redirect(url_for('index')) return redirect(url_for('index'))
@app.route('/build-assets')
def build_assets():
"""Build minified assets with cache busting"""
if app.debug:
try:
# Build assets
css_bundle.build()
js_bundle.build()
return {'status': 'success', 'message': 'Assets built successfully'}
except Exception as e:
return {'status': 'error', 'message': str(e)}, 500
else:
return {'status': 'error', 'message': 'Asset building only available in debug mode'}, 403
if __name__ == '__main__': if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000) # Check environment variables for debug mode
debug_mode = os.environ.get('FLASK_DEBUG', '1') == '1' and os.environ.get('FLASK_ENV') != 'production'
app.run(debug=debug_mode, host='0.0.0.0', port=5000)

143
build_assets.py Normal file
View File

@ -0,0 +1,143 @@
#!/usr/bin/env python3
"""
Asset build script for Kobelly Web Solutions
Handles CSS and JS minification with cache busting
"""
import os
import hashlib
import shutil
from datetime import datetime
from cssmin import cssmin
from jsmin import jsmin
def get_file_hash(filepath):
"""Generate MD5 hash of file content for cache busting"""
with open(filepath, 'rb') as f:
return hashlib.md5(f.read()).hexdigest()[:8]
def minify_css(input_file, output_file):
"""Minify CSS file"""
try:
with open(input_file, 'r', encoding='utf-8') as f:
css_content = f.read()
minified_css = cssmin(css_content)
with open(output_file, 'w', encoding='utf-8') as f:
f.write(minified_css)
print(f"✓ CSS minified: {input_file}{output_file}")
return True
except Exception as e:
print(f"✗ Error minifying CSS {input_file}: {e}")
return False
def minify_js(input_file, output_file):
"""Minify JavaScript file"""
try:
with open(input_file, 'r', encoding='utf-8') as f:
js_content = f.read()
minified_js = jsmin(js_content)
with open(output_file, 'w', encoding='utf-8') as f:
f.write(minified_js)
print(f"✓ JS minified: {input_file}{output_file}")
return True
except Exception as e:
print(f"✗ Error minifying JS {input_file}: {e}")
return False
def create_cache_busted_assets():
"""Create cache-busted versions of assets"""
static_dir = 'static'
css_dir = os.path.join(static_dir, 'css')
js_dir = os.path.join(static_dir, 'js')
# Ensure directories exist
os.makedirs(css_dir, exist_ok=True)
os.makedirs(js_dir, exist_ok=True)
# CSS files
css_files = [
('main.css', 'main.min.css')
]
# JS files
js_files = [
('main.js', 'main.min.js')
]
print("🔨 Building assets...")
print(f"📅 Build time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print()
# Process CSS files
for input_file, output_file in css_files:
input_path = os.path.join(css_dir, input_file)
output_path = os.path.join(css_dir, output_file)
if os.path.exists(input_path):
if minify_css(input_path, output_path):
# Generate cache-busted filename
file_hash = get_file_hash(output_path)
cache_busted_name = f"main.{file_hash}.min.css"
cache_busted_path = os.path.join(css_dir, cache_busted_name)
# Copy minified file to cache-busted version
shutil.copy2(output_path, cache_busted_path)
print(f"✓ Cache-busted CSS created: {cache_busted_name}")
else:
print(f"⚠️ CSS file not found: {input_path}")
print()
# Process JS files
for input_file, output_file in js_files:
input_path = os.path.join(js_dir, input_file)
output_path = os.path.join(js_dir, output_file)
if os.path.exists(input_path):
if minify_js(input_path, output_path):
# Generate cache-busted filename
file_hash = get_file_hash(output_path)
cache_busted_name = f"main.{file_hash}.min.js"
cache_busted_path = os.path.join(js_dir, cache_busted_name)
# Copy minified file to cache-busted version
shutil.copy2(output_path, cache_busted_path)
print(f"✓ Cache-busted JS created: {cache_busted_name}")
else:
print(f"⚠️ JS file not found: {input_path}")
print()
print("🎉 Asset build completed!")
def clean_old_assets():
"""Clean old cache-busted assets"""
static_dir = 'static'
css_dir = os.path.join(static_dir, 'css')
js_dir = os.path.join(static_dir, 'js')
# Remove old cache-busted files
for directory in [css_dir, js_dir]:
if os.path.exists(directory):
for filename in os.listdir(directory):
if filename.startswith('main.') and filename.endswith('.min.css') or filename.endswith('.min.js'):
if not filename in ['main.min.css', 'main.min.js']: # Keep the base minified files
filepath = os.path.join(directory, filename)
os.remove(filepath)
print(f"🗑️ Removed old asset: {filename}")
if __name__ == '__main__':
import sys
if len(sys.argv) > 1 and sys.argv[1] == 'clean':
print("🧹 Cleaning old assets...")
clean_old_assets()
print("✓ Cleanup completed!")
else:
clean_old_assets()
create_cache_busted_assets()

23
docker-compose.dev.yml Normal file
View File

@ -0,0 +1,23 @@
version: '3.8'
services:
web:
build: .
ports:
- "10332:5000"
environment:
- FLASK_ENV=development
- FLASK_DEBUG=1
- SECRET_KEY=dev-secret-key-change-in-production
restart: unless-stopped
volumes:
- .:/app
- /app/static/css/main.min.css
- /app/static/js/main.min.js
networks:
- default
- nginx-proxy-manager_default
networks:
nginx-proxy-manager_default:
external: true

View File

@ -6,8 +6,9 @@ services:
ports: ports:
- "10332:5000" - "10332:5000"
environment: environment:
- FLASK_ENV=development - FLASK_ENV=production
- SECRET_KEY=your-secret-key-change-in-production - SECRET_KEY=your-secret-key-change-in-production
- FLASK_DEBUG=0
restart: unless-stopped restart: unless-stopped
networks: networks:
- default - default

View File

@ -1,5 +1,8 @@
Flask==2.3.3 Flask==2.3.3
Werkzeug==2.3.7 Werkzeug==2.3.7
Flask-Assets==2.1.0
cssmin==0.2.0
jsmin==3.0.1
click==8.1.7 click==8.1.7
blinker==1.6.3 blinker==1.6.3
# Translation system - JSON-based, no compilation needed # Translation system - JSON-based, no compilation needed

View File

@ -37,7 +37,9 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" /> <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}"> {% assets "css_all" %}
<link rel="stylesheet" href="{{ ASSET_URL }}">
{% endassets %}
<!-- Structured Data (JSON-LD) --> <!-- Structured Data (JSON-LD) -->
<script type="application/ld+json"> <script type="application/ld+json">
@ -187,6 +189,8 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script> <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script src="{{ url_for('static', filename='js/main.js') }}"></script> {% assets "js_all" %}
<script src="{{ ASSET_URL }}"></script>
{% endassets %}
</body> </body>
</html> </html>