skills/psincraian/myfy/frontend-module

frontend-module

SKILL.md

FrontendModule - Server-Side Rendering

FrontendModule provides Jinja2 templates with Tailwind 4, DaisyUI 5, and Vite bundling.

Quick Start

from myfy.core import Application
from myfy.web import WebModule, route
from myfy.frontend import FrontendModule, render_template

app = Application()
app.add_module(WebModule())
app.add_module(FrontendModule(auto_init=True))  # Auto-scaffolds!

@route.get("/")
async def home() -> str:
    return render_template("home.html", title="Welcome")

Configuration

Environment variables use the MYFY_FRONTEND_ prefix:

Variable Default Description
MYFY_FRONTEND_ENVIRONMENT development Environment mode
MYFY_FRONTEND_ENABLE_VITE_DEV True Start Vite dev server
MYFY_FRONTEND_VITE_DEV_SERVER http://localhost:3001 Vite server URL
MYFY_FRONTEND_STATIC_URL_PREFIX /static Static files URL path
MYFY_FRONTEND_CACHE_STATIC_ASSETS True Enable static caching
MYFY_FRONTEND_CACHE_MAX_AGE 31536000 Cache max-age (1 year)
MYFY_FRONTEND_SHOW_VITE_LOGS False Show Vite console output

Module Options

FrontendModule(
    templates_dir="frontend/templates",  # Template directory
    static_dir="frontend/static",        # Static files directory
    auto_init=True,                      # Auto-scaffold if missing
)

Auto-Scaffolding

With auto_init=True, FrontendModule creates:

frontend/
  templates/
    base.html           # Base template with Tailwind
    home.html           # Example home page
  static/
    src/
      main.js           # Entry point
      main.css          # Tailwind imports
package.json            # Node dependencies
vite.config.js          # Vite configuration
tailwind.config.js      # Tailwind configuration

Template Rendering

Basic Rendering

from myfy.frontend import render_template

@route.get("/")
async def home() -> str:
    return render_template("home.html", title="Home")

With Context

@route.get("/users/{user_id}")
async def user_profile(user_id: int, session: AsyncSession) -> str:
    user = await session.get(User, user_id)
    return render_template("profile.html", user=user)

With Request Context

from starlette.requests import Request

@route.get("/dashboard")
async def dashboard(request: Request, user: User) -> str:
    return render_template(
        "dashboard.html",
        request=request,  # For url_for, CSRF, etc.
        user=user,
    )

Base Template

<!-- frontend/templates/base.html -->
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}My App{% endblock %}</title>
    {{ vite_assets("src/main.js") }}
</head>
<body class="min-h-screen bg-base-100">
    <div class="navbar bg-base-200">
        <a class="btn btn-ghost text-xl" href="/">My App</a>
    </div>
    <main class="container mx-auto px-4 py-8">
        {% block content %}{% endblock %}
    </main>
</body>
</html>

Page Template

<!-- frontend/templates/home.html -->
{% extends "base.html" %}

{% block title %}{{ title }} - My App{% endblock %}

{% block content %}
<div class="hero min-h-[50vh]">
    <div class="hero-content text-center">
        <div class="max-w-md">
            <h1 class="text-5xl font-bold">Hello, World!</h1>
            <p class="py-6">Welcome to your myfy application.</p>
            <button class="btn btn-primary">Get Started</button>
        </div>
    </div>
</div>
{% endblock %}

DaisyUI Components

DaisyUI 5 provides ready-to-use components:

<!-- Buttons -->
<button class="btn btn-primary">Primary</button>
<button class="btn btn-secondary">Secondary</button>
<button class="btn btn-outline">Outline</button>

<!-- Cards -->
<div class="card bg-base-100 shadow-xl">
    <div class="card-body">
        <h2 class="card-title">Card Title</h2>
        <p>Card content here.</p>
        <div class="card-actions justify-end">
            <button class="btn btn-primary">Action</button>
        </div>
    </div>
</div>

<!-- Forms -->
<div class="form-control">
    <label class="label">
        <span class="label-text">Email</span>
    </label>
    <input type="email" class="input input-bordered" />
</div>

<!-- Alerts -->
<div class="alert alert-success">
    <span>Success! Your action was completed.</span>
</div>

Theme Switching

<!-- Theme toggle -->
<label class="swap swap-rotate">
    <input type="checkbox" class="theme-controller" value="dark" />
    <svg class="swap-on w-6 h-6" fill="currentColor"><!-- sun icon --></svg>
    <svg class="swap-off w-6 h-6" fill="currentColor"><!-- moon icon --></svg>
</label>

Static Assets

Vite Helper

<!-- Loads JS and CSS with HMR in development -->
{{ vite_assets("src/main.js") }}

Manual Asset URLs

<img src="{{ asset_url('images/logo.png') }}" alt="Logo">

Development Workflow

  1. Start the application:

    myfy run
    

    Vite dev server starts automatically with HMR.

  2. Edit templates and CSS - changes reflect instantly.

  3. Build for production:

    npm run build
    

    Creates optimized assets in frontend/static/dist/.

Production Build

# Build assets
npm run build

# Set production mode
export MYFY_FRONTEND_ENVIRONMENT=production
export MYFY_FRONTEND_ENABLE_VITE_DEV=false

# Run application
myfy run

Assets are served from the built manifest with cache headers.

Custom Jinja Filters

from myfy.frontend import FrontendModule
from starlette.templating import Jinja2Templates

# After module initialization
templates: Jinja2Templates = container.get(Jinja2Templates)
templates.env.filters["currency"] = lambda x: f"${x:.2f}"

Use in templates:

<span>{{ price | currency }}</span>

Best Practices

  1. Use auto_init for new projects - Gets you started quickly
  2. Extend base.html - Keep consistent layout
  3. Use DaisyUI components - Pre-styled, accessible
  4. Enable caching in production - Set long cache max-age
  5. Build assets before deploy - Don't rely on Vite in production
  6. Use template inheritance - Keep templates DRY
  7. Pass request for url_for - Enables dynamic URL generation
Weekly Installs
4
Repository
psincraian/myfy
GitHub Stars
86
First Seen
Jan 29, 2026
Installed on
gemini-cli4
cline4
codebuddy4
github-copilot4
codex4
continue4