Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save itswindtw/2e2281855d055590021f092a0a2ddcee to your computer and use it in GitHub Desktop.
Save itswindtw/2e2281855d055590021f092a0a2ddcee 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:

module.exports = {
  lintOnSave: true,
  outputDir: "../priv/static",
  assetsDir: "assets"
};

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. I use hash mode for Vue Router, and I would like to leverage Plug.Static due to its etag cache mechanism.

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

defmodule HelloWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :hello_web
  
  socket "/socket", HelloWeb.UserSocket

  plug :rewrite_index
  
  plug Plug.Static,
    at: "/", from: :hello, gzip: false,
    only: ~w(assets favicon.ico robots.txt index.html)
 
  ...
  
  defp rewrite_index(%Plug.Conn{path_info: []} = conn, _opts) do
    %Plug.Conn{conn | path_info: ["index.html"]}
  end
  
  defp rewrite_index(conn, _opts), do: conn
end

We simply provide a default request_path to Phoenix. Plug.Static will take care of everything for us.

Rerun the server and try to navigate to http://localhost:4000/, it'll bring you to the index.html 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.

Create assets/serve.js. We need a wrapper performing npm run serve to make sure server will be closed when Phoenix shuts down.

const spawn = require("child_process").spawn;
let watcher = spawn("npm", ["run", "serve"], { stdio: "inherit" });

process.stdin.on("end", function() {
  process.kill(watcher.pid);
  process.exit(0);
});

process.stdin.resume();

Edit config/dev.exs

config :hello, HelloWeb.Endpoint,
  ...
  watchers: [
    node: ["serve.js", cd: Path.expand("../assets", __DIR__)]
  ]

Now mix phx.server will bring both servers up.

Production Build

npm run build, mix phx.digest and you are ready to go.

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