Skip to content

Instantly share code, notes, and snippets.

@jpbecotte
Last active August 23, 2020 05:32
Show Gist options
  • Save jpbecotte/d37260ecd64fc4cbef7e9dbbf7f6bef3 to your computer and use it in GitHub Desktop.
Save jpbecotte/d37260ecd64fc4cbef7e9dbbf7f6bef3 to your computer and use it in GitHub Desktop.
Vue-cli 3, Phoenix 1.3, a complete how-to

Introduction

I have been struggling to start a new project with Phoenix 1.3 and the new vue-cli 3 for Vue.js. There are tons of example already but none of them suited my needs, because:

  • I want to use the new Vue-cli to select the features that I want,
  • I do NOT want to setup Webpack (I know, what a shame!). The new Vue-cli includes the new vue-cli-service, which uses an instance of webpack-dev-server, so you don't have to import it manually in your project.
  • I do not want to use Brunch.

Create your Phoenix App

Assuming that you have Elixir and Phoenix 1.3 are both installed, let's build our new App.

$ mix phx.new hello --no-brunch
$ cd hello
$ mix ecto.create

You should have the directory structure of your freshly created project.

Creating the structure of the front-end part, including Vue.js

The next step will be to create what's inside the assets folder. Phoenix 1.3 introduce some changes where everything from the front-end must be inside this folder. I think that's an amazing approach, leaving all the related files such as package.json isolated from the rest.

Then, if you haven't done yet, let's install the Vue-cli toolkit. More information can be found here: https://github.com/vuejs/vue-cli.

$ npm install -g @vue/cli

Make sure you are inside your project folder, and then simply create a new project with this command. It will create a directory named assets that will be specially made for your needs.

$ vue create assets

That's where the fun starts! The vue-cli tool asks us which features we want. It guides you through the selection. It even asks you for which style pre-processor you want to use, linter, unit testing environments (Hello to Jest!!!), etc.

Vue CLI v3.0.0-beta.1
? Please pick a preset: Manually select features
? Check the features needed for your project:
 ◉ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◉ Router
 ◉ Vuex
 ◉ CSS Pre-processors
 ◯ Linter / Formatter
❯◉ Unit Testing
 ◯ E2E Testing

Once this is done, make sure the structure is in the assets folder, and just contemplate on how small is the package.json file:

{
  "name": "assets",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve --open",
    "build": "vue-cli-service build",
    "test": "vue-cli-service test"
  },
  "dependencies": {
    "vue": "^2.5.13",
    "vue-router": "^3.0.1",
    "vuex": "^3.0.1"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^3.0.0-beta.1",
    "@vue/cli-plugin-unit-jest": "^3.0.0-beta.1",
    "@vue/cli-service": "^3.0.0-beta.1",
    "@vue/test-utils": "^1.0.0-beta.10",
    "babel-core": "^7.0.0-0",
    "babel-jest": "^22.0.4",
    "node-sass": "^4.7.2",
    "sass-loader": "^6.0.6",
    "vue-template-compiler": "^2.5.13"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}

The first build

We'll come to Hot Module Reloading (HMR) later. For now, all we need is to produce an index.html that can be served by Phoenix. Before doing so, we must tell Vue-cli to produce its own build in the priv/static folder, which is the official directory for static content. (Keep in mind that in production, this is where our front-end build will reside). For this, we'll add a vue.config.js in our assets directory, containing the following lines:

const path = require('path');

module.exports = {
  lintOnSave: true,
  configureWebpack: {
    output: {
      path: path.resolve(__dirname, '../priv/static'),
    },
  },
};

Once this is done, run a production build:

$ npm run build

 DONE  Compiled successfully in 19858ms                                                                                                                                                 17 h 19 min 02 s

  File                                    Size              Gzipped

  ../priv/static/js/vendor.13e9fbad.js    94.72 kb          32.19 kb
  ../priv/static/js/app.5859fc59.js       12.82 kb          8.07 kb
  ../priv/static/css/app.d88a32fa.css     0.42 kb           0.26 kb

  Images and other types of assets omitted.

 DONE  Build complete. The ../priv/static directory is ready to be deployed.

Finally, we must tell Phoenix to serve this fantastic index.html we just created. It will also be our default route, and because I want Vue's router to use the HTML5 History Mode to handle 404's, we need to make it our fallback default route.

First, edit lib/hello_web/controllers/page_controller.ex so it ressembles to this:

defmodule HelloWeb.PageController do
  use HelloWeb, :controller

  def index(conn, _) do
    conn
    |> put_resp_header("content-type", "text/html; charset=utf-8")
    |> Plug.Conn.send_file(200, "priv/static/index.html")
    |> halt()
  end
end

We simply tell Phoenix to serve the static index.html.

Then, modify the following line in lib/morrier_web/router.ex so it matches anything:

    get "/*anything", PageController, :index

Rerun the server and try to navigate to http://localhost:4000/about, it'll bring you to the specific page of our app!

HMR (Hot Module Reload)

We will be using the webpack-dev-server that is packaged with Vue-cli, and not the one that comes bundled with Phoenix. That means two servers will co-exists in your dev environment.

To run the backend process:

$ mix phx.server

And to run the front-end server (usually localhost:8080):

$ cd assets
$ npm run serve

Production Build

To deploy (on Heroku), you don't need the Phoenix buildpack. I've deceided to build before push to heroku. So run npm run build and commit the files in priv/static.

Also, make sure all you images are in assets/public and referenced correctly in your code. I found it weird at first to import images in JS, but that's how Webpack works.

@Nayshins
Copy link

Nayshins commented Aug 5, 2018

I got it to work using this:

const path = require('path');

module.exports = {
  lintOnSave: true,
  outputDir: path.resolve(__dirname, '../priv/static'),
};

@kamidev
Copy link

kamidev commented Aug 5, 2018

Works with Phoenix 1.4, too. Just create the project with:

mix phx.new hello --no-webpack

@mmccall10
Copy link

Might need to add "img" to the Plug.Static endpoint config.

plug(
    Plug.Static,
    at: "/",
    from: :my_app,
    gzip: false,
    only: ~w(css fonts images img js favicon.ico robots.txt)
  )

@ijunaid8989
Copy link

Works with Phoenix 1.4, too. Just create the project with:

mix phx.new hello --no-webpack

but then Vuejs will add webpack it self?

@ijunaid8989
Copy link

when you do npm server or yarn server. it starts a separate server at localhost:8080..

how can you configure it this way that..

when you change anything in asset folder it server files instead of creating a new server?

@ctrlShiftBryan
Copy link

as of phoenix 1.4 and vue cli 3.7.0 this is the vue.config.js I needed.

module.exports = {
  outputDir: "../priv/static",
  configureWebpack: {
    output: {
      filename: "js/[name].js",
      chunkFilename: "js/[name].js"
    }
  }
};

@lobo-tuerto
Copy link

lobo-tuerto commented Apr 25, 2020

Changes made on files inside assets aren't being compiled and displayed through the Phoenix app in localhost:4000, you need to see them through localhost:8080.

If you want to see those new changes done in Vue, but through Phoenix you'll need to cd assets && yarn build everytime.

I was wondering if there is a way to make the vue-cli-service output any changes to priv/assets.
Just like how the default Webpack server that comes with Phoenix do.

@McRaeAlex
Copy link

@lobo-tuerto I can get the same sort of developer experience by using the proxy. I'm digging deeper into how to replicate the behavior you want as I would also like that. I will post it here if I figure it out. (if someone else figures it out please post it) Thanks! 😀

@McRaeAlex
Copy link

@lobo-tuerto
Copy link

@McRaeAlex I'll check it out. Thanks!

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