In the time I have been 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 are you talking about? —you say

Glad you asked, well, for starters:

Package manager: Yarn — Fast, reliable, and secure dependency management.

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

UI CSS framework: TailwindCSS — A utility-first CSS framework for rapidly building custom designs.

TailwindCSS — A utility-first CSS framework for rapidly building custom designs. UI component framework: Vuetify — Material Design component framework.

Vuetify — Material Design component framework. Material icons library: Material Design Icons — 4000+ Material Design Icons from the Community.

Material Design Icons — 4000+ Material Design Icons from the Community. 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.

Axios — Promise based HTTP client for the browser and Node.js. Utility library: Lodash — A modern JavaScript utility library.

A good alternative could be Ramda.

Lodash — A modern JavaScript utility library. A good alternative could be Ramda. State management pattern + library: Vuex — Centralized state management for Vue.js.

Vuex — Centralized state management for Vue.js. Date library: date-fns — Modern JavaScript date utility library.

Alternatives in this space: Moment.js, Luxon, Day.js.

Take a look at this: you don’t need momentjs.

§ Template and preprocessor languages

HTML template engine: Pug — A robust, elegant, feature rich template engine for Node.js.

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.

§ Vue plugins

Common filters: vue2-filters — A collection of standard filters for Vue.js.

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

vue-i18n — Internationalization plugin for Vue.js. Form validation: Vuelidate — Simple, lightweight model-based validation for Vue.js.

Last, but definitely not least:

§ Set up a new Vue.js app

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.

Install Node.js, Yarn and vue-cli

§ Generate a new project

vue create my-vuejs-project

My usual preset looks like this:

Vue CLI v4.3.1 ? Please pick a preset: Manually select features ? Check the features needed for your project: Babel, PWA, Router, Vuex, CSS Pre-processors, Linter / Formatter, 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: ESLint + Standard config ? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection): Lint on save ? Pick a unit testing solution: Jest ? Pick a E2E testing solution: Cypress (Chrome only) ? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files ? Save this as a preset for future projects? No ? Pick the package manager to use when installing dependencies: Yarn

§ Install app dependencies

I like having the power of Sass at my disposal when writing CSS rules.

I also like to write my templates using Pug for simplified —easy to read— views.

Let’s add Pug to our devDependencies section in our package.json file:

yarn add --dev pug pug-plain-loader

Here are some other project dependencies that I always end up installing at one point or another when working on a Vue.js project:

yarn add axios d3 date-fns lodash vue-i18n vue2-filters vuelidate

§ Initial configuration

Adjust ESLint rules

I like using stricter ESLint rules for all my Vue.js code:

@@ -4,7 +4,7 @@ module.exports = { node: true }, extends: [ - 'plugin:vue/essential', + 'plugin:vue/recommended', '@vue/standard' ], parserOptions: { @@ -12,7 +12,8 @@ module.exports = { }, rules: { 'no-console': process.env.NODE_ENV - 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' + 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', + 'no-multiple-empty-lines': [2, { max: 2 }] }, overrides: [ {

The plugin:vue/recommended enforces the rules found in the Priority C Rules: Recommended section of the Official Vue.js style guide.

The no-multiple-empty-lines change allows me to leave two consecutive blank lines between some elements inside my .js and .vue files.

For example, for .js files, I leave two spaces between the importing section and the code that follows.

For .vue files, I leave two spaces between <template> , <script> and <style> sections.

Lint the project

Take this opportunity to run the linter and fix anything that pops up:

yarn lint

If, when trying to add the latest changes to Git you’re greeted by: 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.

If you run the tests right now, they all should be OK:

yarn test :unit yarn test :e2e

But as you saw, the last command opens up a Chrome browser window and expects you to drive the testing process.

That’s why we will create a new script to run e2e tests in headless mode.

Headless e2e tests

This could prove useful for CI / CD pipelines:

"build": "vue-cli-service build", "test:unit": "vue-cli-service test:unit", "test:e2e": "vue-cli-service test:e2e", + "test:e2e:headless": "vue-cli-service test:e2e --headless", "lint": "vue-cli-service lint" }, "dependencies": {

Try it, execute the following command:

yarn test :e2e:headless

§ Set up plugins

Remember the dependencies we added above?

Well, to be used they need to be imported somehow into the app.

The best way to do it is to have a separate file for each one, then import them on src/main.js .

Setup vue2-filters

Add a src/plugins/vue2-filters.js file:

import Vue from 'vue' import Vue2Filters from 'vue2-filters' Vue.use(Vue2Filters)

Setup vuelidate

Add a src/plugins/vuelidate.js file:

import Vue from 'vue' import Vuelidate from 'vuelidate' Vue.use(Vuelidate)

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 : 'es' , messages })

Enable the plugins

Finally, import the plugins on src/main.js .

Note that in order to be able to use translations, we need to pass the i18n plugin as a reference when instantiating the Vue app:

@@ -4,10 +4,16 @@ import './registerServiceWorker' import router from './router' import store from './store' +import i18n from './plugins/vue-i18n' +import './plugins/vue2-filters' +import './plugins/vuelidate' + + Vue.config.productionTip = false new Vue({ router, store, + i18n, render: h => h(App) }).$mount('#app')

To display translated text in the current locale just use the $t function in a view like this:

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

The code above, along with the current configuration, would display:

< h1 > hola mundo </ h1 >

§ Expose useful information in the console

It’s good practice to update your version in package.json when you are working on your app.

If you do, you’ll always be able to tell what version is deployed on that server just by visiting the browser console —believe me, it helps, a lot.

You could also show any other piece of information that you deem important.

I usually add this to any brand new src/main.js :

@@ -7,8 +7,14 @@ import store from './store' import i18n from './plugins/vue-i18n' import './plugins/vue2-filters' import './plugins/vuelidate' +import { version } from '../package.json' +/* eslint-disable no-console */ +console.log(`App v${version}`) +console.log(`locale:${i18n.locale} | fallbackLocale:${i18n.fallbackLocale}`) +/* eslint-enable no-console */ + Vue.config.productionTip = false new Vue({

Which will output this to the console:

App v0.1.0 locale:es | fallbackLocale:en

§ Build for production

To build your app just type:

yarn build

You’ll see something along the lines of:

File Size Gzipped dist/js/chunk-vendors.fb283e83.js 174.07 KiB 57.57 KiB dist/js/app.de4c34bc.js 7.99 KiB 2.81 KiB dist/service-worker.js 1.05 KiB 0.61 KiB dist/precache-manifest.e3a02d9da053574 0.75 KiB 0.35 KiB fe196c018482e818a.js dist/js/about.95da692c.js 0.44 KiB 0.31 KiB dist/css/app.2462baa1.css 0.42 KiB 0.26 KiB Images and other types of assets omitted.

These and other files will be put inside the dist directory.

You can try out your production build locally like this:

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 / —a root directory.

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 defining a publicPath attribute like this:

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

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

§ Import Lodash the right way

If you want to avoid importing the whole library into your code to decrease your app size, then you should import only the functions you use.

For example, if you’ll be using only the pick and cloneDeep functions in a component, instead of this:

import _ from 'lodash' const picked = _.pick(obj, [ 'attr1' , 'attr2' , 'attr2' ]) const cloned = _.cloneDeep(someOtherObj)

You should do this:

import cloneDeep from 'lodash/cloneDeep' import pick from 'lodash/pick' const picked = pick(obj, [ 'attr1' , 'attr2' , 'attr2' ]) const cloned = cloneDeep(someOtherObj)

Aye, it’s a bit tedious, but if you are trying to save some KBs, then it’s the right thing to do.

§ About UI frameworks

If you want a batteries included type of framework, then Vuetify is a solid choice.

Learn how to add Vuetify to Vue.js.

If you are after freedom, and want to create your own design system in a very pragmatic way using utility classes as building blocks, then Tailwind CSS is for you.

Learn How to add Tailwind CSS to Vue.js.

§ A note on Pug and the new slot syntax

If you are using Jest, in conjunction with Pug and the new slot syntax:

v-menu template(#activator="{ on }") v-btn(color="primary" v-on="on") //- ...

template(#activator="{ on }") is equivalent to:

template(v-slot:activator="{ on }") but in shorthand syntax.

You might get an error that looks like this:

ERROR: Unexpected character '#' (...) STACK: SyntaxError: Unexpected character '#' (...)

You can read more about it here.

To fix it, just add this to jest.config.js:

module .exports = { globals : { 'vue-jest' : { pug : { doctype : 'html' } } } }

That’s it!

Have a good one. :)