One more question then: Since Phoenix 1.7 will get rid of the HomeView views, how would I replace them in the render/1 function? Something like this maybe?
defmodule AppWeb.HomeLive do
use AppWeb, :live_view
def render(assigns), do: AppWeb.HomeHTML.render("show.html", assigns)
end
defmodule AppWeb.HomeHTML do
use AppWeb, :html
embed_templates "home/*"
end
This gives me the error: function AppWeb.HomeHTML.render/2 is undefined or private
Or do I have to define a Phoenix.Component that renders the show.html.heex so that I can use the child template function calls?
I tried colocating the template, but then I can’t use the child template function calls. So, this here won’t work for example:
# lib/app_web/live/home_live.ex
defmodule AppWeb.HomeLive do
use AppWeb, :live_view
end
# lib/app_web/live/home_live.html.heex
<div>
<h1>Hello World</h1>
<.child />
</div>
# lib/app_web/live/child.html.heex
<div>
<h2>From the child!</h2>
</div>
render (of views) is no longer a thing unless you’re manually including phoenix_view. Templates will become function components with embeds_templates. No more render("show.html", assigns), but <.show {assigns} /> or show(assigns).
You cannot colocate multiple templates and you never could in the past. Personally I’d love to get rid of them, because they’re just causing so much confusion. Explicitly requiring embed_templates in the LV module would’ve been so much cleaner, but it’s too late for that now. I’d suggest maintaining everything which is a subcomponent separate from the LV module and use co-located templates only as the single place of composition of subcomponents if at all. Since 1.7 I even tend to inline the render callback on LV modules most of the time. Also one-of pieces of markup are simple to be maintained as function (components) instead of in templates. If they’re so big that they need a template file I think it’s worth to move them to a separate module.
I tried to visualize the old and new ways of rendering HTML from the LiveView or Controller through render, colocation, or calling a View. Does this below make sense or did I forget something?
This totally misses the fact that colocated templates for LV used Phoenix.View pre 1.7 – you just never saw it (which is one of my problems with the feature).
LVs can be their own view module instead of having an external one. That internal functionality uses the exact same functionality as an external view module would, just limited to a single template with an inferred but known name. That applies to both pre 1.7 phoenix as well as post 1.7 phoenix.
The thing that changed is that Phoenix.View(render/2) and all APIs based on it were replaced with plain function calls of template_name/1, which happen to align with what heex function components call internally.
Thanks! I updated the drawing a bit to incorporate your feedback.
Also, to sum up the findings regarding rendering child templates:
From Phoenix 1.7 on, one has to wrap the template and its child templates in a Phoenix.Component and then call it as functions, right?
So, this is the old way:
# lib/app_web/live/home_live.ex
defmodule AppWeb.HomeLive do
use AppWeb, :live_view
def render(assigns), do: AppWeb.HomeView.render("show.html", assigns)
end
# lib/app_web/views/home_view.ex
defmodule AppWeb.HomeView do
use AppWeb, :view
end
# lib/app_web/templates/home/show.html.heex
<div>
<h1>Hello World</h1>
<%= render "_child.html", assigns %>
</div>
# lib/app_web/templates/home/_child.html.heex
<div>
<h2>From the child!</h2>
</div>
And this would be the new way:
# lib/app_web/live/home_live.ex
defmodule AppWeb.HomeLive do
use AppWeb, :live_view
def render(assigns) do
~H"""
<AppWeb.HomeHTML.show />
"""
end
end
# lib/app_web/live/home_html.ex
defmodule AppWeb.HomeHTML do
use AppWeb, :html
embed_templates "home/*"
end
# lib/app_web/live/home/show.html.heex
<div>
<h1>Hello World</h1>
<.child />
</div>
# lib/app_web/live/home/child.html.heex
<div>
<h2>From the child!</h2>
</div>
I think the graph can be simplified in two parts. There are two questions:
How to render templates
How to define templates
Rendering goes like this for controllers and LVs:
Controller → Phoenix.Template.render → FooBarHTML.action/1 which returns HEEx (in particular, the Phoenix.LiveView.Rendered struct)
LiveView → FooBarLive.render → returns HEEx
Now to define templates:
Either define a function that uses the ~H sigil
Use embed_templates
This is important because you can totally use the ~H sigil and define FooBarHTML.action/1 manually. So IMO the graph should have two completely separate parts that meet in the same place only at the end (they meet on the HEEx) or two separate diagrams.
Yes, I’d also like to suggest the option to have a --no-tailwind option. I work on a lot of projects that require the use of OSI approved licenses and tailwind isn’t open source, and on bigger corporate teams is likely to require their costly Team License.
I understand that as things stand with 1.7, it’s only the core_components that uses tailwind out of the box and that I could replace the styling of the components in there to use another framework. That’s not a big deal and it’s likely that the community will share examples of these core components being styled for other frameworks.
My main concern is if this “tailwind by default” approach will appear in other parts of generator outputs, mainly because of the license incompatibility that might cause when it comes to open source projects using Phoenix
---
title: LiveComponent - Life Cycle
---
flowchart LR
*((start)):::event-..->P
WE([wait for external changes]):::event-.->U
W([wait for events]):::event-.->H
subgraph w[" "]
direction TB
subgraph i[" "]
direction TB
P(preload/1):::callback-->M
M(mount/1):::callback-->U
end
U(update/2):::callback-->R
subgraph j[" "]
direction LR
A --> |yes| R
H(handle_event/3):::callback-->A{any changes?}:::diamond
end
A --> |no| W
R[render/1]:::callback_req-->W
end
classDef event fill:#fff,color:#000,stroke:#000
classDef diamond fill:#FFFF8C,color:#000,stroke:#000
classDef callback fill:#66B2FF,color:#000,stroke-width:0
classDef callback_req fill:#2C4D6E,color:#fff,stroke:#fff,stroke-width:1
(fixed \n not understood by :ex_doc) Making sure my local doc works.
@zachallaun I understand that the CSS files are MIT licensed, but the licensing for the components is ambiguous. As you know, the Tailwind UI components are just some “prefab” HTML with Tailwind classes. How can I know whether or not the component auto-generated by Phoenix contains HTML/CSS code that falls under the Tailwind UI license or not?
For example, a casual inspection of the generated code for the modal in Phoenix 1.7 shows a high degree of similarity with the code for the Tailwind UI modal components. How do I know that the developer didn’t copy the Tailwind UI component code and modify it to create the component that I’m now using?
Add to this that the Tailwind UI license is vague about what it considers a derivative, and states that derivative components and templates are subject to the Tailwind UI license. This would mean that when I see tailwind in the code then there’s no way of knowing, prima facie, whether it’s subject to the Tailwind UI license or not.
The Tailwind team also generously designed the new project landing page, CRUD pages, and authentication system pages for new projects, giving you a first-class and polished starting point for building out your apps.
The last step is a bit of work, but you’d only need to do it 1 time and then copy it over if you create a new project. The other steps are less then 5 minutes of work.
So your concern is that the Tailwind team, who worked with the Phoenix team to design the new default generated components based on TailwindCSS, are going to begin pursuing legal action against random users generating default templates?
Perhaps it’s my own naïveté, but this reads as some pretty serious FUD to me.
The core_components.ex generator is part of Phoenix and Phoenix is MIT licensed. The tailwind team was kind enough to contribute the designs and markup for it, but it wholly separate from the TailwindUI product (which is fantastic).