add minifying, cache busting, and auto prod and dev mode
This commit is contained in:
@ -9,6 +9,13 @@ RUN pip install --no-cache-dir -r requirements.txt
|
||||
# Copy all application files
|
||||
COPY . .
|
||||
|
||||
# Build assets for production
|
||||
RUN python build_assets.py
|
||||
|
||||
# Set production environment
|
||||
ENV FLASK_ENV=production
|
||||
ENV FLASK_DEBUG=0
|
||||
|
||||
# Ensure proper permissions
|
||||
RUN chmod +x app.py
|
||||
|
||||
|
||||
50
Makefile
Normal file
50
Makefile
Normal 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
116
README.md
@ -8,8 +8,9 @@ A professional Flask-based website for promoting small business web development
|
||||
- 📱 **Responsive Design**: Modern, mobile-friendly interface
|
||||
- 🎨 **Professional UI**: Clean, modern design with Bootstrap 5
|
||||
- 🐳 **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
|
||||
- 🚀 **Asset Optimization**: CSS/JS minification with cache busting
|
||||
|
||||
## Pages
|
||||
|
||||
@ -27,16 +28,45 @@ A professional Flask-based website for promoting small business web development
|
||||
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
|
||||
python app.py
|
||||
```
|
||||
|
||||
3. **Access the website:**
|
||||
4. **Access the website:**
|
||||
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
|
||||
|
||||
#### Production Mode (Default)
|
||||
1. **Build and run with Docker Compose:**
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
#### 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
|
||||
|
||||
```
|
||||
@ -56,7 +105,17 @@ Kobelly/
|
||||
├── requirements.txt # Python dependencies
|
||||
├── Dockerfile # Docker configuration
|
||||
├── docker-compose.yml # Docker Compose configuration
|
||||
├── Makefile # Build and deployment commands
|
||||
├── build_assets.py # Asset minification script
|
||||
├── 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
|
||||
│ ├── base.html # Base template with navigation
|
||||
│ ├── index.html # Homepage
|
||||
@ -111,12 +170,62 @@ Users can switch languages using the language selector in the navigation bar. Th
|
||||
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
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `SECRET_KEY`: Secret key for Flask sessions (default: 'your-secret-key-change-in-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
|
||||
|
||||
@ -135,6 +244,7 @@ For production deployment:
|
||||
- **Internationalization**: Flask-Babel
|
||||
- **Containerization**: Docker, Docker Compose
|
||||
- **Styling**: Custom CSS with CSS variables
|
||||
- **Asset Management**: Flask-Assets with CSS/JS minification
|
||||
|
||||
## Browser Support
|
||||
|
||||
|
||||
42
app.py
42
app.py
@ -1,4 +1,5 @@
|
||||
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
|
||||
import os
|
||||
from datetime import datetime
|
||||
@ -7,6 +8,29 @@ from xml.etree import ElementTree as ET
|
||||
app = Flask(__name__)
|
||||
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
|
||||
init_app(app)
|
||||
|
||||
@ -90,5 +114,21 @@ def set_language_route(language):
|
||||
return redirect(request.referrer or 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__':
|
||||
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
143
build_assets.py
Normal 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
23
docker-compose.dev.yml
Normal 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
|
||||
@ -6,8 +6,9 @@ services:
|
||||
ports:
|
||||
- "10332:5000"
|
||||
environment:
|
||||
- FLASK_ENV=development
|
||||
- FLASK_ENV=production
|
||||
- SECRET_KEY=your-secret-key-change-in-production
|
||||
- FLASK_DEBUG=0
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- default
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
Flask==2.3.3
|
||||
Werkzeug==2.3.7
|
||||
Flask-Assets==2.1.0
|
||||
cssmin==0.2.0
|
||||
jsmin==3.0.1
|
||||
click==8.1.7
|
||||
blinker==1.6.3
|
||||
# Translation system - JSON-based, no compilation needed
|
||||
@ -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://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="{{ url_for('static', filename='css/main.css') }}">
|
||||
{% assets "css_all" %}
|
||||
<link rel="stylesheet" href="{{ ASSET_URL }}">
|
||||
{% endassets %}
|
||||
|
||||
<!-- Structured Data (JSON-LD) -->
|
||||
<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://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>
|
||||
</html>
|
||||
Reference in New Issue
Block a user