Skip to content

Commit 542f4eb

Browse files
authored
🔥 feat: Add support for ReloadViews() (#3876)
1 parent d00826d commit 542f4eb

File tree

6 files changed

+102
-1
lines changed

6 files changed

+102
-1
lines changed

.markdownlint.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ default: true
77
extends: null
88

99
# MD001/heading-increment : Heading levels should only increment by one level at a time : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md001.md
10-
MD001: true
10+
# NOTE: The docs intentionally jump heading levels for anchor stability, so skip this rule globally.
11+
MD001: false
1112

1213
# MD003/heading-style : Heading style : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md003.md
1314
MD003:
@@ -244,5 +245,9 @@ MD055:
244245
# Table pipe style
245246
style: "consistent"
246247

248+
# MD060/table-column-style : Table column style : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md060.md
249+
# NOTE: Legacy docs rely on hand-crafted alignment, so disable this rule to avoid noisy warnings.
250+
MD060: false
251+
247252
# MD056/table-column-count : Table column count : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md056.md
248253
MD056: true

app.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,27 @@ func (app *App) RegisterCustomBinder(customBinder CustomBinder) {
710710
app.customBinders = append(app.customBinders, customBinder)
711711
}
712712

713+
// ReloadViews reloads the configured view engine by invoking its Load method.
714+
// It returns an error if no view engine is configured or if reloading fails.
715+
func (app *App) ReloadViews() error {
716+
app.mutex.Lock()
717+
defer app.mutex.Unlock()
718+
719+
if app.config.Views == nil {
720+
return ErrNoViewEngineConfigured
721+
}
722+
723+
if viewValue := reflect.ValueOf(app.config.Views); viewValue.Kind() == reflect.Pointer && viewValue.IsNil() {
724+
return ErrNoViewEngineConfigured
725+
}
726+
727+
if err := app.config.Views.Load(); err != nil {
728+
return fmt.Errorf("fiber: failed to reload views: %w", err)
729+
}
730+
731+
return nil
732+
}
733+
713734
// SetTLSHandler Can be used to set ClientHelloInfo when using TLS with Listener.
714735
func (app *App) SetTLSHandler(tlsHandler *TLSHandler) {
715736
// Attach the tlsHandler to the config

app_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1986,6 +1986,59 @@ func (invalidView) Load() error { return errors.New("invalid view") }
19861986

19871987
func (invalidView) Render(io.Writer, string, any, ...string) error { panic("implement me") }
19881988

1989+
type countingView struct {
1990+
loadErr error
1991+
loads int
1992+
}
1993+
1994+
func (v *countingView) Load() error {
1995+
v.loads++
1996+
return v.loadErr
1997+
}
1998+
1999+
func (*countingView) Render(io.Writer, string, any, ...string) error { return nil }
2000+
2001+
func Test_App_ReloadViews_Success(t *testing.T) {
2002+
t.Parallel()
2003+
view := &countingView{}
2004+
app := New(Config{Views: view})
2005+
initialLoads := view.loads
2006+
2007+
require.NoError(t, app.ReloadViews())
2008+
require.Equal(t, initialLoads+1, view.loads)
2009+
2010+
require.NoError(t, app.ReloadViews())
2011+
require.Equal(t, initialLoads+2, view.loads)
2012+
}
2013+
2014+
func Test_App_ReloadViews_Error(t *testing.T) {
2015+
t.Parallel()
2016+
wantedErr := errors.New("boom")
2017+
view := &countingView{loadErr: wantedErr}
2018+
app := New(Config{Views: view})
2019+
2020+
err := app.ReloadViews()
2021+
require.Error(t, err)
2022+
require.ErrorIs(t, err, wantedErr)
2023+
}
2024+
2025+
func Test_App_ReloadViews_NoEngine(t *testing.T) {
2026+
t.Parallel()
2027+
app := New()
2028+
2029+
err := app.ReloadViews()
2030+
require.ErrorIs(t, err, ErrNoViewEngineConfigured)
2031+
}
2032+
2033+
func Test_App_ReloadViews_InterfaceNilPointer(t *testing.T) {
2034+
t.Parallel()
2035+
var view *countingView
2036+
app := &App{config: Config{Views: view}}
2037+
2038+
err := app.ReloadViews()
2039+
require.ErrorIs(t, err, ErrNoViewEngineConfigured)
2040+
}
2041+
19892042
// go test -run Test_App_Init_Error_View
19902043
func Test_App_Init_Error_View(t *testing.T) {
19912044
t.Parallel()

docs/api/app.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,25 @@ Returns `b` unchanged when [`Immutable`](./fiber.md#immutable) is disabled or `b
2525
func (app *App) GetBytes(b []byte) []byte
2626
```
2727

28+
### ReloadViews
29+
30+
Reloads the configured view engine on demand by calling its `Load` method. Use this helper in development workflows (e.g., file watchers or debug-only routes) to pick up template changes without restarting the server. Returns an error if no view engine is configured or reloading fails.
31+
32+
```go title="Signature"
33+
func (app *App) ReloadViews() error
34+
```
35+
36+
```go title="Example"
37+
app := fiber.New(fiber.Config{Views: engine})
38+
39+
app.Get("/dev/reload", func(c fiber.Ctx) error {
40+
if err := app.ReloadViews(); err != nil {
41+
return err
42+
}
43+
return c.SendString("Templates reloaded")
44+
})
45+
```
46+
2847
## Routing
2948

3049
import RoutingHandler from './../partials/routing/handler.md';

docs/whats_new.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ We have made several changes to the Fiber app, including:
8484
- **State**: Provides a global state for the application, which can be used to store and retrieve data across the application. Check out the [State](./api/state) method for further details.
8585
- **NewErrorf**: Allows variadic parameters when creating formatted errors.
8686
- **GetBytes / GetString**: Helpers that detach values only when `Immutable` is enabled and the data still references request or response buffers. Access via `c.App().GetString` and `c.App().GetBytes`.
87+
- **ReloadViews**: Lets you re-run the configured view engine's `Load()` logic at runtime, including guard rails for missing or nil view engines so development hot-reload hooks can refresh templates safely.
8788

8889
#### Custom Route Constraints
8990

error.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ var (
1818
ErrNotRunning = errors.New("shutdown: server is not running")
1919
// ErrHandlerExited is returned by App.Test if a handler panics or calls runtime.Goexit().
2020
ErrHandlerExited = errors.New("runtime.Goexit() called in handler or server panic")
21+
// ErrNoViewEngineConfigured indicates that a helper requiring a view engine was invoked without one configured.
22+
ErrNoViewEngineConfigured = errors.New("fiber: no view engine configured")
2123
)
2224

2325
// Fiber redirection errors

0 commit comments

Comments
 (0)