Use webpack with Hugo to manage assets for your static website
Tags: hugo, webpack, static
Published: 2019-09-02 16:15:11


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](https://gohugo.io/hugo-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

```bash
├── 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`](https://github.com/danethurber/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`](https://github.com/webpack/webpack-dev-server) is used to host the bundled files during development from memory. 


```js
// ./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. 

```html
<!-- ./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.

```json
  "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](https://github.com/kazushisan/gadgetlunatic) 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 🙂