Opinions

In the time I have delving into Vue.js I’ve come to really appreciate the framework, and its surrounding libraries.

This opinionated guide details the steps I take to start with a solid foundation for a new Vue.js project.


What particular opinions does it include?
Well, for starters:

CLI tools

  • Package manager: Yarn — Fast, reliable, and secure dependency management.
  • Project generation tool: vue-cli — CLI for rapid Vue.js development.

UI

  • UI component framework: Vuetify — Material Design component framework.
  • Material icons library: Material Design Icons — 4000+ Material Design Icons from the Community.
  • Validation library: Vuelidate — Simple, lightweight model-based validation for Vue.js.
  • Data visualization library: D3 — A JavaScript library for visualizing data using web standards.

Utility libraries

  • HTTP client: Axios — Promise based HTTP client for the browser and Node.js.
  • Utility library: Lodash — A modern JavaScript utility library.
  • State management pattern + library: Vuex — Centralized state management for Vue.js.
  • Date library: Moment.js — A lightweight JavaScript date library for parsing, validating, manipulating, and formatting dates.

Template and preprocessor languages

  • HTML template engine: Pug — A robust, elegant, feature rich template engine for Node.js.
  • CSS preprocessor language: Sass — Sass is the most mature, stable, and powerful professional grade CSS extension language in the world.

Misc

  • Filters: vue2-filters — A collection of standard filters for Vue.js.
  • i18n: vue-i18n — Internationalization plugin for Vue.js.

I have found that these tools and libraries are performant, intuitive and very easy to work with.

Browser developer tools

Last, but definitely not least:


I had a similar stack for Angular that included Angular Material plus some custom component primitives for rendering dynamic forms, data tables and other stuff.

I was really fond of the dynamic forms implementation, it allowed the user to specify highly configurable forms using a simple JSON specification.
The generated forms integrated well with our Rails JSON API backend.

I plan to write a tutorial about doing the same thing but this time with Vue.js and Vuetify, but I digress…

Setting up a new Vue.js app for success

Here we’ll see how to setup a newly created app with vue-cli so it’ll be ready for us to start hacking on it right away.

Prerequisites

Install Node.js, Yarn and vue-cli

Generate a new project

vue create my-project

My usual preset looks like this:

Vue CLI v3.10.0

? Please pick a preset:
Manually select features

? Check the features needed for your project:
Babel, PWA, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E

? Use history mode for router?
(Requires proper server setup for index fallback in production)
No

? Pick a CSS pre-processor
(PostCSS, Autoprefixer and CSS Modules are supported by default):
Sass/SCSS (with dart-sass)

? Pick a linter / formatter config:
Standard

? Pick additional lint features:
Lint on save

? Pick a unit testing solution:
Jest

? Pick a E2E testing solution:
Cypress

? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.?
In dedicated config files

Install project dependencies

Let’s use the new vue add command to install Vuetify in our brand new project.

vue add vuetify

You’ll see something along the lines of:

📦  Installing vue-cli-plugin-vuetify...
✔  Successfully installed plugin: vue-cli-plugin-vuetify

? Choose a preset: Configure (advanced)
? Use a pre-made template? (will replace App.vue and HelloWorld.vue) Yes
? Use custom theme? Yes
? Use custom properties (CSS variables)? Yes
? Select icon font Material Design Icons
? Use fonts as a dependency (for Electron or offline)? No
? Use a-la-carte components? Yes
? Select locale English

🚀  Invoking generator for vue-cli-plugin-vuetify...
📦  Installing additional dependencies...
⚓  Running completion hooks...

✔  Successfully invoked generator for plugin: vue-cli-plugin-vuetify
   The following files have been updated / added:

     src/assets/logo.svg
     src/plugins/vuetify.js
     package.json
     public/index.html
     src/App.vue
     src/components/HelloWorld.vue
     src/main.js
     src/views/Home.vue
     yarn.lock

   You should review these changes with git diff and commit them.

After file generation, take this opportunity to run:

yarn lint

If when trying to add the latest changes to Git you’re greeted by something like:

fatal: CRLF would be replaced by LF in src/plugins/vuetify.js.

Fix it by opening that file in VSCode and change the End of Line Sequence from CRLF to LF —you’ll find the control for that near the bottom right corner.


Let’s add some other project dependencies —these are the ones I always end up installing at one point or another. Customize at your leisure:

yarn add axios d3 lodash moment vue-i18n vue2-filters vuelidate

I like having the power of Sass at my disposal when writing CSS rules.
Also, I like to write my templates using Pug for simplified templates.

--dev will add dependencies to the devDependencies section in your package.json file:

yarn add pug pug-plain-loader --dev

Initial app configuration and setup

Adjust ESLint rules

Add this line to the rules key in the .eslintrc.js file:

'no-multiple-empty-lines': [2, { max: 2 }]

The reason for this change, is that I usually leave two consecutive blank lines between some elements inside my .js and .vue component files.

For example between import sections and following code for .js files.
Or between <template>, <script> and <style> sections for .vue files.

Setup i18n

To setup Vue I18n, add a src/plugins/vue-i18n.js file:

import Vue from 'vue'
import VueI18n from 'vue-i18n'


Vue.use(VueI18n)

const messages = {
  de: {
    message: {
      hello: 'hallo welt'
    }
  },
  en: {
    message: {
      hello: 'hello world'
    }
  },
  es: {
    message: {
      hello: 'hola mundo'
    }
  }
}

export default new VueI18n({
  fallbackLocale: 'en',
  locale: 'en',
  messages
})

Then on src/main.js you need to import this file, and then pass a reference keyed as i18n when instantiating a Vue instance:

// ...
import i18n from './plugins/vue-i18n'
// ...

new Vue({
  router,
  store,
  vuetify,
  i18n,
  render: h => h(App)
}).$mount('#app')

Using translations now is as easy as doing:

<template lang="pug">
.headline {{ $t("message.hello") }}
</template>

Setup common filters

To setup vue2-filters, add a src/plugins/vue2-filters.js file:

import Vue from 'vue'
import Vue2Filters from 'vue2-filters'

Vue.use(Vue2Filters)

Then, import it into your src/main.js:

// ...
import './plugins/vue2-filters'
// ...

Setup form validation

To setup Vuelidate, add a src/plugins/vuelidate.js file:

import Vue from 'vue'
import Vuelidate from 'vuelidate'

Vue.use(Vuelidate)

Then, import it into your src/main.js:

// ...
import './plugins/vuelidate'
// ...

Show current app version and locale in the browser console

Finally, I like to see somewhere in the console the version set in package.json, along with the locale set for i18n, so I usually add this to my src/main.js:

// ...
import i18n from './plugins/vue-i18n'
import { version } from '../package.json'


/* eslint-disable no-console */
console.log(`App version: ${version}`)
console.log(`App locale: ${i18n.locale}`)
/* eslint-enable no-console */
// ...

Basic Vuetify templates

To see Vuetify in action with Pug, change your src/App.vue file to:

<template lang="pug">
v-app
  v-app-bar(app)
    v-toolbar-title.headline.text-uppercase
      span.mr-2 Material
      span.font-weight-light Design

    v-spacer

    v-btn(
      text
      href="https://github.com/vuetifyjs/vuetify/releases/latest"
      target="_blank"
    )
      span Latest Release
      v-icon(right) mdi-open-in-new

  router-view
</template>

And change src/views/Home.vue to:

<template lang="pug">
v-content
  v-container
    hello-world(msg="Welcome to Vuetify")
</template>


<script>
import HelloWorld from '@/components/HelloWorld'

export default {
  components: {
    HelloWorld
  }
}
</script>

Finally, let’s convert src/components/HelloWorld.vue to Pug:

<template lang="pug">
v-layout(text-center wrap)
  v-flex(xs12)
    v-img(
      :src="require('../assets/logo.svg')"
      class="my-3"
      contain
      height="200"
    )

  v-flex(mb-4)
    h1.mb-6
      .headline {{ $t("message.hello") }}
      .display-3 {{ msg }}

    p.subheading.font-weight-regular
      | For help and collaboration with other Vuetify developers,
      br
      | please join our online
      |
      a(href="https://community.vuetifyjs.com" target="_blank") Discord Community

  v-flex(mb-5 xs12)
    h2.headline.mb-3 What's next?
    v-layout(justify-center)
      a(
        v-for="(next, i) in whatsNext"
        :key="i"
        :href="next.href"
        class="subheading mx-3"
        target="_blank"
      ) {{ next.text }}

  v-flex(xs12 mb-5)
    h2.headline.mb-3 Important Links
    v-layout(justify-center)
      a(
        v-for="(link, i) in importantLinks"
        :key="i"
        :href="link.href"
        class="subheading mx-3"
        target="_blank"
      ) {{ link.text }}

  v-flex(xs12 mb-5)
    h2.headline.mb-3 Ecosystem
    v-layout(justify-center)
      a(
        v-for="(eco, i) in ecosystem"
        :key="i"
        :href="eco.href"
        class="subheading mx-3"
        target="_blank"
      ) {{ eco.text }}
</template>

<script>
export default {
  props: {
    msg: String
  },
  data: () => ({
    ecosystem: [
      {
        text: 'vuetify-loader',
        href: 'https://github.com/vuetifyjs/vuetify-loader'
      },
      {
        text: 'github',
        href: 'https://github.com/vuetifyjs/vuetify'
      },
      {
        text: 'awesome-vuetify',
        href: 'https://github.com/vuetifyjs/awesome-vuetify'
      }
    ],
    importantLinks: [
      {
        text: 'Documentation',
        href: 'https://vuetifyjs.com'
      },
      {
        text: 'Chat',
        href: 'https://community.vuetifyjs.com'
      },
      {
        text: 'Made with Vuetify',
        href: 'https://madewithvuejs.com/vuetify'
      },
      {
        text: 'Twitter',
        href: 'https://twitter.com/vuetifyjs'
      },
      {
        text: 'Articles',
        href: 'https://medium.com/vuetify'
      }
    ],
    whatsNext: [
      {
        text: 'Explore components',
        href: 'https://vuetifyjs.com/components/api-explorer'
      },
      {
        text: 'Select a layout',
        href: 'https://vuetifyjs.com/layout/pre-defined'
      },
      {
        text: 'Frequently Asked Questions',
        href: 'https://vuetifyjs.com/getting-started/frequently-asked-questions'
      }
    ]
  })
}
</script>

Just for completion, src/views/About.vue should look like this:

<template lang="pug">
v-content
  v-container
    h1 This is an about page
</template>

Basic routing

Your src/router.js should look like:

import Vue from 'vue'
import Router from 'vue-router'

import Home from './views/Home.vue'


Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (about.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
    }
  ]
})

Typography

The Material Design guidelines for typography, state that Roboto and Noto are the standard typefaces to use.

When you installed Vuetify through vue add vuetify it modified your public/index.html file and added these lines:

<link
  rel="stylesheet"
  href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"
>

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"
>

Have a look

Start your project with:

yarn serve

And visit: http://localhost:8080.

Running the tests

First, let’s make some fixes.
In tests/e2e/specs/test.js change this:

 describe('My First Test', () => {
   it('Visits the app root url', () => {
     cy.visit('/')
-    cy.contains('h1', 'Welcome to Your Vue.js App')
+    cy.contains('h1', 'Welcome to Vuetify')
   })
 })

Then, in tests/unit/example.spec.js:

-import { shallowMount } from '@vue/test-utils'
+import { createLocalVue, shallowMount } from '@vue/test-utils'
+import VueI18n from 'vue-i18n'
+import Vue2Filters from 'vue2-filters'
+import Vuelidate from 'vuelidate'
+import Vuetify from 'vuetify'
 import HelloWorld from '@/components/HelloWorld.vue'
 
+const localVue = createLocalVue()
+localVue.use(VueI18n)
+localVue.use(Vue2Filters)
+localVue.use(Vuelidate)
+localVue.use(Vuetify)
+
 describe('HelloWorld.vue', () => {
   it('renders props.msg when passed', () => {
     const msg = 'new message'
     const wrapper = shallowMount(HelloWorld, {
-      propsData: { msg }
+      propsData: { msg },
+      localVue,
+      i18n: new VueI18n(),
+      vuetify: new Vuetify()
     })
     expect(wrapper.text()).toMatch(msg)
   })

To run the unit tests:

yarn test:unit

To run the end-to-end tests:

yarn test:e2e

Build your app

To build your app just type:

yarn build

The output of that command will be in the dist directory.

If you want to quickly try out your production version locally then type:

cd dist/
python -m http.server

Now visit http://localhost:8000.

Deploying to a subdirectory

When building your app for production the compiler assumes that your app is going to be deployed to /.

If that is not the case and you are deploying to /some-other-dir then you need to create a new vue.config.js file in your app’s root directory with this content:

module.exports = {
  baseUrl: process.env.NODE_ENV === 'production'
    ? '/some-other-dir/'
    : '/'
}

You can find more info about vue.config.js here.

That’s it!
Have a good one. :)

Bonus section

Color theme tool

If you are looking for a Material Design color browser, a tool that’ll let you mix and match colors, then check this Vuetify color theme builder out!

Useful links


— lt

Feedback & comments

Get in touch on Twitter

Or by good ol' email at adriandcs@gmail.com