loading...

Use webpack with Hugo to manage assets for your static website

kazushikonosu profile image Kazushi Konosu ・4 min read

I recently updated my personal website, gadgetlunatic.com using Hugo. While Hugo is an awesome tool to build HTML files from markdown files, the situation for other files needed is different.

Hugo does provides a way to manage Javascript and CSS files using Pipes but the features are limited. Compared to tools used in today's frontend development, Hugo's ecosystem for handling assets lacks features (and third-party add-ons).

Webpack has become extremely popular among frontend developers to manage asset files for your website. Not only does it bundle Javascript files, it is also capable of managing images and stylesheets. It's now a must-have for projects using frontend libraries/frameworks like React or Vue.

Combining the two tools was a natural decision for me, however there weren't examples of using the two tools together. I'll be highlighting the process of combining Hugo and Webpack to create a static website with modern asset management.

Note

This articles assumes that you create your own theme files under layouts/ and static/; We are not going to use public themes that go under themes/

Also, I will not go into the details of how to create a static website with Hugo.

The goal

The goal of this guide is to use webpack along with Hugo to bundle and build assets used in a static website. Here are some things we will do in detail.

  • Bundle Javascript / Stylesheet files.
  • Process images and changed the filename based on the hash.
  • Use hash file names for bundled files for cache optimization.
  • Serve JS/CSS files from memory during development to reduce disk writes.

Directory Structure

├── README.md
├── archetypes
├── config.toml
├── content # where the article markdown files will be stored
├── data # ignored by git
│   └── manifest.json
├── layouts
├── node_modules  # ignored by git
├── package.json
├── public # Where the final files will be built to; ignored by git
├── resources
├── src # The files used for the website
│   ├── assets
│   ├── js
│   └── scss
├── static # ignored by git
├── webpack.config.js 
└── yarn.lock

We will manage Javascript and SCSS files under src/. The files here will be processed by webpack and be built to static/. Once processed, webpack will emit data/manifest.json. This file will be referenced by Hugo on build.

The final files used to publish will be written under public/

1. Configure

Webpack

First, let's setup webpack. This sample setup will bundle Javascript and SCSS files under src/js and src/scss respectively. It will also process images under src/assets.

We will use webpack-manifest-plugin which will produce data/mainfest.json on build; This file will include the files names of bundled files that use hash.

Also, webpack-dev-server is used to host the bundled files during development from memory.

// ./webpack.config.js

const path = require('path')
const autoprefixer = require('autoprefixer')
const cssnano = require('cssnano')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const ManifestPlugin = require('webpack-manifest-plugin')

module.exports = env => {
    const isProduction = Boolean(env && env.production)
    console.log('Production: ', isProduction)

    return {
        mode: isProduction ? 'production' : 'development',
        devtool: isProduction ? 'source-map' : 'inline-source-map',
        entry: path.resolve(__dirname, 'src/js/index.js'),
        output: {
            path: path.resolve(__dirname, 'static/'),
            filename: `js/${isProduction ? '[hash].' : ''}bundle.js`,
            publicPath: '/'
        },
        plugins: [
            new MiniCssExtractPlugin({
                filename: `css/${isProduction ? '[hash].' : ''}bundle.css`
            }),
            new ManifestPlugin({
                fileName: '../data/manifest.json'
            })
        ],
        devServer: {
            port: 1314
        },
        module: {
            rules: [
                {
                    test: /\.js$/,
                    exclude: /node_modules/,
                    loader: 'babel-loader'
                },
                {
                    test: /\.scss$/,
                    use: [
                        {
                            loader: MiniCssExtractPlugin.loader
                        },
                        {
                            loader: 'css-loader',
                            options: {
                                importLoaders: 2,
                                sourceMap: !isProduction
                            }
                        },
                        {
                            loader: 'postcss-loader',
                            options: {
                                sourceMap: !isProduction,
                                plugins: [
                                    cssnano({ preset: 'default' }),
                                    autoprefixer({ grid: true })
                                ]
                            }
                        },
                        {
                            loader: 'sass-loader',
                            options: {
                                sourceMap: !isProduction
                            }
                        }
                    ]
                },
                {
                    test: /\.(png|jpg|gif|svg)$/,
                    loader: 'file-loader',
                    options: {
                        outputPath: 'assets/'
                    }
                }
            ]
        }
    }
}

Hugo

Here's a portion of a sample baseof.html layout file. During development, Hugo will load bundled assets from webpack-dev-server, running of localhost:1314.

When we build static files to publish, Hugo will reference data/manifest.json emitted from Webpack to find out the name of the bundled file.

<!-- ./layouts/baseof.html -->

{{ .Scratch.Set "css" "http://localhost:1314/css/bundle.css" }}
{{ .Scratch.Set "js" "http://localhost:1314/js/bundle.js" }}
{{ if not .Site.IsServer }}
{{ .Scratch.Set "css" (index .Site.Data.manifest "main.css") }}
{{ .Scratch.Set "js" (index .Site.Data.manifest "main.js") }}
{{ end }}
<!DOCTYPE html>
<html>
    <head>
        <!-- Omitted -->
        {{ $css := .Scratch.Get "css" }}
        <link rel="stylesheet" href="{{ $css }}"></link>
        {{ template "_internal/opengraph.html" . }}
    </head>
    <body>
    <!-- Omitted -->
        {{ $js := .Scratch.Get "js" }}
    <script src="{{ $js }}"></script>
    </body>
</html>

NPM scirpts

Here are the npm scripts that's used for this setup.

  "scripts": {
    "start": "webpack-dev-server & hugo server",
    "build": "webpack --env.production && hugo --minify"
  }

2. Develop

run yarn start, then both webpack-dev-server and hugo server will start. hugo server on localhost:1313 will use the bundled files hosted from localhost:1314.

3. Build

run yarn build. First webpack will bundle and process the assets under src/. Once finished, it will also produce data/mainfest.json. After that, hugo will build the files to public/. Hugo will use the mainfest.json to find out the names of the bundle files.

That's it!

I hope this was helpful to those looking to use webpack along with Hugo.
Also checkout my Github repository which contains a running version of this setup.

Thanks for reading my first post! Feel free to ask if there are any questions, I will be glad to answer 🙂

Posted on by:

kazushikonosu profile

Kazushi Konosu

@kazushikonosu

Student at College of Policy and Planning Sciences, University of Tsukuba. Interested in React, JS/TS, etc.

Discussion

pic
Editor guide