Dev

Laravel & Vue.js - Building a Todo list, Part 6: Some Refactoring

Profil Picture

Guillaume Briday

3 minutes

I've taken the time to refactor some parts of the code since the last article. No major changes, but updating dependencies forced me to make several modifications that I wanted to present before continuing the series.

Separating the Webpack configuration

As the application evolves, the Webpack configuration becomes increasingly specific, depending on the environment we're in.

To simplify its readability, we can separate it into several files specific to each environment.

In our case, there will be three: one common, one for development, and one for production.

$ tree build

build
├── webpack.common.js
├── webpack.dev.js
└── webpack.prod.js

We will need the webpack-merge package to merge our configurations:

$ npm install webpack-merge

It's a function that takes several parameters—the configurations you want to merge. You can pass as many as needed.

For example, for the production configuration, I can now do:

// ./build/webpack.prod.js

let merge = require('webpack-merge')
let common = require('./webpack.common.js')

module.exports = merge(common, {
  // Production-specific configuration
})

Here, we're going to add the minification of HTML, CSS, and JavaScript.

Thus, we no longer need to use conditions and override an attribute of the configuration as we did before:

- var inProduction = (process.env.NODE_ENV === 'production')

- if (inProduction) {
-   module.exports.devtool = false
-   // http://vue-loader.vuejs.org/en/workflow/production.html
-   module.exports.plugins = (module.exports.plugins || []).concat([
-     // Plugin configuration
-   ])
- }

The inProduction variable is no longer needed since we run the Webpack build directly with the appropriate configuration file depending on the desired environment:

"scripts": {
-  "development": "cross-env NODE_ENV=development webpack --progress --hide-modules",
+  "development": "cross-env NODE_ENV=development webpack --config build/webpack.dev.js --mode development --progress --hide-modules",
-  "production": "cross-env NODE_ENV=production webpack --no-progress --hide-modules",
+  "production": "cross-env NODE_ENV=production webpack --config build/webpack.prod.js --mode production --no-progress --hide-modules",
}

They will then automatically use the common configuration file.

I also took the opportunity to migrate from PurifyCSS to PurgeCSS, as it offered much better compatibility with Tailwind, especially for classes containing special characters like :, which are omnipresent in Tailwind.

Changes in Vue Loader and Dotenv configuration

There was a major change in handling .vue files between versions 14 and 15 of vue-loader.

The documentation on this subject is here. It's a notable change worth mentioning.

You'll also notice the presence of a new .env file that allows me, with dotenv-webpack, to manage my Node environment variables easily without having to share my sensitive information.

Font Awesome 5

I think I'll write an entire article on using the new Vue.js component that Font Awesome provides in its version 5.

Now I can use icons in the form:

<fa icon="spinner" class="mr-1" spin />

Axios interceptors

A JWT token has a limited validity period. Therefore, we need to manage its expiration and refresh directly in the browser. For that, I use Axios interceptors.

They allow me to intercept all requests and responses that pass through the application. Since the application is stateless, I need to control the state of the token with each request and response to know its validity.

It's in the App.vue file, which is the entry point of my application, that I'm going to add this management because it ensures that the interceptors will be properly set up by Axios.

// src/js/components/App.vue

import axios from 'axios'
import moment from 'moment'

export default {
  created() {
    axios.interceptors.request.use((config) => {
      let expiresAt = moment(window.localStorage.getItem('expiresAt'))

      if (expiresAt.isValid() && moment().isBefore(expiresAt)) {
        let diff = moment.duration(expiresAt.diff(moment()))

        if (diff.asHours() < 12 && config.url !== 'auth/refresh') {
          axios.post('auth/refresh').then(({ data }) => {
            this.$store.commit('LOGIN', data)
          })
        }
      }

      return config
    })

    axios.interceptors.response.use(
      (response) => response,
      (error) => {
        if (error.response && error.response.status === 401) {
          this.$store.dispatch('logout')
        }

        return Promise.reject(error)
      }
    )
  },
}

During the initial login, the server returns the token's expiration date, which I save in localStorage. From there, if the token expires in less than 12 hours, I refresh it using the Vuex store. It will then be available again for 24 hours, and this process is completely transparent to the user without even logging them out of the application.

Otherwise, if the server returns a 401 Unauthorized status, it means the client's token has expired and I cannot renew it automatically. In that case, we need to simulate a logout to remove the old information from localStorage and redirect to the authentication page.

ESLint Vue plugin

I took the opportunity to add the official Vue plugin for ESLint. The configuration is very simple to change, and everything is in this commit: 5f81780.

Conclusion

This is a transition article before talking more in detail about task management with Vuex and deployment.

But I think with so many changes, it was important to do a recap on the modifications to start from a common base in the rest of the series.

We'll talk about task management in the next article!

Thank you!

Simplify your time tracking with Timecop

Timecop is a time tracking app that brings simplicity in your day to day life.

Timecop projects