Cool Things with Webpack

Jake Tripp
5 min readJun 4, 2018

--

Pic by Andrew Neel

It’s a really cool point in your developer journey when you start to question the way to do things.

Update (August 8, 2020): I mention html-webpack-exclude-assets-plugin in the article. A helpful reader pointed out that this package doesn’t play nice with modern versions of Webpack (~4.3). He recommended html-webpack-skip-assets-plugin as a replacement.

For my job, one requirement is that we’re trying to make the site almost entirely 508 compliant (I’ll leave that for a separate post).

I learned and set up Webpack 2 for our first project, and now we’re using it for our second project, too.

We want to leverage EJS to stay DRY (footer, nav, etc.)

I’m going to briefly walk through some of the parts of the code that are really significant to me or are things I’m proud of.

Building the Pages with the Correct Assets

I use these to build the HTML files ahead of time using the EJS files as templates with the correct link and script tags included.

Configuring the EJS loader:

module: {
loaders: [
{
test: /\.(ejs)$/,
loader: "ejs-compiled-loader"
}
]
}

Some Good Default HtmlWebpackPlugin Options

new HtmlWebpackPlugin({
hash: true,
minify: {
removeComments: true,
collapseWhitespace: true
},
chunks: ["vendorCSS", "infoPageCSS", "common", "vendor"],
filename: "about.html",
template: "!!ejs-compiled-loader!./views/about.ejs"
})
  • hash: adds a hash to the filename for cache-busting
  • minify: takes an object of html-minifier’s options. I wanted to remove comments and collapse whitespace. More on that here.
  • chunks: the chunks created by Webpack that should get injected to the final HTML file
  • filename: the HTML filename to be created
  • template: the syntax to use ejs-compiled-loader to compile the EJS file into an HTML file, to then use that as the template for HtmlWebpackPlugin. Yep, you read that right. A template for a template!

Sorting the Assets for your Pages

When I was setting up the Webpack config, I kept running into the problem that my custom CSS would be injected before the vendor CSS (AKA Bootstrap’s CSS), which made the pages look awful.

Enter chunksSortMode:

function chunksSortMode(chunk1, chunk2) {
var orders = ["vendorCSS", "infoPageCSS", "common", "vendor", "g-page", "index"];
var order1 = orders.indexOf(chunk1.names[0]);
var order2 = orders.indexOf(chunk2.names[0]);
return order1 - order2
}

chunksSortMode is a function I got from Anthony Ettinger and adapted to my needs. All you have to do is set orders equal to an array of your entry properties in the order you want them to be injected into your HTML files. Then just set chunksSortMode on your HtmlWebpackPlugin options equal to the function.

This is a little confusing to wrap your head around. Probably none of your HTML files will include ALL of the chunks. That’s okay. I always want the vendorCSS chunk to come before other CSS. I always want to have the common JS chunk, then the vendor JS chunk, then the custom JS chunks.

After you have that straightened out, just reference the function like this:

new HtmlWebpackPlugin({
chunksSortMode: chunksSortMode
}

Handling Relative Path Issues:

Webpack is hard to configure your first time. Hopefully, one day it will become easier for first-timers.

For my work project, if a user went to a URL that didn’t exist, I needed to show a 404 page. However, when I went to a nested URL, (i.e., /some/nested/url), it messed up the paths for the images and CSS and JS files.

publicPath to the rescue!

output: {path: path.join(__dirname, "dist"),
publicPath: "/",
filename: "[name].bundle.js"
},

This fixed my problem. No matter how nested the URL, the paths would be correct. More on that here.

SCSS:

Getting SCSS configured the first time was quite hard for me the first time until I found this excellent YouTube channel!

Workflow:

  1. Write SCSS partials for individual parts (footer, searchBar, nav, etc.)
  2. Create SCSS files for each page, importing the necessary SCSS partials
  3. Add said files to webpack.config.js in the entry section.
  4. Profit

The Problem

The problem is that while it does generate the CSS files correctly, it also makes dumb JS files that serve zero purpose. That’s where html-webpack-exclude-assets-plugin comes in. Refer to the documentation (easy to set up and quality docs)

new HtmlWebpackPlugin({
excludeAssets: [/vendorCSS.*.js/, /resultsCSS.*.js/, /infoPageCSS.*.js/]
}

I add an excludeAssets property to the options for HtmlWebpackPlugin set to an array of regular expressions that match the files you want to exclude. Easy win.

My absolute favorite part!

The project that the provided webpack.config.file is based on has like 25 HTML files to generate, and there was a lot of repetition on the HtmlWebpackPlugin options.

I had never really tried doing something entirely from my brain in the Webpack config file — it was a little intimidating.

Or a lot intimidating.

I had the idea that since

new HtmlWebpackPlugin(optionsObject)

goes in the plugins array, I could create a function that returned that. Initially, I thought I would have to run the function inside the array, but you actually don’t. I think this is because Webpack runs all of the things in the plugins array — so you don’t have to.

The plugins array has function definitions, not function calls (because it calls them when you run Webpack).

😎 My awesome function 😎

function customHtmlWebpackPlugin(specificOptions) {let defaults = {// hashes the file to prevent bad caching
hash: true,
// custom function to inject files in correct order
chunksSortMode: chunksSortMode,
// exclude worthless JS files
excludeAssets: [/vendorCSS.*.js/, /resultsCSS.*.js/, /infoPageCSS.*.js/],
minify: {
removeComments: true,
collapseWhitespace: true
}
};// cool ES6 spread operator
// add the default options with custom object passed as parameter
return new HtmlWebpackPlugin({ ...defaults, ...specificOptions });
}

THIS:

new HtmlWebpackPlugin({hash: true,
chunksSortMode: chunksSortMode,
excludeAssets: [/vendorCSS.*.js/, /resultsCSS.*.js/, /infoPageCSS.*.js/],
minify: {
removeComments: true,
collapseWhitespace: true
},
filename: "about.html",
template: "!!ejs-compiled-loader!./views/about.ejs",
chunks: ["vendorCSS", "infoPageCSS", "common", "vendor"]
}),

BECAME THIS:

customHtmlWebpackPlugin({filename: "about.html",
template: "!!ejs-compiled-loader!./views/about.ejs",
chunks: ["vendorCSS", "infoPageCSS", "common", "vendor"]
}),

Improvements:

  1. Drastically reduced number of lines of code (over 100 lines in my case)
  2. Easier to conceptualize — you only see what makes the HTML page different
  3. More DRY — less repetition means if I decided I wanted to change one of the default properties, I’d only have to change it in one place.

It’s a really cool point in your developer journey when you start to question the way to do things.

Thanks for reading. If you found value in this, I’d really appreciate it if you recommend this post (by clicking the clap button) so other people can see it!

--

--