first commit
This commit is contained in:
@@ -0,0 +1,451 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user