I recently started on a 1.6 to 1.7 upgrade with all the changes to remove phoenix_view and had some troubles. Despite Jose mentioning that phoenix_view will be maintained, I was already half-way done, so I continued on a complete migration.
Hopefully, this documentation helps someone. Also, the pre-requisite reading is:
Phoenix 1.6 to 1.7 upgrade
Phoenix View - replaced by Phoenix.Component
Main changes
A sample phoenix 1.7 project is required (currently at 1.7.2).
app_web.ex
Several additional functions need to be copied from a new project
- live_component
- live_view
- html
- html_helpers
- verified_routes
Need to eventually remove “view”
Copy files / replace from app_web/components/*
-
app_web/components/core_components.ex
-
app_web/controllers/error_html.ex
-
app_web/controllers/error_json.ex
-
Layout replacement. root.html.heex now takes on the function of both templates/layouts/{app.html.heex, live_app.html.heex}
- app_web/components/layouts.ex (replaces app_web/views/layout_view.ex)
- app_web/components/layouts/app.html.heex
<%= @inner_content %>
- app_web/components/layouts/root.html.heex
- <%= csrf_meta_tag() %>
+ <meta name="csrf-token" content={get_csrf_token()} />
- router.ex
- plug :put_layout, {AppWeb.LayoutView, :app}
+ plug :put_root_layout, {AppWeb.Layouts, :root}
- config/config.ex
config :app, AppWeb.Endpoint,
- render_errors: [view: AppWeb.ErrorView, accepts: ~w(html json)]
+ render_errors: [
+ formats: [html: AppWeb.ErrorHTML, json: AppWeb.ErrorJSON],
+ layout: false
+ ]
Non-Liveview
Directory structure changes
app_web/controllers/bla_controller.ex → app_web/controllers/bla_controller.ex
app_web/templates/bla/* → app_web/controllers/bla_html/*
app_web/views/bla_view.ex → app_web/controllers/bla_html.ex
File changes bla_view.ex → bla_html.ex
use AppWeb, :view
def some_helper() do
end
to
use AppWeb, :html
embed_templates "bla_html/*"
# Or to keep directory structure
# embed_templates "../templates/bla/*"
def some_helper() do
end
@doc """
NOTE: In migration to Phoenix 1.7.2, needed to keep this function for some generated eex files
Generates tag for inlined form input errors.
"""
def error_tag(form, field) do
Enum.map(Keyword.get_values(form.errors, field), fn error ->
content_tag(:span, translate_error(error), class: "help-block")
end)
end
Moving/Deletion
Can eventually delete
- lib/app_web/controllers/fallback_controller.ex
- lib/app_web/views/*
New location could be found for
- lib/app_web/templates/*
Render changes
Have to be very explicit in templates now as there is no way to dynamically choose a function that I can see so far. The functions here are actually template names as found by the directive embed_templates/* in the controllers/bla_html.ex
- <%= render "#{@page_name}.html", email: @email, action: Routes.session_path(@conn, @page_action), changeset: @changeset %>
+ <%= case @page_name do
+ "landing" -> landing(assigns)
+ "login_form" -> login_form(assigns)
+ "register_form" -> register_form(assigns)
+ "forgot_form" -> forgot_form(assigns)
+ end
+ %>
LiveView changes
Directory structure changes
Perhaps I’m doing it wrong, but this is what I had to do.
- app_web/live/first/ - the directory
- app_web/live/first/main.ex - the live_view / live_component
- use Phoenix.LiveComponent
+ use AppWeb, :live_component
+ import AppWeb.First.Helpers # these were previously defined in AppWeb.FirstView
+ embed_templates "templates/*"
- def render(assigns) do
AppWeb.FirstView.render("main.html", assigns)
end
- app_web/live/first/helpers.ex - whatever functions that were previously in FirstView
+ use AppWeb, :html
- app_web/live/first/main.html.heex - the file that gets directly rendered on component/view load. Any sub-templates need to be rendered from the templates/* directory. The render changes to a function call. If there is any conditional rendering, there needs to be a case statement with the function calls.
- <%= render("file.html", target: @myself) %>
+ <.file target={@myself}
What if you want to keep the render function in your liveview?
This is needed for conditional rendering of templates. In that case, move the main.html.heex into the whatever templates directory you have defined in embed_templates/* and it changes from
def render(assigns) do
cond do
assigns.print == true ->
AppWeb.FirstView.render("print.html", assigns)
!is_nil(assigns.alt_layout ) >
AppWeb.FirstView.render("layout#{assigns.alt_layout}.html", assigns)
true ->
AppWeb.FirstView.render("main.html", assigns)
end
end
def render(assigns) do
cond do
assigns.print == true ->
~H"<%= print(assigns) %>"
!is_nil(assigns.alt_layout) ->
current_layout = assigns.alt_layout
case current_layout do
"1" -> ~H"<%= layout1(assigns) %>"
"2" -> ~H"<%= layout2(assigns) %>"
end
true ->
~H"<%= main(assigns) %>"
end
end
Conclusion
In the end, the migration wasn’t hard, just time consuming. I had an organic sprawl of 30 templates and 20 liveview components in templates and live and had to touch around 70 files total and took the opportunity to namespace everything better.
I’m solo right now, but in the future if someone had to touch the code and look at the phoenix documentation, they would have been very confused.
Regardless, I prefer this new setup without phoenix_view as there seems like there is a bit less magic. Verified routes is also a nice incremental improvement. Also, perhaps this is a pipedream, but I’d like to keep this application running for the next 10 years (preferably 50) without too many major migrations.