Skip to content

Instantly share code, notes, and snippets.

@kgllev
Created July 14, 2019 11:07
Show Gist options
  • Save kgllev/7b0d9dcfa094e550a5d9e0dda8f6f241 to your computer and use it in GitHub Desktop.
Save kgllev/7b0d9dcfa094e550a5d9e0dda8f6f241 to your computer and use it in GitHub Desktop.
Setting up a Basic Phoenix+Vue+Brunch project

Work in Progress: Phoenix + Vue + Brunch

This documents how I integrate Vue 2.0 with Phoenix 1.x using the default brunch pipeline.

The toolchain

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.

The code

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.

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