Flask Integration¶
Overview¶
The Flask bridge provides a simple way to serve wilco components from any Flask application. It exposes API endpoints via a Flask Blueprint for listing, fetching, and getting metadata for components.
Installation¶
Install wilco with Flask support using the optional extra:
pip install wilco[flask]
This installs wilco with Flask (>= 3.0.0). For development, you’ll also want a WSGI server like gunicorn or werkzeug’s built-in server.
Quick start¶
Here’s a minimal example that serves components from a directory:
from pathlib import Path
from flask import Flask
from wilco import ComponentRegistry
from wilco.bridges.flask import create_blueprint
app = Flask(__name__)
# Create a registry pointing to your components
registry = ComponentRegistry(Path("./components"))
# Register the wilco blueprint
app.register_blueprint(create_blueprint(registry), url_prefix="/api")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8200)
This creates three endpoints:
GET /api/bundles- List available componentsGET /api/bundles/{name}.js- Get bundled JavaScriptGET /api/bundles/{name}/metadata- Get component metadata
API reference¶
create_blueprint¶
from wilco.bridges.flask import create_blueprint
def create_blueprint(
registry: ComponentRegistry,
build_dir: Path | None = None,
) -> Blueprint:
"""Create a Flask Blueprint with component serving endpoints.
Args:
registry: The component registry to serve components from.
build_dir: Optional path to pre-built bundles directory.
When provided, serves pre-built bundles from the manifest
and falls back to live bundling for missing components.
Returns:
A Flask Blueprint that can be registered on any app.
"""
The returned blueprint provides the following endpoints:
GET /bundles¶
List all available component bundles.
Response:
[
{"name": "counter"},
{"name": "product_card"},
{"name": "ui.button"}
]
GET /bundles/{name}.js¶
Get the bundled JavaScript for a component.
Parameters:
name: Component name (e.g.,counter,ui.button)
Response:
Content-Type:
application/javascriptCache-Control:
public, max-age=31536000, immutable
The response contains the bundled ESM JavaScript with inline source maps.
Errors:
404: Component not found422: Invalid component name
GET /bundles/{name}/metadata¶
Get metadata for a component, including its JSON schema and content hash.
Parameters:
name: Component name
Response:
{
"title": "Counter",
"description": "A simple counter component",
"version": "1.0.0",
"properties": {
"initialValue": {"type": "number", "default": 0},
"step": {"type": "number", "default": 1}
},
"hash": "abc123def456"
}
The hash field can be used for cache busting on the frontend.
Errors:
404: Component not found422: Invalid component name
Component registry¶
Multiple sources¶
You can register components from multiple directories:
registry = ComponentRegistry()
# Add components from multiple sources
registry.add_source(Path("./shared_components"))
registry.add_source(Path("./app_components"), prefix="app")
app.register_blueprint(create_blueprint(registry), url_prefix="/api")
With a prefix, components are namespaced:
./shared_components/button/becomesbutton./app_components/header/becomesapp:header
Caching¶
The Flask bridge returns long-lived cache headers for component bundles:
Cache-Control: public, max-age=31536000, immutable
To enable cache busting:
Fetch the metadata endpoint to get the
hashAppend the hash as a query parameter:
/api/bundles/counter.js?abc123
The hash changes whenever the component source changes.
Template-based rendering¶
For server-rendered applications using Jinja2 templates, you can embed wilco components directly in your HTML templates using a widget helper:
import html
import json
import uuid
from markupsafe import Markup
class WilcoComponentWidget:
def __init__(self, component_name: str, props: dict = None, api_base: str = "/api"):
self.component_name = component_name
self.props = props or {}
self.api_base = api_base
def render(self) -> str:
props_json = html.escape(json.dumps(self.props), quote=True)
container_id = f"wilco-{uuid.uuid4().hex[:8]}"
return f'''<div id="{container_id}"
data-wilco-component="{self.component_name}"
data-wilco-props="{props_json}"
data-wilco-api="{self.api_base}">
Loading...
</div>'''
def __html__(self):
return self.render()
Use in a view:
@app.route("/")
def product_list():
products = Product.query.all()
widget = WilcoComponentWidget("store:product_list", props={
"products": [{"name": p.name, "price": float(p.price)} for p in products]
})
return render_template("products.html", widget=widget)
In your template:
<!DOCTYPE html>
<html>
<body>
{{ widget }}
<script src="/wilco-static/wilco/loader.js" defer></script>
</body>
</html>
Static files setup¶
Serve the wilco loader script alongside your application’s static files:
from wilco.bridges.base import STATIC_DIR as WILCO_STATIC_DIR
@app.route("/wilco-static/<path:filename>")
def wilco_static(filename):
return send_from_directory(str(WILCO_STATIC_DIR), filename)
Live preview in Flask-Admin¶
The Flask example includes live preview functionality that shows real-time component updates as admin users edit forms.
Architecture¶
The live preview system consists of four parts:
Script injection - An
after_requesthook injects preview scripts into admin pagesadmin-preview-inject.js - Creates the two-column layout UI
live-loader-flask.js - Handles form validation and preview updates
Validation endpoints - Server-side form validation returning component props
Script injection¶
Use Flask’s after_request hook to inject scripts into admin HTML pages:
INJECT_SCRIPTS = """
<script src="/wilco-static/wilco/loader.js" defer></script>
<script src="/static/wilco/admin-preview-inject.js" defer></script>
<script src="/static/wilco/live-loader-flask.js" defer></script>
</body>"""
@app.after_request
def inject_preview_scripts(response):
if (
request.path.startswith("/admin")
and response.content_type
and "text/html" in response.content_type
):
html = response.get_data(as_text=True)
if "</body>" in html:
html = html.replace("</body>", INJECT_SCRIPTS)
response.set_data(html)
return response
Validation endpoint¶
Create a validation endpoint that receives form data and returns component props:
from flask import jsonify, request
from .models import Product
def get_preview_props(form_data, product=None):
"""Convert form data to component props."""
price = form_data.get("price", 0)
if isinstance(price, str):
try:
price = float(price.replace(",", ".")) if price else 0
except ValueError:
price = 0
image = form_data.get("image", "")
if image:
image_url = f"/media/{image}"
elif product and product.image:
image_url = f"/media/{product.image}"
else:
image_url = "https://picsum.photos/seed/placeholder/600/400"
return {
"name": form_data.get("name", ""),
"price": float(price),
"description": form_data.get("description", "") or "",
"imageUrl": image_url,
}
def validate_preview(product_id=None):
"""Validate form data and return props for live preview."""
product = None
if product_id:
product = Product.query.get(product_id)
data = dict(request.form)
errors = {}
if not data.get("name"):
errors["name"] = ["Name is required"]
if not data.get("price"):
errors["price"] = ["Price is required"]
if errors:
return jsonify({"success": False, "errors": errors})
props = get_preview_props(data, product)
return jsonify({"success": True, "props": props})
# Register routes
app.add_url_rule("/admin/product/preview", "product_preview",
validate_preview, methods=["POST"])
app.add_url_rule("/admin/product/<int:product_id>/preview", "product_preview_edit",
validate_preview, methods=["POST"])
Production deployment¶
For production, you can pre-compile component bundles to avoid runtime esbuild dependency and serve them as static files.
Pre-building bundles¶
Use the wilco build CLI to pre-compile components:
wilco build --output dist/wilco/ --components-dir ./components --prefix store
This generates:
dist/wilco/bundles/{name}.{hash}.js— hashed bundle filesdist/wilco/manifest.json— component-to-file mapping
Using pre-built bundles¶
Pass the build_dir parameter to create_blueprint:
from pathlib import Path
from wilco.bridges.flask import create_blueprint
build_dir = Path("./dist/wilco")
# Serves pre-built bundles when available, falls back to live bundling
app.register_blueprint(
create_blueprint(registry, build_dir=build_dir),
url_prefix="/api",
)
You can also set the WILCO_BUILD_DIR environment variable and resolve it
programmatically:
from wilco.manifest import resolve_build_dir
build_dir = resolve_build_dir(Path("./dist/wilco"))
In static mode, the API bundle endpoint returns 404 and clients load bundles from static file URLs instead.
Example application¶
A complete example application is available in the examples/flask/ directory. It demonstrates:
Jinja2 template-based rendering with wilco components
Flask-Admin for product management with live preview
SQLAlchemy database models
Shared components from
examples/common/components/
To run the example:
cd examples/flask
make setup # Install deps, create database, load fixtures
make start # Run development server
Visit http://localhost:8200 for the store, http://localhost:8200/admin for the admin.