-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Feature Design: SPA Engine
Target Version: v1.3
Status: Draft
This document outlines the design specifications for adding a first-class Single Page Application (SPA) engine to Violetear. This allows building multi-view applications where navigation is handled instantly in the browser without page reloads, while maintaining full Server-Side Rendering (SSR) capability for the initial load.
Objective
Provide a Pythonic abstraction for defining client-side routes, views, and navigation logic. The framework should handle the complexity of the History API, DOM reconciliation, and deep-linking support automatically.
1. New Module: violetear.spa
We will introduce violetear.spa to contain the shell and routing logic.
1.1 The SPA Class (The Shell)
Inherits from Document. It acts as the container for the entire application.
class SPA(Document):
def __init__(self, title: str, base_url: str = "/"):
super().__init__(title=title)
self.routes = {} # Map[path, Component]
self.base_url = base_url
def page(self, path: str, component: Callable[[], Element]):
"""Registers a view for a specific route."""
self.routes[path] = component
return self
def render(self, initial_path: str = "/") -> str:
"""
Renders the Shell.
Logic:
1. Iterate through all registered pages.
2. Render each one into a container <div id="page-{hash}">.
3. Set 'display: block' ONLY for the page matching 'initial_path'.
4. Set 'display: none' for all others.
5. Inject the client-side router script.
"""
pass1.2 The Link Component
A helper to create navigation links that hook into the client-side router instead of triggering a browser refresh.
class Link(Element):
def __init__(self, text: str, to: str, **kwargs):
super().__init__("a", text=text, href=to, **kwargs)
# Mark it so the Router knows to intercept the click
self.attrs(data_spa_link="true")2. Client-Side Runtime (violetear/spa_client.py)
This Python module will be bundled into the client (injected by App). It manages the browser state.
2.1 The Router Class
class Router:
def __init__(self, route_map: dict):
self.routes = route_map # Map[path, div_id]
# Listen for Back/Forward buttons
window.addEventListener("popstate", self.handle_popstate)
# Listen for Link clicks (Event Delegation)
document.body.addEventListener("click", self.handle_link_click)
def navigate(self, path: str):
"""
1. Update window.history.pushState(path).
2. Call self.render(path).
"""
pass
def render(self, path: str):
"""
1. Find the target div ID for this path.
2. Hide all other page divs.
3. Show target div.
"""
pass3. Server-Side Integration (violetear.app.App)
We need a way to serve the SPA shell regardless of which sub-route the user requests (Deep Linking support).
3.1 spa_route Decorator
def spa_route(self, path: str):
"""
Registers a catch-all route for an SPA.
Example: @app.spa_route("/dashboard/{path:path}")
"""
def decorator(func):
# 1. Register route with FastAPI (catch-all)
# 2. When called, pass 'path' to the user function
# 3. Expect user function to return an SPA instance
# 4. Call spa.render(initial_path=path) to ensure correct SSR state
pass
return decorator4. Usage Example
dashboard = SPA(title="Admin Panel", base_url="/admin")
# Define Views
def Home():
return Element("div").add(
Element("h1", text="Dashboard"),
Link(text="Settings", to="/admin/settings")
)
def Settings():
return Element("div").add(
Element("h1", text="Settings"),
Link(text="Back", to="/admin")
)
# Register Views
dashboard.page("/", Home)
dashboard.page("/settings", Settings)
# Server Mount
@app.spa_route("/admin/{path:path}")
def serve_dashboard(request, path: str):
return dashboard5. Task List
- Create
violetear.spamodule withSPAandLinkclasses. - Implement
violetear/spa_client.py(Client-side Router logic usingvioletear.dom). - Add
spa_routemethod toAppclass for catch-all routing. - Update
App._generate_bundleto include thespa_clientmodule if an SPA is detected.