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.
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.
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"
]
}
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!
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.
npm run build
, mix phx.digest
and you are ready to go.