This documents how I integrate Vue 2.0 with Phoenix 1.x using the default brunch pipeline.
Start out by adding the vue-brunch plugin. You will need a version later than 1.2.3 in order to be able to use the extractCSS
option (see later). At the time of writing, 1.2.3 was still the version fetched by npm so I suggest just getting the tip of the dev
branch for now (this branch is for Vue 2.0 compatibility anyway):
npm install git+https://github.com/nblackburn/vue-brunch.git#dev --save-dev
This will make it possible to use single-file .vue
components, which I find to be a big help in keeping code organized and easy to follow. It will also enable pre-compiled Vue templates, which is a must for large applications.
In brunch-config.js
add the following plugin configuration:
plugins: {
...
vue: {
extractCSS: true,
out: 'priv/static/css/components.css'
}
...
}
This will disable in-line CSS injection which at the moment results in a runtime error in the browser because of a missing Vueify dependency. Alternatively, you can leave out the extractCSS
option and explicitly import the missing module to enable inline CSS style injection e.g. in your web/static/js/app.js
file:
import "vueify/lib/insert-css"
but in this case I suspect you should consider wrapping the import in a Phoenix <%= if %>
statement as it only makes sense in development, and then use the extractCSS
option in prod.
The out
option tells the Vueify compiler where to place the extracted component CSS -- don't foget to add a proper include statement to your application layout web/templates/layout/app.html.eex
:
<link rel="stylesheet" href="<%= static_path(@conn, "/css/components.css") %>">
At this point you are all set with respect to the toolchain and can just start the brunch watcher alongside the phoenix server, as usual:
npm run watch
mix phoenix.server
The vue-brunch
plugin will use Vueify behind the scenes to extract and compile Vue templates from the .vue
files and merge the resulting render functions along with the component code itself (also extracted) into the global priv/static/js/app.js
file.
I prefer to add all my Vue components as .vue
files in a separate directory web/static/components
, e.g. web/static/compnents/my-app.vue
:
<template>
<div class="my-app">
<h1>My Awesome App</h1>
{{message}}
</div>
</template>
<script>
// Import other components using a relative path, e.g.
//
// import SecretSauce from './secret-sauce.vue'
//
// or specific third-party modules installed via npm (and listed in
// the dependencies list in package.json), e.g.
//
// import _ from 'lodash'
//
export default {
props: ['message'],
// If you reference other components, then you must also list them
// in the components map. e.g:
//
// components: {
// SecretSauce
// }
}
</script>
<style lang="sass">
.my-app {
margin-left: auto;
margin-right: auto;
width: 800px;
h1 {
text-align: center;
margin-top: 50px;
margin-bottom: 100px;
}
}
</style>
I then bootstrap the Vue app in web/static/js/app.js
, e.g:
'use strict';
import Vue from 'vue'
import MyApp from "../components/my-app.vue"
// Import "globally" used libraries -- importing them from Vue components alone
// seems to trip up brunch at the moment (they won't be included in the final
// app.js file)
import "lodash"
// Create the main component
Vue.component('my-app', MyApp)
// And create the top-level view model:
new Vue({
el: '#app',
data() {
return {
// state for the top level component, e.g
currentMessage: "Hello World"
}
},
render: function (createElement) {
return createElement(MyApp, {
props: {
// props for the top level component, e.g.
message: this.currentMessage
}
})
}
});
Finally, I add the top-level component tag to the "index" Phoenix template, e.g web/templates/home/index.html.eex
:
<div id="app">
<my-app :message='message'><my-app>
</div>
From there on out its .vue
all the way down.