Dev

Laravel & Vue.js - Building a Todo list, Part 4: Setting up the frontend application with Webpack

Profil Picture

Guillaume Briday

5 minutes

This is one of the most important parts of the frontend: setting up the environment with Webpack.

Before starting

I didn't start from a blank project; there are official templates you can install to get a basic configuration for your project. To use them, you first need to install vue-cli:

$ npm install -g vue-cli

The available templates are hosted in the vuejs-templates organization. Personally, I wanted to start from the simplest setup to truly understand the fundamentals of Webpack.

Before this project, I used Webpack only with Laravel Mix, which is an excellent tool but doesn't, in my opinion, allow you to fully grasp how Webpack works if you've never used it before.

Webpack

On the frontend side, Webpack will handle everything. It will provide us with a server featuring Hot Module Replacement and manage our dependencies in both production and development environments. It is by far the most powerful tool available today, but it isn't easy to get started with initially.

I won't explain how Webpack works; for that, I invite you to read the Concepts page of the official documentation, which is very helpful. Instead, I'll detail the modifications and additions I made to the Webpack configuration to meet the project's needs and to delve deeper into certain points.

To install our template:

$ vue init webpack-simple todolist-frontend

Our entry points

Even though with Webpack you can manage CSS via JS, as Adam Wathan does in his example with Tailwind—I personally prefer to keep them separate. Importing a CSS file from an index.js still unsettles me. Feel free to change this if you prefer.

So, I'll have two entry points: one for JS and one for CSS, located in two distinct folders.

entry: {
  app: [
    './src/js/app.js',
    './src/styles/app.scss'
  ]
},

Our modules

module: {
  rules: [
    {
      test: /\.vue$/,
      loader: 'vue-loader',
      options: {
        extractCSS: true
      }
    },
    {
      test: /\.s[ac]ss$/,
      use: ExtractTextPlugin.extract({
        use: [
          {loader: 'css-loader', options: {importLoaders: 2}},
          'postcss-loader',
          'sass-loader'
        ],
        fallback: 'style-loader'
      })
    },
    {
      test: /\.js$/,
      loader: 'babel-loader',
      exclude: /node_modules/
    },
    {
      test: /\.(js|vue)$/,
      loader: 'eslint-loader',
      enforce: 'pre',
      include: [path.resolve(__dirname, './src')],
      options: {
        emitWarning: true
      }
    },
    {
      test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
      use: [{
        loader: 'file-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'fonts/'
        }
      }]
    }
  ]
},

A few important points to note.

We'll, of course, use vue-loader to create components in .vue files. This is essential for developing with Vue.js, in my opinion.

I always use Sass to write my CSS. I simply use the sass-loader with a test on .sass or .scss files.

I want the final CSS to be extracted into a separate app.css file, not embedded within a <style> tag as is the default with the style-loader.

To achieve this, we need to use the Extract Text Plugin. It allows us to extract the result into a separate file if possible; otherwise, it will use the default style-loader. Coupled with vue-loader, it enables us to extract the CSS written in .vue files directly into the main CSS file using the option extractCSS: true.

For .js files, we'll use Babel to take advantage of ES2015 syntax.

Tailwind CSS

To install Tailwind CSS with Webpack, we need to use PostCSS. I use the sass-loader after the postcss-loader because the order matters, as indicated here.

PostCSS also allows us to automatically autoprefix our CSS.

// postcss.config.js

var tailwindcss = require('tailwindcss')

module.exports = {
  plugins: [tailwindcss('./tailwind.config.js'), require('autoprefixer')],
}

Font Awesome

To install fonts, we'll need the file-loader. Indeed, we need to extract the fonts into files to load them via our browser.

In my case, I want to extract them into a /fonts folder.

I just need to modify the Font Awesome path variable in the SCSS and import the main file:

// src/styles/app.scss

$fa-font-path: '~font-awesome/fonts';
@import '~font-awesome/scss/font-awesome.scss';

ESLint

For .vue and .js files, we'll use ESLint to lint our code and maintain consistency in syntax, especially if other developers might work on the project. There are several configuration options; I use the JavaScript Standard Style. The configuration is located in the .eslintrc.js file.

If you use VS Code, there's a great extension that uses your current project's configuration and reports errors directly in your editor!

$ code --install-extension dbaeumer.vscode-eslint

Plugins

var inProduction = process.env.NODE_ENV === 'production'

// ...

plugins: [
  new ExtractTextPlugin({
    filename: 'css/[name].[contenthash].css',
    disable: !inProduction,
  }),
  new HtmlWebpackPlugin({
    template: 'index.html',
  }),
]

It's not necessary to extract the CSS into a file during development phases, which is why I disable ExtractTextPlugin for environments other than production. The plugin will then use the fallback we configured earlier.

The HTML Webpack plugin will generate an index.html file, automatically injecting, among other things, our JavaScript and CSS with the corresponding tags. This is very handy for generating the final project with the correct URLs containing a hash for versioning.

In a section dedicated solely to production, I added the PurifyCSS plugin.

It scans our .html and .vue files (we can configure others) to keep only the CSS used inside them. It also allows us to minify the CSS once filtered.

new PurifyCSSPlugin({
  // Give paths to parse for rules. These should be absolute!
  paths: glob.sync([
    path.join(__dirname, './src/js/components/**/*.vue'),
    path.join(__dirname, 'index.html')
  ]),
  minimize: true
}),

Docker and Docker-Compose

To use Node and NPM, I prefer to use Docker to facilitate deployment and ensure a consistent environment among all project developers.

The default Node image doesn't expose any ports and doesn't have a WORKDIR. We could change the WORKDIR via the docker-compose.yml file, but I prefer to modify it in the Dockerfile to make it easier to do without Docker Compose in the future if needed.

So I created a super simple Dockerfile to handle these two cases:

FROM node
LABEL maintainer="[email protected]"

WORKDIR /app

EXPOSE 8080

I don't add any command (CMD) at startup because the goal is to use Docker Compose only to launch the containers more easily.

Using Docker Compose just for this is completely overkill, we agree. Using Docker directly would work perfectly, but I prefer it this way. It allows me to always have the same command to launch even if the configuration changes in the future.

NPM is installed by default in this image, so we don't need to install it ourselves.

The docker-compose.yml is also very simple:

version: '3'

services:
  node:
    build: ./provisioning
    image: todolist-frontend
    ports:
      - 8080:8080
    volumes:
      - .:/app:cached

In our case, running docker-compose up wouldn't have any purpose; I don't want there to be a default command for now.

Instead, we can use it to run commands:

# Install our dependencies
$ docker-compose run node npm install

# Launch the Webpack server with Hot Module Replacement
$ docker-compose run --service-ports node npm run hot

# Compile the application in development mode
$ docker-compose run --rm node npm run dev

# Compile the application in development mode with watch mode
$ docker-compose run --rm node npm run watch

# Compile the application in production mode (minification, etc.)
$ docker-compose run --rm node npm run production

# Run ESLint
$ docker-compose run --rm node npm run lint

The --service-ports flag activates port mapping between the containers and the host when executing a command. Indeed, outside of docker-compose up, it's disabled by default.

Travis CI

To ensure everything works, I've set up Travis CI to test if the project compiles successfully and if ESLint doesn't report any errors. In the near future, I might add unit and functional tests for the application.

language: node_js

node_js:
  - '7'
  - '8'
  - '9'

cache:
  directories:
    - 'node_modules'

script:
  - npm run lint
  - npm run production

notifications:
  email: false

Conclusion

The configuration is likely to evolve further during development, of course.

Everything is available on the GitHub repository: guillaumebriday/todolist-frontend-vuejs.

Simplify your time tracking with Timecop

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

Timecop projects