Upgrad­ing to web­pack 4

Publisher: TJ Fogarty

Modified: 2018-05-12

It’s no big secret that I love Lar­avel Mix. It’s handy enough to throw into most projects, and I had been using it with Word­Press sites for a long while as it made onboard­ing new devs a lot eas­i­er. Babel and Sass? Done.

mix.js('src/app.js', 'dist/').sass('src/app.scss', 'dist/');

It abstracts away all the web­pack wiz­ardry so you can spend less time set­ting up. It’s an amaz­ing tool and I have no prob­lem rec­om­mend­ing it to peo­ple. You can inject your own con­fig­u­ra­tion if you need to extend it as well so you’re not locked out of anything.

On the flip­side I’m a div­il for tin­ker­ing, so a one-lin­er is not con­ducive to my mis­chief. After see­ing the vic­to­ries achieved by the web­pack team on ver­sion 4 I was eager to explore it, plus Lar­avel Mix is on web­pack 3 (soon to be ver­sion 4 by the looks of it).

Here’s the list of things I need­ed to do:

  • Tran­spile my JS
  • Han­dle styles writ­ten in Less
  • Use PostC­SS for Tailwind
  • Out­put styles to a sep­a­rate file
  • Gen­er­ate a ser­vice worker
  • Mini­fy assets for production

Set­up #

The start of my con­fig loads the pack­ages I need, and I cap­ture the mode we’re in (devel­op­ment or pro­duc­tion). I’ll then use this mode lat­er to update the con­fig with any pro­duc­tion-spe­cif­ic actions. For con­text, webpack.config.js sits at the root of my project, with source files and final assets liv­ing in a web folder.

const path = require('path')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const workboxPlugin = require('workbox-webpack-plugin')

let env = process.env.NODE_ENV
let isDev = env === 'development'

Gen­er­al Con­fig­u­ra­tion #

JavaScript #

This part took a bit of tweak­ing to get my paths right for code split­ting and cor­rect­ly load­ing chunks from the cor­rect url, but in the end I set­tled on:

const WEBPACK_CONFIG = {
  mode: env, // development or production
  entry: {
    main: './web/src/js/main.js'
  },
  output: {
    publicPath: '/',
    path: path.resolve(__dirname, 'web'),
    filename: 'assets/js/[name].js',
    chunkFilename: 'assets/js/chunks/[name].js'
  }
}

I need­ed to set the publicPath to / so the chunks would load cor­rect­ly, but beyond that there’s enough there to han­dle every­thing else.

Styles #

Styles took a bit of play­ing around with, turns out I’m a fool and didn’t read the instruc­tions on where to place the less-loader plu­g­in. I got there in the end though, so the updat­ed con­fig looks like this:

const WEBPACK_CONFIG = {
  mode: env,
  entry: {
    main: './web/src/js/main.js',
    styles: './web/src/less/app.less'
  },
  output: {
    publicPath: '/',
    path: path.resolve(__dirname, 'web'),
    filename: 'assets/js/[name].js',
    chunkFilename: 'assets/js/chunks/[name].js'
  },
  module: {
    rules: [
      {
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader',
          'less-loader'
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'assets/css/app.css'
    })
  ]
}

I updat­ed the entry object for my styles, and added rules for deal­ing with less files. Final­ly I added the MiniCssExtractPlugin to point the out­put into my assets folder.

Tail­wind

To get Tail­wind work­ing I added a postcss.config.js file to my project containing:

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

The tailwind.js ref­er­ence being my con­fig­u­ra­tion file.

Mis­cel­la­neous #

Anoth­er thing I want­ed to do was clear out the assets fold­er on each run in case I added some extra files, like unnamed chunks so I didn’t have a fold­er full of 1..n.js files.

For that I append­ed the fol­low­ing to the plu­g­ins array:

new CleanWebpackPlugin(['web/assets'])

Pro­duc­tion-only #

Mini­fy #

I only want­ed to mini­fy in pro­duc­tion, so with that I added a con­di­tion to append to the web­pack if it wasn’t in devel­op­ment mode:

// `isDev` is set up earlier to check if process.env.NODE_ENV === 'development'
if (!isDev) {
  WEBPACK_CONFIG.optimization = {
    minimizer: [
      new UglifyJsPlugin({
        cache: true,
        parallel: true,
        sourceMap: true // set to true if you want JS source maps
      }),
      new OptimizeCSSAssetsPlugin({})
    ]
  }
}

Ser­vice Work­er #

I’ll be hon­est, this is some­thing that I still need to work on if I want to go full on PWA. I’m using Work­box to help with this.

So still inside the if (!isDev) block I added:

WEBPACK_CONFIG.plugins.push(
  new workboxPlugin.InjectManifest({
    swSrc: './web/src/js/sw.js',
    swDest: 'sw.js'
  })
)

This com­piles the ser­vice work­er from this file:

workbox.skipWaiting()
workbox.clientsClaim()

workbox.routing.registerRoute(
  /\.(?:png|gif|jpg|jpeg|svg)$/,
  workbox.strategies.cacheFirst({
    cacheName: 'images',
    plugins: [
      new workbox.expiration.Plugin({
        maxEntries: 60,
        maxAgeSeconds: 30 * 24 * 60 * 60 // 30 Days
      })
    ]
  })
)

workbox.routing.registerRoute(
  /\.(?:js|css)$/,
  workbox.strategies.staleWhileRevalidate({
    cacheName: 'static-resources'
  })
)

workbox.precaching.precacheAndRoute(self.__precacheManifest)

This caches images, JavaScript, and CSS. I real­ly need to read up more on it.

Wha’ Hap­pen? #

In the end, run­ning npm run dev went from tak­ing 6 sec­onds to 2.5 sec­onds, and npm run production dropped from 14 sec­onds to 4. Absolute­ly fan­tas­tic gains there, and a tes­ta­ment to the fine work those smart folks do work­ing on web­pack. This was more of a learn­ing expe­ri­ence to get clos­er to the met­al as it were, and I’ll con­tin­ue tweak­ing and break­ing things because it’s my site. Bet­ter I do it here than at work. I’ll only have myself to answer to, and I’m my own harsh­est critic.

You find the final file in my repo for this site.