-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
📝 [Proposal]: We need partial render! #3297
Comments
Thanks for opening your first issue here! 🎉 Be sure to follow the issue template! If you need help or want to chat with us, join us on Discord https://gofiber.io/discord |
+1 This sounds like a great idea. Due to the way SSE and A problem we could potentially have with this is that a If possible, we may want to add this functionality to a different interface to avoid that confusion. |
@grivera64 @vmpartner Should we implement this functionality out as a new middleware? |
I think method RenderToWriter must appear inside main app, it cannot be implement in middleware |
I think it is possible to use a middleware to encapsulate the feature, though we would still need to implement a An idea could be to create this method under a separate package (or even bundle it alongside the middleware in a
partials.RenderAsBytes(templateName string) []byte
partials.RenderToWriter(w *bufio.Writer, templateName string) error This way, we could directly use this in a middleware: app.Get("/partial", partials.New("my-partial", func() fiber.Map {
return getLiveData() // assuming getLiveData() returns a bind of type fiber.Map
}) What do you both think about this setup? @vmpartner @JIeJaitt |
@grivera64 Sounds good! |
Dear @grivera64 <https://github.com/grivera64>, as i know we must work with
engine, its init with fiber app. I cant imagine how to use middleware for
it.
вт, 4 мар. 2025 г. в 14:13, JIeJaitt ***@***.***>:
… @grivera64 <https://github.com/grivera64> Sounds good!
—
Reply to this email directly, view it on GitHub
<#3297 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAKKFRE2BDIHIIGV7ZLAJ5T2SVVD5AVCNFSM6AAAAABWDTX5UKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDMOJWG42TSOBQGY>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
[image: JIeJaitt]*JIeJaitt* left a comment (gofiber/fiber#3297)
<#3297 (comment)>
@grivera64 <https://github.com/grivera64> Sounds good!
—
Reply to this email directly, view it on GitHub
<#3297 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAKKFRE2BDIHIIGV7ZLAJ5T2SVVD5AVCNFSM6AAAAABWDTX5UKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDMOJWG42TSOBQGY>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Yes, you are right @vmpartner. I forgot to mention about how we would provide the middleware with access to the template engine. The proposal above should be modified to factor that in. With that in mind, we could potentially do one of the following:
func New(name string, bindFunc func() fiber.Map) fiber.Handler {
return func(c fiber.Ctx) error {
// Get Engine from fiber.Ctx (since ctx may be deallocated/reused elsewhere
// when the streamWriter executes)
engine := c.App().Config().Views
// Use engine here
return c.SendStreamWriter(func(w *bufio.Writer) error {
for {
// Get the current bind
bind := bindFunc()
// ...
// Wait before next request (e.g. Timeout, Some event)
// ...
}
})
}
}
engine := ...
app := fiber.New(fiber.Config{
Views: engine,
// Other configs ...
})
partialRenderer := partials.New(partials.Config{
Views: engine,
})
app.Get("/partial", partialRenderer.Render("my-partial", func() fiber.Map {
return getLiveData() // assuming getLiveData() returns a bind of type fiber.Map
})
func (c *DefaultCtx) RenderPartial(name string, bindFunc func() fiber.Map, layouts ...string) error {
// Set headers for SSE...
return c.SendStreamWriter(func(w *bufio.Writer) error {
for {
// Get the current bind
bind := bindFunc()
// Do the same from L1375 to L1426 in fiber/ctx.go
// to minimize memory allocations
// ...
// Write to the buffer with the result from the template engine
w.Write(buf.Bytes())
// Wait before next request (e.g. Timeout, Some event)
// ...
}
})
} Out of these three, I like both 2 and 3. 1 may not work for the long run if Another issue is deciding how would this feature allow a user to specify when it should call engine := ...
app := fiber.New(fiber.Config{
Views: engine,
// Other configs ...
})
partialRenderer := partials.New(partials.Config{
Views: engine,
})
app.Get("/", func (c fiber.Ctx) error {
return c.SendStreamWriter(func(w *bufio.Writer) {
for {
data := getLiveData()
partialRenderer.RenderToWriter(w, "my-live-partial-template", data)
time.Sleep(5*time.Second)
}
})
}) What are your thoughts on these approaches? |
Sorry, hard to understand this cases for me. I understand this approach, seems it will work (with limitations): engine := ...
app := fiber.New(fiber.Config{
Views: engine,
// Other configs ...
})
partialRenderer := partials.New(partials.Config{
Views: engine,
})
app.Get("/", func (c fiber.Ctx) error {
return c.SendStreamWriter(func(w *bufio.Writer) {
for {
data := getLiveData()
partialRenderer.RenderToWriter(w, "my-live-partial-template", data)
time.Sleep(5*time.Second)
}
})
}) but i preferer new func inside core app.Get("/", func (c fiber.Ctx) error {
return c.SendStreamWriter(func(w *bufio.Writer) {
for {
data := getLiveData()
c.RenderToWriter(w, "my-live-partial-template", data)
time.Sleep(5*time.Second)
}
})
}) its need not only for SSE, but in many other cases, when you need render email template for example. |
No worries. It's a rough sample, so there's a lot of details missing but I was trying to give the main ideas for the feature. Essentially, ideas 1 and 2 would be to implement middleware that is explicitly for making an SSE endpoint that will return a partial and fill it in using a For example: app.Get("/partial", partials.Render("my-live-partial-template"), func() fiber.Map {
return getLiveData()
}, 5 * time.Second)
Would create the following equivalent handler: app.Get("/partial", func(c fiber.Ctx) error {
// Set headers accordingly
// ...
// Get the View from config
view := c.App().Config().Views
// Name of the partial
name := "my-live-partial-template"
// Bind Map Function gets the live data
bindMap := func() fiber.Map {
return getLiveData()
}
// Timeout
timeout := 5*time.Second
return c.SendStreamWriter(func(w *bufio.Writer) error {
view.Render(w, name, bindFunc())
time.Sleep(timeout)
})
})
We could add a Since
In this case, would it make more sense to instead make it easier to obtain a app.Get("/", func (c fiber.Ctx) error {
view := c.App().Config().Views
return c.SendStreamWriter(func(w *bufio.Writer) {
for {
data := getLiveData()
view.Render(w, "my-live-partial-template", data)
time.Sleep(5*time.Second)
}
})
}) could become: app.Get("/", func (c fiber.Ctx) error {
view := c.View()
return c.SendStreamWriter(func(w *bufio.Writer) {
for {
data := getLiveData()
view.Render(w, "my-live-partial-template", data)
time.Sleep(5*time.Second)
}
})
}) This way, we make it much simpler for users to access a view for any of their needs, though this change itself wouldn't implement a partial renderer on its own. A middleware or add-on would better simplify the process, but I am not sure if such an approach would be too limiting. |
Thank you for details, now it's more clear. Get views looks great idea
пт, 7 мар. 2025 г., 11:11 Giovanni Rivera ***@***.***>:
… Sorry, hard to understand this cases for me.
No worries. It's a rough sample, so there's a lot of details missing but I
was trying to give the main ideas for the feature. Essentially, ideas 1 and
2 would be to implement middleware that is explicitly for making an SSE
endpoint that will return a partial and fill it in using a bind from a
user-provided function (it would contain logic to build the bind) just like
in c.Render().
For example:
app.Get("/partial", partials.Render("my-live-partial-template"), func() fiber.Map {
return getLiveData()
}, 5 * time.Second)
where partials.Render() has the signature:
func Render(name string, bindFunc fiber.Map, timeout time.Duration)
Would create the following equivalent handler:
app.Get("/partial", func(c fiber.Ctx) error {
// Set headers accordingly
// ...
// Get the View from config
view := c.App().Config().Views
// Name of the partial
name := "my-live-partial-template"
// Bind Map Function gets the live data
bindMap := func() fiber.Map {
return getLiveData()
}
// Timeout
timeout := 5*time.Second
return c.SendStreamWriter(func(w *bufio.Writer) error {
view.Render(w, name, bindFunc())
time.Sleep(timeout)
})
})
but i preferer new func inside core
app.Get("/", func (c fiber.Ctx) error {
return c.SendStreamWriter(func(w *bufio.Writer) {
for {
data := getLiveData()
c.RenderToWriter(w, "my-live-partial-template", data)
time.Sleep(5*time.Second)
}
})
})
We could add a RenderToWriter() function (or similar function) to core
instead of as an add-on, but we can not attach it to fiber.Ctx if we want
to be able to use it within c.SendStreamWriter(). A fiber context is only
valid within a handler, and is recycled after the return statement.
Since c.SendStreamWriter() runs after the handler returns, it is
undefined behavior to use a fiber.Ctx within the streamWriter function.
It works here since even after the context is recycled, the c.Config()
will be the same for all pooled contexts, but it might give the false
impression that other c.XXX() methods can be called from within
c.SendStreamWriter().
its need not only for SSE, but in many other cases, when you need render
email template for example.
In this case, would it make more sense to instead make it easier to obtain
a View from the fiber.Ctx? For example:
app.Get("/", func (c fiber.Ctx) error {
view := c.App().Config().Views
return c.SendStreamWriter(func(w *bufio.Writer) {
for {
data := getLiveData()
view.Render(w, "my-live-partial-template", data)
time.Sleep(5*time.Second)
}
})
})
could become:
app.Get("/", func (c fiber.Ctx) error {
view := c.View()
return c.SendStreamWriter(func(w *bufio.Writer) {
for {
data := getLiveData()
view.Render(w, "my-live-partial-template", data)
time.Sleep(5*time.Second)
}
})
})
This way, we make it much simpler for users to access a view for any of
their needs, though this change itself wouldn't implement a partial
renderer on its own. A middleware or add-on would better simplify the
process, but I am not sure if such an approach would be too limiting.
—
Reply to this email directly, view it on GitHub
<#3297 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAKKFRFNPXCU7UKKSGUUIMD2TE2CXAVCNFSM6AAAAABWDTX5UKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDOMBVGYYTKMRZGE>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
[image: grivera64]*grivera64* left a comment (gofiber/fiber#3297)
<#3297 (comment)>
Sorry, hard to understand this cases for me.
No worries. It's a rough sample, so there's a lot of details missing but I
was trying to give the main ideas for the feature. Essentially, ideas 1 and
2 would be to implement middleware that is explicitly for making an SSE
endpoint that will return a partial and fill it in using a bind from a
user-provided function (it would contain logic to build the bind) just like
in c.Render().
For example:
app.Get("/partial", partials.Render("my-live-partial-template"), func() fiber.Map {
return getLiveData()
}, 5 * time.Second)
where partials.Render() has the signature:
func Render(name string, bindFunc fiber.Map, timeout time.Duration)
Would create the following equivalent handler:
app.Get("/partial", func(c fiber.Ctx) error {
// Set headers accordingly
// ...
// Get the View from config
view := c.App().Config().Views
// Name of the partial
name := "my-live-partial-template"
// Bind Map Function gets the live data
bindMap := func() fiber.Map {
return getLiveData()
}
// Timeout
timeout := 5*time.Second
return c.SendStreamWriter(func(w *bufio.Writer) error {
view.Render(w, name, bindFunc())
time.Sleep(timeout)
})
})
but i preferer new func inside core
app.Get("/", func (c fiber.Ctx) error {
return c.SendStreamWriter(func(w *bufio.Writer) {
for {
data := getLiveData()
c.RenderToWriter(w, "my-live-partial-template", data)
time.Sleep(5*time.Second)
}
})
})
We could add a RenderToWriter() function (or similar function) to core
instead of as an add-on, but we can not attach it to fiber.Ctx if we want
to be able to use it within c.SendStreamWriter(). A fiber context is only
valid within a handler, and is recycled after the return statement.
Since c.SendStreamWriter() runs after the handler returns, it is
undefined behavior to use a fiber.Ctx within the streamWriter function.
It works here since even after the context is recycled, the c.Config()
will be the same for all pooled contexts, but it might give the false
impression that other c.XXX() methods can be called from within
c.SendStreamWriter().
its need not only for SSE, but in many other cases, when you need render
email template for example.
In this case, would it make more sense to instead make it easier to obtain
a View from the fiber.Ctx? For example:
app.Get("/", func (c fiber.Ctx) error {
view := c.App().Config().Views
return c.SendStreamWriter(func(w *bufio.Writer) {
for {
data := getLiveData()
view.Render(w, "my-live-partial-template", data)
time.Sleep(5*time.Second)
}
})
})
could become:
app.Get("/", func (c fiber.Ctx) error {
view := c.View()
return c.SendStreamWriter(func(w *bufio.Writer) {
for {
data := getLiveData()
view.Render(w, "my-live-partial-template", data)
time.Sleep(5*time.Second)
}
})
})
This way, we make it much simpler for users to access a view for any of
their needs, though this change itself wouldn't implement a partial
renderer on its own. A middleware or add-on would better simplify the
process, but I am not sure if such an approach would be too limiting.
—
Reply to this email directly, view it on GitHub
<#3297 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAKKFRFNPXCU7UKKSGUUIMD2TE2CXAVCNFSM6AAAAABWDTX5UKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDOMBVGYYTKMRZGE>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
No problem! In this case, should we rename/create a separate subissue to track the get views proposal? We can also still consider the partials middleware for a simpler approach for a partial render, though other use cases of Views can still be doable through the 1st get views proposal.
|
Feature Proposal Description
We have amazing gofiber + htmx + sse. We can send partial template on each update via SSE
Example how it can be reached now:
I propose that we can add method RenderToWriter(w *bufio.Writer, templateName string, data fiber.Map, layoutName string)
Alignment with Express API
HTTP RFC Standards Compliance
API Stability
Feature Examples
Checklist:
The text was updated successfully, but these errors were encountered: