lustre-labs / lustre

A Gleam web framework for building HTML templates, single page applications, and real-time server components.
https://hexdocs.pm/lustre
MIT License
949 stars 65 forks source link

#16 add fragment #95

Closed JScearcy closed 6 months ago

JScearcy commented 6 months ago

Add fragment support to client

Summary

This commit only considered the client runtime as a jumping off point. I'm hoping to get feedback on general direction since I want to make sure it fits stylistically and within the vision for the package.

Changes

TODO

Testing App ``` import gleam/int import lustre import lustre/attribute import lustre/element.{type Element} import lustre/element/html import lustre/event import lustre/ui // MAIN ------------------------------------------------------------------------ pub fn main() { let app = lustre.simple(init, update, view) let assert Ok(_) = lustre.start(app, "#app", 0) } // MODEL ----------------------------------------------------------------------- type Model = Int fn init(initial_count: Int) -> Model { case initial_count < 0 { True -> 0 False -> initial_count } } // UPDATE ---------------------------------------------------------------------- pub opaque type Msg { Incr Decr } fn update(model: Model, msg: Msg) -> Model { case msg { Incr -> model + 1 Decr -> model - 1 } } // VIEW ------------------------------------------------------------------------ fn count_fragment(model: Model) -> Element(Msg) { let count = int.to_string(model) let count_1 = int.to_string(model + 1) let count_2 = int.to_string(model + 2) element.fragment([ html.p([attribute.style([#("text-align", "center")])], [element.text(count)]), html.p([attribute.style([#("text-align", "center")])], [ element.text(count_1), ]), html.p([attribute.style([#("text-align", "center")])], [ element.text(count_2), ]), ]) } fn view(model: Model) -> Element(Msg) { let styles = [#("width", "100vw"), #("height", "100vh"), #("padding", "1rem")] let count = int.to_string(model) element.fragment([ ui.centre( [attribute.style(styles)], ui.stack([], [ ui.button([event.on_click(Incr)], [element.text("+")]), count_fragment(model), element.fragment([ element.fragment([html.p([], [element.text("immediate fragment")])]), html.p([attribute.style([#("text-align", "center")])], [ element.text("nest fragment next"), ]), element.fragment([ html.p([], [element.text("nest count")]), html.p([], [element.text(count)]), html.p([attribute.style([#("text-align", "center")])], [ element.text("nest nest fragment"), ]), element.fragment([ html.p([], [element.text("nest nest count")]), html.p([], [element.text(count)]), ]), ]), ]), ui.button([event.on_click(Decr)], [element.text("-")]), html.p([attribute.style([#("text-align", "center")])], [ element.text("sibling fragment"), ]), element.fragment([ html.p([], [element.text("sibling fragment")]), html.p([], [element.text(count)]), ]), html.p([], [element.text("Empty fragment next")]), element.fragment([]), ]), ), ]) } ```
Testing App render screenshot ![testing-app-render-screenshot](https://github.com/lustre-labs/lustre/assets/12852633/f385e81b-1dbb-498f-bfe5-b70c3710fa4f)
hayleigh-dot-dev commented 6 months ago

Oh no! I think you might be in for some work refactoring this: it looks like you had a slightly stale fork 😅 I think you should not have too much trouble moving things but pretty much Everything changed/moved so it won't be a smooth rebase.

Using a DocumentFragment is a clever idea, I didn't even know that was a real thing!

Instead of the unique identifier I think we can just check at runtime if curr.elements !== undefined.

I'm not sure how this is going to play with keyed elements (which are new and you don't have yet), might end up being a bit of a sticking point.

JScearcy commented 6 months ago

Yeah, I cloned yesterday just to look and made the mistake of not pulling latest before starting 😅 Honestly, sick refactor. I actually think it simplifies things for Fragment - I think it won't need the DocumentFragment anymore. Keys change things too for sure but nothing too wild. If I think about React a Fragment takes a key also, and I imagine that's the expected behavior here. Looking at the latest now though!

JScearcy commented 6 months ago

Spoke too soon I think 😬 tried a couple different approaches with slightly different issues - gonna think on it and try again. Though I'll open a different PR - this one might confuse things since it's a bit outdated

hayleigh-dot-dev commented 6 months ago

Ah no! 😭 thank you for taking a look anyway! 💕