451 lines
11 KiB
Markdown
451 lines
11 KiB
Markdown
# TankStopp Templ Integration Guide
|
|
|
|
## Overview
|
|
|
|
This guide walks you through the process of migrating from traditional HTML templates to the new `a-h/templ` system in TankStopp. The migration provides better performance, type safety, and developer experience.
|
|
|
|
## Prerequisites
|
|
|
|
Before starting the migration, ensure you have:
|
|
|
|
- Go 1.21 or later installed
|
|
- Access to the TankStopp codebase
|
|
- Basic understanding of Go templates and HTML
|
|
- Familiarity with the existing codebase structure
|
|
|
|
## Step 1: Install Required Tools
|
|
|
|
### Install templ CLI
|
|
|
|
```bash
|
|
go install github.com/a-h/templ/cmd/templ@latest
|
|
```
|
|
|
|
### Install development tools (optional but recommended)
|
|
|
|
```bash
|
|
# Hot reload tool
|
|
go install github.com/cosmtrek/air@latest
|
|
|
|
# File watcher (for manual watch scripts)
|
|
# macOS
|
|
brew install entr
|
|
|
|
# Linux
|
|
sudo apt-get install entr
|
|
```
|
|
|
|
## Step 2: Update Dependencies
|
|
|
|
The templ dependency should already be in your `go.mod` file:
|
|
|
|
```go
|
|
require (
|
|
github.com/a-h/templ v0.3.906
|
|
// ... other dependencies
|
|
)
|
|
```
|
|
|
|
If not, add it:
|
|
|
|
```bash
|
|
go get github.com/a-h/templ@latest
|
|
go mod tidy
|
|
```
|
|
|
|
## Step 3: Generate Templ Templates
|
|
|
|
Generate the Go code from the templ templates:
|
|
|
|
```bash
|
|
# Using the build script
|
|
./scripts/build.sh generate
|
|
|
|
# Or using templ directly
|
|
templ generate ./internal/views/
|
|
|
|
# Or using the Makefile
|
|
make generate
|
|
```
|
|
|
|
## Step 4: Update Your Handlers
|
|
|
|
### Before (Old HTML Template System)
|
|
|
|
```go
|
|
// Old handler using html/template
|
|
func DashboardHandler(w http.ResponseWriter, r *http.Request) {
|
|
data := struct {
|
|
Title string
|
|
User *models.User
|
|
Stops []models.FuelStop
|
|
}{
|
|
Title: "Dashboard",
|
|
User: user,
|
|
Stops: stops,
|
|
}
|
|
|
|
tmpl := template.Must(template.ParseFiles("templates/dashboard.html"))
|
|
tmpl.Execute(w, data)
|
|
}
|
|
```
|
|
|
|
### After (New Templ System)
|
|
|
|
```go
|
|
// New handler using templ
|
|
func DashboardHandler(w http.ResponseWriter, r *http.Request) {
|
|
// Get data...
|
|
user := getUserFromContext(r.Context())
|
|
stops, _ := h.db.GetFuelStops(user.ID, "", "", "", "")
|
|
vehicles, _ := h.db.GetVehicles(user.ID)
|
|
|
|
// Render templ component
|
|
component := pages.DashboardPage(user, user.Username, stops, vehicles, len(stops), 0.0, 0.0, nil)
|
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
if err := component.Render(r.Context(), w); err != nil {
|
|
http.Error(w, "Failed to render dashboard", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
```
|
|
|
|
## Step 5: Update Route Registration
|
|
|
|
### Replace Old Template Handlers
|
|
|
|
Update your main router to use the new templ handlers:
|
|
|
|
```go
|
|
// main.go or router setup
|
|
func setupRoutes() *mux.Router {
|
|
r := mux.NewRouter()
|
|
|
|
// Create templ handlers
|
|
templHandlers := handlers.NewTemplHandlers(db, auth, currency)
|
|
|
|
// Authentication routes
|
|
r.HandleFunc("/login", templHandlers.LoginHandler).Methods("GET", "POST")
|
|
r.HandleFunc("/register", templHandlers.RegisterHandler).Methods("GET", "POST")
|
|
r.HandleFunc("/logout", templHandlers.LogoutHandler).Methods("POST")
|
|
|
|
// Protected routes (add auth middleware)
|
|
protected := r.PathPrefix("/").Subrouter()
|
|
protected.Use(authMiddleware)
|
|
|
|
protected.HandleFunc("/dashboard", templHandlers.DashboardHandler).Methods("GET")
|
|
protected.HandleFunc("/add", templHandlers.AddFuelStopHandler).Methods("GET", "POST")
|
|
protected.HandleFunc("/edit/{id}", templHandlers.EditFuelStopHandler).Methods("GET", "POST")
|
|
protected.HandleFunc("/vehicles", templHandlers.VehiclesHandler).Methods("GET")
|
|
protected.HandleFunc("/add-vehicle", templHandlers.AddVehicleHandler).Methods("GET", "POST")
|
|
protected.HandleFunc("/settings", templHandlers.SettingsHandler).Methods("GET")
|
|
|
|
// Static files
|
|
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
|
|
|
|
return r
|
|
}
|
|
```
|
|
|
|
## Step 6: Update Authentication Middleware
|
|
|
|
Ensure your auth middleware sets the user in the request context:
|
|
|
|
```go
|
|
func authMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Get session from cookie
|
|
cookie, err := r.Cookie("session")
|
|
if err != nil {
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
// Validate session and get user
|
|
user, err := auth.ValidateSession(cookie.Value)
|
|
if err != nil {
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
// Add user to request context
|
|
ctx := context.WithValue(r.Context(), "user", user)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
```
|
|
|
|
## Step 7: Update Main Application
|
|
|
|
Update your main application file to use the new routing:
|
|
|
|
```go
|
|
// main.go
|
|
func main() {
|
|
// Initialize database, auth, currency services
|
|
db := database.New("fuel_stops.db")
|
|
authService := auth.New(db)
|
|
currencyService := currency.New()
|
|
|
|
// Setup routes with new handlers
|
|
router := setupRoutes(db, authService, currencyService)
|
|
|
|
// Start server
|
|
log.Println("Server starting on :8080")
|
|
log.Fatal(http.ListenAndServe(":8080", router))
|
|
}
|
|
```
|
|
|
|
## Step 8: Build and Test
|
|
|
|
### Build the application
|
|
|
|
```bash
|
|
# Using the build script
|
|
./scripts/build.sh dev
|
|
|
|
# Or using the Makefile
|
|
make dev
|
|
|
|
# Or manual build
|
|
make generate
|
|
go build -o tankstopp ./cmd/main.go
|
|
```
|
|
|
|
### Run the application
|
|
|
|
```bash
|
|
# Direct run
|
|
./tankstopp
|
|
|
|
# Or with hot reload
|
|
make run-dev
|
|
|
|
# Or using air
|
|
air
|
|
```
|
|
|
|
### Test the migration
|
|
|
|
1. **Login/Registration**: Test user authentication flows
|
|
2. **Dashboard**: Verify fuel stops display correctly
|
|
3. **Add Fuel Stop**: Test form submission and validation
|
|
4. **Vehicle Management**: Test vehicle CRUD operations
|
|
5. **Settings**: Test user preference updates
|
|
6. **Responsive Design**: Test on different screen sizes
|
|
|
|
## Step 9: Clean Up Old Templates
|
|
|
|
Once everything is working, remove the old HTML templates:
|
|
|
|
```bash
|
|
# Backup old templates (optional)
|
|
mkdir -p backup
|
|
mv templates backup/
|
|
|
|
# Or remove directly
|
|
rm -rf templates/
|
|
```
|
|
|
|
## Step 10: Update Build Process
|
|
|
|
### Update CI/CD Pipeline
|
|
|
|
If you have a CI/CD pipeline, update it to include templ generation:
|
|
|
|
```yaml
|
|
# .github/workflows/build.yml
|
|
- name: Generate templ templates
|
|
run: |
|
|
go install github.com/a-h/templ/cmd/templ@latest
|
|
templ generate ./internal/views/
|
|
|
|
- name: Build application
|
|
run: go build -o tankstopp ./cmd/main.go
|
|
```
|
|
|
|
### Update Docker Build
|
|
|
|
If using Docker, update your Dockerfile:
|
|
|
|
```dockerfile
|
|
# Dockerfile
|
|
FROM golang:1.21-alpine AS builder
|
|
|
|
WORKDIR /app
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
|
|
# Install templ
|
|
RUN go install github.com/a-h/templ/cmd/templ@latest
|
|
|
|
COPY . .
|
|
|
|
# Generate templates and build
|
|
RUN templ generate ./internal/views/
|
|
RUN go build -o tankstopp ./cmd/main.go
|
|
|
|
FROM alpine:latest
|
|
RUN apk --no-cache add ca-certificates
|
|
WORKDIR /root/
|
|
COPY --from=builder /app/tankstopp .
|
|
COPY --from=builder /app/static ./static
|
|
CMD ["./tankstopp"]
|
|
```
|
|
|
|
## Development Workflow
|
|
|
|
### Daily Development
|
|
|
|
1. **Start development server**:
|
|
```bash
|
|
make run-dev
|
|
# or
|
|
air
|
|
```
|
|
|
|
2. **Make changes to templ files**:
|
|
- Edit `.templ` files in `internal/views/`
|
|
- Templates are automatically regenerated in watch mode
|
|
|
|
3. **Format code regularly**:
|
|
```bash
|
|
make format
|
|
```
|
|
|
|
### Adding New Pages
|
|
|
|
1. **Create templ template**:
|
|
```bash
|
|
# Create new template file
|
|
touch internal/views/pages/my_new_page.templ
|
|
```
|
|
|
|
2. **Define the template**:
|
|
```go
|
|
package pages
|
|
|
|
import (
|
|
"tankstopp/internal/views/components"
|
|
"tankstopp/internal/models"
|
|
)
|
|
|
|
templ MyNewPage(user *models.User, data MyData) {
|
|
@components.BaseLayout("My New Page", user, user.Username) {
|
|
@components.PageHeader("Subtitle", "My New Page")
|
|
|
|
<div class="page-body">
|
|
<div class="container-xl">
|
|
// Your content here
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
```
|
|
|
|
3. **Generate and create handler**:
|
|
```bash
|
|
make generate
|
|
```
|
|
|
|
4. **Add handler method**:
|
|
```go
|
|
func (h *TemplHandlers) MyNewPageHandler(w http.ResponseWriter, r *http.Request) {
|
|
user := getUserFromContext(r.Context())
|
|
// Get data...
|
|
|
|
component := pages.MyNewPage(user, data)
|
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
component.Render(r.Context(), w)
|
|
}
|
|
```
|
|
|
|
5. **Add route**:
|
|
```go
|
|
r.HandleFunc("/my-new-page", templHandlers.MyNewPageHandler).Methods("GET")
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
1. **Templates not updating**:
|
|
- Make sure you're running `make generate` after changing `.templ` files
|
|
- Check that generated `*_templ.go` files are being updated
|
|
|
|
2. **Import errors**:
|
|
- Ensure all imports in templ files are valid Go imports
|
|
- Check that models and services are properly imported
|
|
|
|
3. **Context issues**:
|
|
- Make sure user is properly set in request context
|
|
- Verify auth middleware is working correctly
|
|
|
|
4. **Static files not loading**:
|
|
- Check that static file serving is properly configured
|
|
- Verify file paths in templates are correct
|
|
|
|
### Performance Optimization
|
|
|
|
1. **Enable compression**:
|
|
```go
|
|
// Add gzip middleware
|
|
import "github.com/gorilla/handlers"
|
|
|
|
router := setupRoutes()
|
|
handler := handlers.CompressHandler(router)
|
|
```
|
|
|
|
2. **Add caching headers**:
|
|
```go
|
|
// For static files
|
|
r.PathPrefix("/static/").Handler(
|
|
http.StripPrefix("/static/",
|
|
addCacheHeaders(http.FileServer(http.Dir("./static/")))))
|
|
```
|
|
|
|
3. **Monitor memory usage**:
|
|
```bash
|
|
# Check memory usage
|
|
go tool pprof http://localhost:8080/debug/pprof/heap
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Component Reusability**: Break down complex pages into smaller, reusable components
|
|
2. **Type Safety**: Always use strongly-typed parameters in templ functions
|
|
3. **Error Handling**: Properly handle rendering errors in handlers
|
|
4. **Performance**: Use templ's compile-time optimization features
|
|
5. **Testing**: Write tests for your handlers and components
|
|
6. **Documentation**: Document complex templ components and their usage
|
|
|
|
## Next Steps
|
|
|
|
After successful migration:
|
|
|
|
1. **Add Tests**: Create tests for your new handlers and components
|
|
2. **Performance Monitoring**: Set up monitoring for template rendering performance
|
|
3. **Accessibility**: Ensure templates meet accessibility standards
|
|
4. **SEO**: Add proper meta tags and structured data
|
|
5. **Internationalization**: Consider adding i18n support
|
|
|
|
## Support
|
|
|
|
For issues or questions:
|
|
|
|
1. Check the [templ documentation](https://templ.guide/)
|
|
2. Review the example handlers in `internal/handlers/templ_handlers.go`
|
|
3. Look at the component examples in `internal/views/components/`
|
|
4. Check the build scripts and Makefile for automation examples
|
|
|
|
## Conclusion
|
|
|
|
The migration to templ provides significant benefits:
|
|
- **Type Safety**: Compile-time checking prevents runtime errors
|
|
- **Performance**: No runtime template parsing
|
|
- **Developer Experience**: Better IDE support and refactoring
|
|
- **Maintainability**: Component-based architecture
|
|
|
|
The new system is more robust and provides a better foundation for future development. |