Instantly share code, notes, and snippets.

Embed
What would you like to do?
Phoenix 1.3.x to 1.4.0 Upgrade Guides

Phoenix 1.4 ships with exciting new features, most notably with HTTP2 support, improved development experience with faster compile times, new error pages, and local SSL certificate generation. Additionally, our channel layer internals receiveced an overhaul, provided better structure and extensibility. We also shipped a new and improved Presence javascript API, as well as Elixir formatter integration for our routing and test DSLs.

This release requires few user-facing changes and should be a fast upgrade for those on Phoenix 1.3.x.

Install the new phx.new project generator

The mix phx.new archive can now be installed via hex, for a simpler, versioned installation experience.

To grab the new archive, simply run:

$ mix archive.uninstall phx_new
$ mix archive.install hex phx_new 1.4.0

Update Phoenix and Cowboy deps

To get started, simply update your Phoenix dep in mix.exs:

{:phoenix, "~> 1.4.0"}

Next, replace your :cowboy dependency with :plug_cowboy:

{:plug_cowboy, "~> 2.0"}
{:plug, "~> 1.7"}

To upgrade to Cowboy 2 for HTTP2 support, use ~> 2.0 as above. To stay on cowboy 1, pass ~> 1.0.

Finally, remove your explicit :ecto dependency and update your :phoenix_ecto and :ecto_sql dependencies with the following versions:

  ...,
  {:ecto_sql, "~> 3.0"},
  {:phoenix_ecto, "~> 4.0"}

After running mix deps.get, then be sure to grab the latest npm pacakges with:

$ cd assets
$ npm install

Update your Jason configuration

Phoenix 1.4 uses Jason for json generation in favor of poison. Poison may still be used, but you must add :poison to your deps to continue using it on 1.4. To use Jason instead, :jason to your deps in mix.exs:

  ...,
  {:jason, "~> 1.0"},

Then add the following configuration in config/config.exs:

# Use Jason for JSON parsing in Phoenix
config :phoenix, :json_library, Jason

Update your UserSocket

Phoenix 1.4 deprecated the transport macro, in favor of providing transport information directly on the socket call in your endpoint. Make the following changes:

# app_web/channels/user_socket.ex
- transport :websocket, Phoenix.Transports.WebSocket
- transport :longpoll, Phoenix.Transports.LongPoll, [check_origin: ...]

# app_web/endpoint.ex
- socket "/socket", MyAppWeb.UserSocket
+ socket "/socket", MyAppWeb.UserSocket, 
+   websocket: true # or list of options
+   longpoll: [check_origin: ...]

Update your Presence javascript

A new, backwards compatible Presence JavaScript API has been introduced to both resolve race conditions as well as simplify the usage. Previously, multiple channel callbacks against "presence_state and "presence_diff" events were required on the client which dispatched to Presence.syncState and Presence.syncDiff functions. Now, the interface has been unified to a single onSync callback and the presence instance tracks its own channel callbacks and state. For example:

import {Socket, Presence} from "phoenix"

let renderUsers(presence){
-  someContainer.innerHTML = Presence.list(presence, (id, user) {
-    `<br/>${escape(user.name)}`
-  }.join("")
+  someContainer.innerHTML = presence.list((id, user) {
+    `<br/>${escape(user.name)}`   
+  }).join("")
}

let onJoin = (id, current, newPres) => {
  if(!current){
    console.log("user has entered for the first time", newPres)
  } else {
    console.log("user additional presence", newPres)
  }
}
 
let onLeave = (id, current, leftPres) => {
  if(current.metas.length === 0){
    console.log("user has left from all devices", leftPres)
  } else {
    console.log("user left from a device", leftPres)
  }
})

let channel = new socket.channel("...")
- let presence = {}
- channel.on("presence_state", state => {
-   presence = Presence.syncState(presence, state, onJoin, onLeave)
-   renderUsers(presence)
- })
- channel.on("presence_diff", diff => {
-   presence = Presence.syncDiff(presence, diff, onJoin, onLeave)
-   renderUsers(presence)
- })
+ let presence = new Presence(channel)

+ presence.onJoin(onJoin)
+ presence.onLeave(onLeave)
+ presence.onSync(() => renderUsers(presence))

Optional Updates

The above changes are the only ones necessary to be up and running with Phoenix 1.4. The remaining changes will bring you up to speed with new conventions, but are strictly optional.

Add formatter support

Phoenix 1.4 includes formatter integration for our routing and test DSLs. Create or ammend your .formatter.exs in the root of your project(s) with the following:

[
  import_deps: [:phoenix],
  inputs: ["*.{ex,exs}", "{config,lib,priv,test}/**/*.{ex,exs}"]
]

Add a Routes alias and update your router calls

A Routes alias has been added to app_web.ex for view and controller blocks in favor over the previously imported AppWeb.Router.Helpers.

The new Routes alias makes it clearer where page_path/page_url and friends exist and removes compile-time dependencies across your web stack. To use the latest conventions, make the following changes to app_web.ex:

- import AppWeb.Router.Helpers
+ alias AppWeb.Router.Helpers, as: Routes

Next, update any controllers, views, and templates calling your imported helpers or static_path|url, to use the new alias, for example:

- <%= link "show", to: user_path(@conn, :show, @user) %>
+ <%= link "show", to: Routes.user_path(@conn, :show, @user) %>

- <script type="text/javascript" src="<%= static_url(@conn, "/js/app.js") %>"></script>
+ <script type="text/javascript" src="<%= Routes.static_url(@conn, "/js/app.js") %>"></script>

Replace Brunch with webpack

The mix phx.new generator in 1.4 now uses webpack for asset generation instead of brunch. The development experience remains the same – javascript goes in assets/js, css goes in assets/css, static assets live in assets/static, so those not interested in JS tooling nuances can continue the same patterns while using webpack. Those in need of optimal js tooling can benefit from webpack's more sophisticated code bunding, with dead code elimination and more.

To proceed:

  • update assets/package.json to replace Brunch with webpack:
   "repository": {},
   "license": "MIT",
   "scripts": {
-    "deploy": "brunch build --production",
-    "watch": "brunch watch --stdin"
+    "deploy": "webpack --mode production",
+    "watch": "webpack --mode development --watch-stdin"
   },
   "dependencies": {
     "phoenix": "file:../deps/phoenix",
     "phoenix_html": "file:../deps/phoenix_html"
   },
   "devDependencies": {
-    "babel-brunch": "6.1.1",
-    "brunch": "2.10.9",
-    "clean-css-brunch": "2.10.0",
-    "uglify-js-brunch": "2.10.0"

+    "@babel/core": "^7.0.0",
+    "@babel/preset-env": "^7.0.0",
+    "babel-loader": "^8.0.0",
+    "copy-webpack-plugin": "^4.5.0",
+    "css-loader": "^0.28.10",
+    "mini-css-extract-plugin": "^0.4.0",
+    "optimize-css-assets-webpack-plugin": "^4.0.0",
+    "uglifyjs-webpack-plugin": "^1.2.4",
+    "webpack": "4.4.0",
+    "webpack-cli": "^2.0.10"
   }
 }
  • delete assets/brunch-config.js
  • create assets/.babelrc with the following contents:
{
    "presets": [
        "env"
    ]
}
  • create assets/webpack.config.js with the following contents:
const path = require('path');
const glob = require('glob');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = (env, options) => ({
  optimization: {
    minimizer: [
      new UglifyJsPlugin({ cache: true, parallel: true, sourceMap: false }),
      new OptimizeCSSAssetsPlugin({})
    ]
  },
  entry: {
      './js/app.js': ['./js/app.js'].concat(glob.sync('./vendor/**/*.js'))
  },
  output: {
    filename: 'app.js',
    path: path.resolve(__dirname, '../priv/static/js')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      },
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({ filename: '../css/app.css' }),
    new CopyWebpackPlugin([{ from: 'static/', to: '../' }])
  ]
});
  • Update config/dev.exs to use webpack instead of brunch:
-  watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin",
+  watchers: [
+    node: [
+      "node_modules/webpack/bin/webpack.js",
+      "--mode",
+      "development",
+      "--watch-stdin",
+      cd: Path.expand("../assets", __DIR__)
+    ]

CSS changes

  • The main CSS bundle must now be imported from the app.js file. Add the following line to the top of assets/js/app.js:
import css from '../css/app.css';
  • If you are using the default css, replace assets/css/phoenix.css with the following:

https://raw.githubusercontent.com/phoenixframework/phoenix/89cdcfbaa041da1daba39e39b0828f6a28b6d52f/installer/templates/phx_assets/phoenix.css

@theangryangel

This comment has been minimized.

theangryangel commented Oct 10, 2018

If it helps anyone else this grep, xargs, sed invocation dealt with 99% of my route call changes (your mileage may vary, please ensure you have a version you can revert to if it goes wrong in your environment, etc.)

grep -Rl "\s[a-zA-Z_]\+_\(path\|url\)(@\?conn" lib | xargs sed -i -r -e "s/\s([a-zA-Z_]+)_(path|url)\((@)?conn/ Routes.\1_\2(\3conn/g"
@bzitzow

This comment has been minimized.

bzitzow commented Oct 11, 2018

I missed this in the forum post, so sharing here in case others get the same error.

To upgrade the local mix phoenix.new command, first uninstall:

$ mix archive.uninstall phx_new
$ mix archive.install hex phx_new 1.4.0-rc.0

I ran only:

$ mix archive.install hex phx_new 1.4.0-rc.0

And got compilation error:

== Compilation error in file lib/phx_new/ecto.ex ==
** (File.Error) could not read file "/PATH_REDACTED/phoenix/installer/templates/phx_ecto/repo.ex": no such file or directory
    (elixir) lib/file.ex:319: File.read!/1
    lib/phx_new/generator.ex:28: anonymous fn/4 in Phx.New.Generator."MACRO-__before_compile__"/2
    (elixir) lib/enum.ex:1925: Enum."-reduce/3-lists^foldl/2-0-"/3
    lib/phx_new/generator.ex:24: anonymous fn/3 in Phx.New.Generator."MACRO-__before_compile__"/2
    (elixir) lib/enum.ex:1925: Enum."-reduce/3-lists^foldl/2-0-"/3
    expanding macro: Phx.New.Generator.__before_compile__/1
    lib/phx_new/ecto.ex:1: Phx.New.Ecto (module)

The solution was to uninstall and re-install:

$ mix archive.uninstall phx_new
$ mix archive.install hex phx_new 1.4.0-rc.0

Thank you to Eric Meadows-Jonsson for the tip on slack. His advice:

try `mix archive.uninstall phx_new`
it’s a bug in elixir, the fix will ship in elixir 1.7.4
@anticide

This comment has been minimized.

anticide commented Oct 11, 2018

.babelrc should contain:

{
    "presets": [
        "@babel/preset-env"
    ]
}

Since "env" is referencing the old version.

@pushpankar

This comment has been minimized.

pushpankar commented Oct 18, 2018

I am getting

ERROR in ./js/app.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
Error: Cannot find module 'babel-preset-env' from '/Users/pushpankar/til/assets'
- Did you mean "@babel/env"?
@anticide

This comment has been minimized.

anticide commented Oct 18, 2018

@pushpankar read my other comment, should fix your issue.

@chrismccord can you please update .babelrc?

@pushpankar

This comment has been minimized.

pushpankar commented Oct 19, 2018

@anticide When I use this one as .babelrc

{
  "presets": [
    "@babel/preset-env"
  ]
}

I get syntax error

ERROR in ./js/app.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: /Users/pushpankar/til/assets/js/app.js: Unexpected token (59:2)

  57 | // console.log(client)
  58 | const App = () => (
> 59 |   <BrowserRouter>
     |   ^
  60 |     <ApolloProvider client={client}>
  61 |       <Main />
  62 |     </ApolloProvider>

I tried to add react to .babelrc like this

{
  "presets": [
    "@babel/preset-env",
    "react"
  ]
}

but then got
Error: Plugin/Preset files are not allowed to export objects, only functions. In /Users/pushpankar/til/assets/node_modules/babel-preset-react/lib/index.js
I guess issue is related to react/webpack not phoenix1.4

@anticide

This comment has been minimized.

anticide commented Oct 19, 2018

You're using React, Babel doesn't understand JSX, so you need to install another preset that translates JSX to JS.

First install the dependency:

npm install --save-dev @babel/preset-react

Then add it to .babelrc:

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ]
}

And that's it, more info: https://babeljs.io/docs/en/next/babel-preset-react

@pushpankar

This comment has been minimized.

pushpankar commented Oct 19, 2018

Thank you @anticide. I used "@babel/react" instead of "react" and the problem was solved.

@phoffer

This comment has been minimized.

phoffer commented Oct 20, 2018

The web socket example is missing a comma, just for people copy-pasting @chrismccord. Should be after websocket: true

# app_web/endpoint.ex
- socket "/socket", MyAppWeb.UserSocket
+ socket "/socket", MyAppWeb.UserSocket, 
+   websocket: true, # or list of options
+   longpoll: [check_origin: ...]
@behe

This comment has been minimized.

behe commented Oct 24, 2018

When using brunch I get an error when trying to build phoenix.js for production:

LOGGY_STACKS=1 node_modules/brunch/bin/brunch build --production

21:20:22 - error: Optimizing of ../priv/static/js/app.js failed. L154:1366 SyntaxError: Unexpected token: name (f)
@Raphx

This comment has been minimized.

Raphx commented Oct 25, 2018

Regarding plug:

{:plug_cowboy, "~> 2.0"}
{:plug, "~> 1.7"}

:plug_cowboy has an explicit dependency on :plug ~> 1.7, so I think it's safe to omit specifying plug in our mix.exs when upgrading from phoenix 1.3.x to 1.4.0.

@navinpeiris

This comment has been minimized.

navinpeiris commented Nov 8, 2018

You can also see a full diff of changes between apps generated between 1.4.0 and earlier versions at: https://www.phoenixdiff.org/?source=1.3.4&target=1.4.0

@hykw

This comment has been minimized.

hykw commented Nov 8, 2018

It also requires:

{:cowboy, "~> 2.5"}

and the following, before mix deps.get

  • mix deps.unlock cowlib
  • mix deps.unlock ranch
@zacksiri

This comment has been minimized.

zacksiri commented Nov 8, 2018

It also requires:

{:cowboy, "~> 2.5"}

and the following, before mix deps.get

  • mix deps.unlock cowlib
  • mix deps.unlock ranch

Actually if you have plug_cowboy you don't need to add cowboy directly anymore.

@hykw

This comment has been minimized.

hykw commented Nov 8, 2018

Actually if you have plug_cowboy you don't need to add cowboy directly anymore.

Ah, yes. But since this is an upgrade guide from 1.3, it is better to write either {:cowboy, "~> 2.5"} or remove {:cowboy, "~> 1.0"} then.

@humberaquino

This comment has been minimized.

humberaquino commented Nov 9, 2018

Used this guide to migrate from Phoenix 1.3.3 to 1.4.0 and everything worked great except for webpack. But after using "@babel/preset-env" instead of "env" within the .babelrc file, the bundle got created.

Thanks @anticide for the fix and @chrismccord for this guide and the awesome release!

@rockwood

This comment has been minimized.

rockwood commented Nov 12, 2018

If it helps anyone else this grep, xargs, sed invocation dealt with 99% of my route call changes (your mileage may vary, please ensure you have a version you can revert to if it goes wrong in your environment, etc.)

grep -Rl "\s[a-zA-Z_]\+_\(path\|url\)(@\?conn" lib | xargs sed -i -r -e "s/\s([a-zA-Z_]+)_(path|url)\((@)?conn/ Routes.\1_\2(\3conn/g"

For those on Mac OS, sed behaves a little differently. This worked for me:

grep -Rl "\s[a-zA-Z_]\+_\(path\|url\)(@\?conn" lib | xargs sed -i.bk -E "s/([a-zA-Z_]+)_(path|url)\((@)?conn/Routes.\1_\2(\3conn/g"
@TomBers

This comment has been minimized.

TomBers commented Nov 12, 2018

Thank you @chrismccord for this excellent guide. Can I just echo, if the right fix for the .babelrc is to change "env" to "@babel/preset-env" can the main doc reflect this, I was scratching my head for a while.

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