Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Phoenix 1.5.x to 1.6 upgrade instructions

Update your deps

In mix.exs, update your phoenix, phoenix_html, telemetry_metrics, telemetry_poller and phoenix_live_dashboard deps, and add phoenix_live_view:

def deps do
    [
      {:phoenix, "~> 1.6.0"},
      ...
      {:phoenix_html, "~> 3.0"},
      {:phoenix_live_view, "~> 0.16.4"},
      {:phoenix_live_dashboard, "~> 0.5"},
      {:telemetry_metrics, "~> 0.6"},
      {:telemetry_poller, "~> 0.5"},
      ...
    ]
end

Next, run mix deps.get to grab your new deps.

Rename your .html.eex and .html.leex templates to .html.heex (optional)

While leex templates have been deprecated, this step is optional. For the most part, existing templates should continue to work, but the HTML-aware HEEx engine will enforce valid markup and is more strict in the elixir expressions that appear within an open and closing tag. For example, the following code will raise:

<div id="<%= @id %>">

Instead of the standard <%= %> EEx expressions, elixir expressions inside tags can only appear withing {}, such as:

<div id={@id}>

<%= %> expressions remain valid outside of HTML tags in the EEx engine.

To update your existing templates, rename all your .html.eex and .html.leex templats to .html.heex and follow the parser errors to find any tags that require the new {} attribute form.

Also be sure to review the HEEx documentation for more information on features.

Migrate to esbuild for js and css bundling (optional)

Phoenix's watchers configuration is build-tool agnostic, so you may continue to enjoy your existing webpack configurations generated by phoenix 1.5 or earlier. If only have basic js and css needs and you would like to take advantage of our new esbuild usage, for a dependency-free asset builder powered by a portably binary, follow these steps:

First delete your webpack config and related node files:

$ rm assets/webpack.config.js assets/package.json assets/package-lock.json assets/.babelrc
$ rm -rf assetes/node_modules

Next, add the esbuild mix dep to your mix.exs deps:

def deps do
  [
    ...
    {:esbuild, "~> 0.2", runtime: Mix.env() == :dev},
  ]
end

Next, configure esbuild in config/config.exs:

# Configure esbuild (the version is required)
config :esbuild,
  version: "0.12.18",
  default: [
    args: ~w(js/app.js --bundle --target=es2016 --outdir=../priv/static/assets),
    cd: Path.expand("../assets", __DIR__),
    env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
  ]

Next, replace the node watcher with esbuild in your endpoint watcher config in config/dev.exs:

config :demo, DemoWeb.Endpoint,
  ...,
  watchers: [
    # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args)
    esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}
  ]

Next, add a new assets.deploy mix alias in your mix.exs for easy asset building:

  defp aliases do
    [
      ...,
      "assets.deploy": ["esbuild default --minify", "phx.digest"]
    ]
  end

Running $ mix assets.deploy will download the esbuild binary on first run and then build your assets:

$ mix assets.deploy
21:40:37.588 [debug] Downloading esbuild from https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.12.18.tgz

  ../priv/static/assets/app.css  9.7kb
  ../priv/static/assets/app.js   1.3kb

⚡ Done in 12ms
Check your digested files at "priv/static"

Next, update your layouts, such as app.html.heex or root.html.heex to use the new assets prefix instead of js/app.js and css/app.css:

    <link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/assets/app.css")}/>
    <script defer phx-track-static type="text/javascript" src={Routes.static_path(@conn, "/assets/app.js")}></script>

Finally, update your Plug.Static :only options in your lib/app_web/endpoint.ex to be aware of the new assets directory:

  plug Plug.Static,
    at: "/",
    from: :my_app,
    gzip: false,
    only: ~w(assets fonts images favicon.ico robots.txt)
@aj-foster
Copy link

aj-foster commented Sep 23, 2021

If you previously used the standalone phx.gen.auth and followed along with https://github.com/aaronrenner/phx_gen_auth_output to keep your code up-to-date, be sure to take note of this commit which occurred during the standalone->bundled transition.

It may be why your test test require_authenticated_user/2 stores the path to redirect to on GET is failing.

@hlship
Copy link

hlship commented Sep 25, 2021

== Compilation error in file lib/sk_web/endpoint.ex ==
** (SyntaxError) lib/sk_web/endpoint.ex:29:57: unexpectedly reached end of line. The current expression is invalid or incomplete
    (elixir 1.12.3) lib/kernel/parallel_compiler.ex:319: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/7
~/workspaces/elixir/speaker-karaoke > mix assets.deploy
Compiling 1 file (.ex)
 > js/app.js:13:7: error: No matching export in "../deps/phoenix_live_view/priv/static/phoenix_live_view.esm.js" for import "default"
    13 │ import LiveSocket from "phoenix_live_view"
       ╵        ~~~~~~~~~~

Getting this on upgrade to 1.6.0, despite previous upgrade to esbuild.

Despite this, the dashboard works, but I don't like errors in my build (warnings I can stomach).

Update: error is harmless locally, but prevents my Dokku-based production build from starting up.

@hlship
Copy link

hlship commented Sep 25, 2021

Figured it out, the line should be:

import {LiveSocket} from "phoenix_live_view"

@theenglishway
Copy link

theenglishway commented Sep 26, 2021

Running mix deps.get with the suggested edits to mix.exs brought me the following warning :

  phoenix_html 2.14.3 => 3.0.0 RETIRED!
    (security) The :class attribute in content_tag and in class={@class} for HEEx is not escaped against XSS

I simply upgraded phoenix_html requirement to v3.0.4 (since this release solves this issue) via {:phoenix_html, "~> 3.0.4"} and it worked like a charm.

@hoyon
Copy link

hoyon commented Sep 27, 2021

We've created a tool here: https://github.com/Multiverse-io/eextoheex which attempts to convert .html.eex and .html.leex templates to .html.heex. We've already used it to convert nearly 200 templates and has been a massive time saver.

Hopefully it can be useful to other people too!

@thiagomajesk
Copy link

thiagomajesk commented Sep 27, 2021

If you are using the new phx 1.6.0 installer (not the RC one), you should also update the telemetry_poller to 1.0.

@crockwave
Copy link

crockwave commented Sep 30, 2021

I had difficulty running mix deps.get on a project upgrade until I learned that phx_gen_auth is now built into this release

@alexpls
Copy link

alexpls commented Oct 3, 2021

Along with adding phoenix_live_view to your deps, you'll also need to add the following to your {app_name}_web.ex file:

defp view_helpers do
  ...
  # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc)
  import Phoenix.LiveView.Helpers
end

Without this you will run into errors like undefined function form/1 when using heex helpers (which are now used by default in generated templates).

@jpcweb
Copy link

jpcweb commented Oct 7, 2021

Great!
If you use Tailwindcss: pragmaticstudio.com/tutorials/adding-tailwind-css-to-phoenix <- for Phoenix 1.6.
Don't forget to yarn add or npm install your packages inside assets.
You'll have to remove phx_gen_auth because it's included.
You'll also probably need to upgrade phoenix_ecto and other dependencies.
Finally the topbar error is fixed above (This gist, above)

@zorn
Copy link

zorn commented Oct 12, 2021

There is a TODO link for the heex docs. Maybe it should go here?

https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Helpers.html#sigil_H/2

Not sure if that would be the conical place to point people or not.

Also, any chance in the future these migration docs could be part of the main repo? Feel like there are missed opportunities to collaborate and make them more detailed having them in a person gist.

@njwest
Copy link

njwest commented Oct 14, 2021

There is an extra step for projects that have an older version of live_helpers.ex from live_views generated with phx 1.5.x:

  • Edit live_modal/3 in app_web/live/live_helpers.ex, removing socket from the function entirely.

Old version of live_modal:

  def live_modal(socket, component, opts) do
    path = Keyword.fetch!(opts, :return_to)
    modal_opts = [id: :modal, return_to: path, component: component, opts: opts]
    live_component(socket, CoronavirusNinjaWeb.ModalComponent, modal_opts)
  end

Version edited for phx 1.6 and related phx_live_view:

  def live_modal(component, opts) do
    path = Keyword.fetch!(opts, :return_to)
    modal_opts = [id: :modal, return_to: path, component: component, opts: opts]
    live_component(CoronavirusNinjaWeb.ModalComponent, modal_opts)
  end

Otherwise you will end up with this error:

..._web/live/some_page_live/show.html.heex:4: undefined function live_modal/2

@njwest
Copy link

njwest commented Oct 14, 2021

Note: upgraders from 1.5 versions may also get ye olde no route found for POST /dialogues/new error from a phx.gen.live-generated resource, for which my phx.gen'd form_component.html.heex <.form is attempting to post on submit rather than hitting the form_component.ex's "save" function.

Forgot how I solved this when I ran into it upgrading phx 1.4 -> 1.5, but am exploring upgrading to 1.6 on a fun side-project so can't spend anymore time today troubleshooting this 1.5->1.6 upgrade. I am still using webpack 5 and config from phx 1.5.

Router:

    live "/dialogues", DialogueLive.Index, :index
    live "/dialogues/new", DialogueLive.Index, :new
    live "/dialogues/:id/edit", DialogueLive.Index, :edit

    live "/dialogues/:id", DialogueLive.Show, :show
    live "/dialogues/:id/show/edit", DialogueLive.Show, :edit

@nathanl
Copy link

nathanl commented Oct 19, 2021

A few tips about renaming .leex templates to .heex:

  • On Elixir 1.13 (not yet released; I tested using commit adff27d27 from master), simply doing this rename will trigger recompilation. On older versions (I tested 1.12.1), it won't; neither renaming the template (eg, user_live/show.html.leex to user_live/show.html.heex) nor modifying the template's contents will trigger recompilation. The Liveview module that the template belongs to (user_live/show.ex) will keep rendering the contents of the old template until you to modify the module itself. At that point, it will recompile with the new template, show you any .heex errors that need fixing, and work normally from then on.
  • Don't forget to add .heex to the live_reload patterns in dev.exs
  • If you're using Tailwind CSS with the JIT feature enabled, it also needs to be configured to look at .heex templates when deciding which CSS selectors are used; otherwise it will stop including styles for selectors that you actually use and will break your styling.

@cchauche
Copy link

cchauche commented Oct 28, 2021

I'm ran in to an issue after upgrading to v 1.6.2 from v 1.5.9 with a test that was auto-generated with the v1.5 of phx_gen_auth after upgrading to 1.6.2 the below test no longer passes.

Failing Test

test "stores the path to redirect to on GET", %{conn: conn} do
      halted_conn =
        %{conn | request_path: "/foo", query_string: ""}
        |> fetch_flash()
        |> UserAuth.require_authenticated_user([])

      assert halted_conn.halted
      assert get_session(halted_conn, :user_return_to) == "/foo"

#  1) test require_authenticated_user/2 stores the path to redirect to on GET (NewFenestra.UserAuthTest)
#     test/new_fenestra/controllers/user_auth_test.exs:142
#     Assertion with == failed
#     code:  assert get_session(halted_conn, :user_return_to) == "/foo"
#     left:  "/"
#     right: "/foo"
#     stacktrace:
#       test/new_fenestra/controllers/user_auth_test.exs:149: (test)

I believe I've tracked down why this test is failing which is a change to phoenix/lib/phoenix/controller.ex the changes happened with commit 44a8d90. Before the current_path/1/2 function used the conn.request_path key but the new normalized_request_path/1 function uses conn.path_info and conn.script_name keys to generate the current path. I believe the test is now failing because the test conn doesn't get a value assigned to conn.path_info so it remains an empty list when it is transformed by the normalized_request_path/1 function which will always return the path "/".

The solution I came to is just to modify the test to match what the new phx_gen_auth creates. There are two more instances to fix in the same test block.

Modified Test Block

test "stores the path to redirect to on GET", %{conn: conn} do
      halted_conn =
        ## Change from: `request_path: "/foo"` to: `path_info: ["foo"]`
        %{conn | path_info: ["foo"], query_string: ""}
        |> fetch_flash()
        |> UserAuth.require_authenticated_user([])

      assert halted_conn.halted
      assert get_session(halted_conn, :user_return_to) == "/foo"

        halted_conn =
        ## Change from: `request_path: "/foo"` to: `path_info: ["foo"]`
        %{conn | path_info: ["foo"], query_string: "bar=baz"}
        |> fetch_flash()
        |> UserAuth.require_authenticated_user([])

      assert halted_conn.halted
      assert get_session(halted_conn, :user_return_to) == "/foo?bar=baz"

      halted_conn =
        ## Change from: `request_path: "/foo?bar"` to: `path_info: ["foo"], query_string: "bar"`
        %{conn | path_info: ["foo"], query_string: "bar", method: "POST"}
        |> fetch_flash()
        |> UserAuth.require_authenticated_user([])

      assert halted_conn.halted
      refute get_session(halted_conn, :user_return_to)

I'm wondering if there are other issues people know about when upgrading to v1.6.2 with a project that is using authorization generated by an older version of phx_gen_auth?

@marcandre
Copy link

marcandre commented Oct 29, 2021

Could you please mention to add the following to config/test.exs:

# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime

@Edward-Heales
Copy link

Edward-Heales commented Nov 2, 2021

Could you please mention to add the following to config/test.exs:

# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime

https://github.com/phoenixframework/phoenix/blob/v1.5/CHANGELOG.md#1510-2021-08-06
Would he need to add it if it has been specified in the v1.4 -> v1.5 official changelog

@marcandre
Copy link

marcandre commented Nov 2, 2021

@Edward-Heales good point, I missed that completely when I upgraded from 1.5.9 to 1.6.x. I'd say it is unfortunate that a change in the source is required on a patch-level release and that it wouldn't hurt to repeat this here in the upgrade guide. As of now, this is not listed in any upgrade guide.

@Edward-Heales
Copy link

Edward-Heales commented Nov 3, 2021

@marcandre Agreed.

@sweeneydavidj
Copy link

sweeneydavidj commented Nov 17, 2021

To better deal with files referenced in CSS or JavaScript there was a small change released in Phoenix 1.6.1
Add external flag to esbuild for fonts and images

@marcandre
Copy link

marcandre commented Nov 24, 2021

It should be noted that any package added to package.json/.lock needs to be re-added after these files are deleted.

@marcandre
Copy link

marcandre commented Nov 24, 2021

Also, target=2016 is quite possible the wrong setting, see phoenixframework/phoenix#4579

@marcandre
Copy link

marcandre commented Nov 25, 2021

Also, same line is missing --external:/fonts/* --external:/images/*, see phoenixframework/phoenix@19ec3c6

@marcandre
Copy link

marcandre commented Nov 26, 2021

IIUC, we should move images and other static assets to priv/static, e.g. mv assets/static/images assets/static/fonts assets/static/video assets/static/docs assets/static/favicon.ico assets/static/robots.txt priv/static, and modify our .gitignore to no longer ignore /priv/static/ but to ignore both /priv/static/assets/ and /priv/static/cache_manifest.json, right?

@kaikuchn
Copy link

kaikuchn commented Jan 14, 2022

IIUC, we should move images and other static assets to priv/static, e.g. mv assets/static/images assets/static/fonts assets/static/video assets/static/docs assets/static/favicon.ico assets/static/robots.txt priv/static, and modify our .gitignore to no longer ignore /priv/static/ but to ignore both /priv/static/assets/ and /priv/static/cache_manifest.json, right?

That's partially correct. You should unignore priv/static and ignore priv/static/assets [1] but you don't need to ignore the cache manifest file since that is the result of phx.digest which should only be run for deployment. After running mix assets.deploy locally you can clean up the phx.digest artifacts using mix phx.digest.clean --all.

[1] https://www.reddit.com/r/elixir/comments/p9t68v/with_the_new_esbuild_transition_what_do_you_use/ha0cskv/?context=3

@marcandre
Copy link

marcandre commented Jan 14, 2022

Thanks @kaikuchn, you are right.
I feel frustrated that we can't improve these instructions yet

@florish
Copy link

florish commented Feb 2, 2022

After doing a couple of Phoenix 1.5 to 1.6 upgrades in the last few weeks, I've found that a diff between the phx.new code from 1.5.0 and 1.6.6 can be very helpful as an extra check to see that you did not miss anything:

https://utils.zest.dev/gendiff/phx_new/62953723EA6DCD4A95D027D4EB99CBAA

Note that some phx.new code has been changed between 1.6.0 and 1.6.6 to fix minor bugs (e.g. es2016 has been replaced with es2017 as the target in the esbuild Elixir config). This all happened after this Gist was published, but is quite relevant in case you are planning a Phoenix 1.6 upgrade right now.

To be sure: most of what is in the diff above is covered in the comments in this Gist, but it can help to see all these changes in one place.

@arfl
Copy link

arfl commented Feb 2, 2022

@florish
Copy link

florish commented Feb 2, 2022

@arfl Hey that's even better! For my understanding: apart from the layout, the diff content is the same as for the link I posted, isn't it?

@arfl
Copy link

arfl commented Feb 3, 2022

@florish I do not know exactly, but the source code is on github (link to github repo in the right upper corner of the app). Seems that they just generate two versions of the app using the --live option and call this code to generate the diff.

@pinksynth
Copy link

pinksynth commented Jun 11, 2022

Very helpful! Noticed a typo: assetes instead of assets:

rm -rf assetes/node_modules

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment