Phoenix 1.7.0-rc.0 is out!

Ah thank you! I actually found this in the docs, which explains it in more detail: Phoenix.Component — Phoenix LiveView v0.18.11

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.

@LostKobrakai thanks for the insight!

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?

4 Likes

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.

1 Like

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 am really looking forward to this one. I am using htmx 14 these days and they have a nice essay on locality of behavior.

Can you expand on your approach? A mini tutorial in a post would be awesome.

1 Like

I think the graph can be simplified in two parts. There are two questions:

  1. How to render templates
  2. 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.

5 Likes

Here is a reference table with how the defaults changed from v1.6 to v1.7.

10 Likes

I am working on it.

1 Like

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

1 Like

I believe you’re mistakenly referring to Tailwind UI, which is a paid offering of pre-styled components using Tailwind CSS.

The Tailwind CSS framework that Phoenix uses is licensed by the MIT License.

4 Likes

Just trying to figure out how things work. Is it close enough to the way a component callbacks work?

7 Likes

Yes, beautifully done. If you feel like converting it to mermaid and sending that as a PR, I would gladly accept it!

7 Likes

Oh, thanks! Will do and share here :smiley:

May be not the best layout, but here you go:

Mermaid Flowchart:

---
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.

3 Likes

@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 helped design the pages:

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.

(from Phoenix 1.7-rc released: Built-in Tailwind, Verified Routes, and more - Phoenix Blog)

So I don’t think that the remarks about the license are really valid.

Having said that, if you don’t want to use tailwind these are the steps you need to take to remove it fully from a new phoenix 1.7 project:

  • In mix.exs: remove tailwind and aliases
  • remove tailwind config from config/config.exs
  • remove tailwind watcher from config/dev.exs
  • remove assets/tailwind.config.js
  • remove the tailwind imports from: assets/css/app.css
  • remove tailwind classes from: components/layouts/*.html.heex, components/core_components.ex, controllers/page_html/home.html.heex

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.

2 Likes

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.

7 Likes

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).

8 Likes

Did you ever discuss with them the possibility of a LV headlessui?

@josevalim I just noticed that what the doc says:

On subsequent renders, these callbacks will be invoked:

preload(list_of_assigns) -> update(assigns, socket) -> render(assigns)

So I guess there’s a missing route in the diagram. I can try to fix it, feeling it’s not going to be pretty :smiley: