This guide covers the internal architecture, development setup, and contribution guidelines for Flask-Vite.
- Architecture Overview
- Development Setup
- Code Structure
- Extension Implementation
- Testing
- Contributing
- Release Process
Flask-Vite follows the Flask extension pattern and integrates with Vite's development and build processes.
src/flask_vite/
├── __init__.py # Public API exports
├── extension.py # Main Vite extension class
├── cli.py # Flask CLI commands
├── npm.py # NPM process wrapper
├── tags.py # Template tag generation
└── starter/ # Default Vite project template
-
Development Mode (
app.debug=True):{{ vite_tags() }}generates<script>tags pointing tolocalhost:3000- Vite dev server serves assets with hot reload
- Flask serves the application on a different port
-
Production Mode (
app.debug=False):{{ vite_tags() }}generates<script>tags pointing to built assets- Flask serves built assets via
/_vite/<filename>route - Assets are fingerprinted and cached
- Python 3.9+
- Node.js 16+
- npm or pnpm
# Clone the repository
git clone https://github.com/abilian/flask-vite.git
cd flask-vite
# Install Python dependencies
uv sync
# Install pre-commit hooks
uv run pre-commit install
# Run tests
uv run pytest
# Run the demo
cd demo
python app.py# Code formatting
uv run ruff format .
# Linting
uv run ruff check . --fix
# Type checking
uv run mypy src tests
# Run all checks
make lint
# Run tests with coverage
make test-with-coverageThe Vite class is the main extension:
class Vite:
def __init__(self, app: Flask | None = None, vite_routes_host: str | None = None):
"""Initialize the extension."""
def init_app(self, app: Flask, vite_routes_host: str | None = None):
"""Configure the extension for the Flask app."""
def after_request(self, response: Response):
"""Auto-inject vite tags if VITE_AUTO_INSERT is enabled."""
def vite_static(self, filename):
"""Serve built Vite assets in production."""Flask CLI commands are implemented using Click:
@click.group()
def vite():
"""Perform Vite operations."""
@command()
@with_appcontext
def init():
"""Init the vite/ directory (if it doesn't exist)"""Handles subprocess calls to npm:
class NPM:
def __init__(self, cwd: str, npm_bin_path: str = "npm"):
self.cwd = cwd
self.npm_bin_path = npm_bin_path
def run(self, *args):
"""Execute npm command with given arguments."""Generates appropriate HTML tags based on Flask's debug mode:
def make_tag() -> str:
"""Generate script/link tags for Vite assets."""
if current_app.debug:
return make_dev_tag() # Point to Vite dev server
else:
return make_prod_tag() # Point to built assetsFlask-Vite follows Flask's extension patterns:
# Registration
app.extensions["vite"] = self
# Configuration
config = app.config
vite_folder_path = config.get("VITE_FOLDER_PATH", "vite")
# Route registration
app.route("/_vite/<path:filename>", endpoint="vite.static")(self.vite_static)
# Template global registration
app.template_global("vite_tags")(make_tag)For applications using host_matching=True:
def _validate_and_configure_vite_routes_host(self, app, vite_routes_host):
"""Configure host-specific routing for vite assets."""
if vite_routes_host == "*":
# Use wildcard to serve from same host as request
vite_routes_host = VITE_ROUTES_HOST_WILDCARD_VARIABLE
@app.url_defaults
def inject_vite_routes_host_if_required(endpoint, values):
if app.url_map.is_endpoint_expecting(endpoint, "vite_routes_host"):
values.setdefault("vite_routes_host", request.host)When VITE_AUTO_INSERT=True:
def after_request(self, response: Response):
"""Automatically inject vite tags into HTML responses."""
if response.status_code != OK or not response.mimetype.startswith("text/html"):
return response
body = b"".join(response.response).decode()
tag = make_tag()
body = body.replace("</head>", f"{tag}\n</head>")
response.response = [body.encode("utf8")]
response.content_length = len(response.response[0])
return responsetests/
├── conftest.py # Pytest configuration and fixtures
├── test_flask_vite.py # Main extension tests
└── __init__.py
- Extension Registration: Test proper Flask extension setup
- CLI Commands: Test all CLI command functionality
- Template Tags: Test tag generation in dev/prod modes
- Auto-injection: Test automatic HTML modification
- Host Matching: Test multi-host configuration
- Error Handling: Test error scenarios and validation
# All tests
uv run pytest
# With coverage
uv run pytest --cov flask_vite
# Specific test
uv run pytest tests/test_flask_vite.py::test_extension_registration
# Multiple Python versions
toxCommon fixtures in conftest.py:
@pytest.fixture
def app():
"""Create Flask app for testing."""
app = Flask(__name__)
app.config['TESTING'] = True
return app
@pytest.fixture
def vite_app(app):
"""Flask app with Vite extension."""
vite = Vite(app)
return app- Follow PEP 8 and project's ruff configuration
- Use type hints for all function signatures
- Write docstrings for public APIs
- Prefer f-strings for string formatting
- Use conventional commits:
feat:,fix:,docs:, etc. - Write clear, descriptive commit messages
- Keep commits atomic (one logical change per commit)
- Fork the repository
- Create a feature branch:
git checkout -b feat/my-feature - Make changes with appropriate tests
- Run the test suite:
make test - Run code quality checks:
make lint - Submit a pull request with clear description
When adding features:
- Design: Consider the API and how it fits with Flask patterns
- Implement: Write the feature with appropriate error handling
- Test: Add comprehensive tests covering edge cases
- Document: Update user and developer documentation
- Demo: Consider adding an example to the demo app
- Follow semantic versioning (SemVer)
- Update version in
pyproject.toml - Update
CHANGELOG.mdwith changes
# 1. Ensure all tests pass
make test-all
# 2. Update changelog
git-cliff > CHANGELOG.md
# 3. Commit changes
git commit -m "chore: release vX.Y.Z"
# 4. Create tag
git tag vX.Y.Z
# 5. Push changes and tags
git push --tags
# 6. Build and publish
make publishThe project uses GitHub Actions for:
- Testing: Run tests on multiple Python versions
- Linting: Check code quality and formatting
- Type Checking: Run mypy for type safety
- Publishing: Automatic PyPI publishing on tagged releases
Enable Flask debug mode to see detailed error messages:
app.debug = TrueAdd logging for debugging extension behavior:
import logging
logger = logging.getLogger(__name__)
def init_app(self, app: Flask):
logger.debug("Initializing Flask-Vite extension")- Template Tags Not Working: Check if extension is properly registered
- Assets Not Loading: Verify Vite dev server is running and reachable
- Import Errors: Ensure proper Python path and dependencies
- CLI Commands Failing: Check Flask application context
- Built assets are served with 1-year cache headers
- Assets are fingerprinted for cache busting
- Gzip compression should be handled by the web server
- Extension maintains minimal state
- No persistent connections to Vite dev server
- NPM processes are spawned only when needed
- Set
FLASK_ENV=production - Use a proper WSGI server (gunicorn, uWSGI)
- Configure web server to serve static assets
- Enable gzip compression for text assets