Implementing Webpack into an Existing Site or Theme

There seems to be no shortage of good Webpack tutorials out there but all the ones that I have come across have been for scaffolding entirely new sites. That’s great to learn Webpack from the ground up but not so useful for a working professional trying to both learn and implement Webpack into an existing site or purchased theme for a client.

As an avid user of Envato Elements, I frequently send base themes to my clients to review and build off of. The problem is that most themes are not packaged well for active development and woefully not ready for a production deployment. They tend to take the kitchen sink approach and include so many libraries, images, and scripts that you are often looking at 10MB+, when finished and trimmed down, the site might be 2-3MB total. Now this is of course expected as theme builders want to give you as many pre-built options as they can but cutting the fat takes a lot of time and can be tricky especially when dealing with dependencies from multiple libraries/files.

So what is a developer to do? I’ve been using Webpack for a while now (and Gulp before that…and Grunt before that…) but I never actually tried to retrofit it into a purchased theme until just recently. It wasn’t all that hard but being more of a backend developer and never implementing Webpack from ground zero before, it was a little awkward. So I figured I’d write about the steps I took getting a purchased Envato HTML theme Webpackified.

Just the basics, ma’am

I started with the 69Studio Creative Agency HTML theme. After unzipping, the directory structure looked like most other purchased themes:

The first thing I did was to extract all theme files into a “theme” directory inside your main project’s directory so that I would have the originals when needed.

From inside your terminal of choice, let’s create our NPM packages and install everything we’re going to need with the following commands:

cd /your/project/directory #Navigate into your project directory
npm init #Initialize a new NPM project/module
#... answer the questions...
npm install --save-dev @babel/core @babel/preset-env babel-loader clean-webpack-plugin css-loader file-loader html-loader html-webpack-plugin image-webpack-loader mini-css-extract-plugin node-sass resolve-url-loader sass-loader webpack webpack-cli webpack-dev-server webpack-md5-hash #Installs all the necessary development packages

Now we should have something that looks like this:

Next, we’re going to create our “src” directory which will contain all of our actively developed files. Let’s start with creating the directory and copying over the “index.html” and “assets” directory into it. This will allow us to start with the index page and have access to all of the images, css, fonts, etc:

mkdir src #Create the src directory
cp theme/index.html ./src/ #Copy the index file into src
cp -R theme/assets ./src/ # Copy all assets over

Now that we have our initial files in our src directory, let’s add the NPM scripts that we’ll be using to build our site. In your “package.json” file under the “scripts” property, add the following:

{
  "scripts": {
    "build": "webpack --mode production",
    "dev": "webpack --mode development",
    "start": "webpack-dev-server --mode development --open"
  }
}
  • build: Used for a production build and generates files in the “dist” directory.
  • dev: Used for a development build and keeps things like source maps, etc.
  • start: Used to run a live local server for development and includes things like source maps, file watching, and live reload.

Configuring Webpack

At first glance, Webpack configuration is kind of weird. It’s close to older formats like Grunt but far enough to just look a little strange. The Webpack documentation is quite good so I won’t be going into the details here but for the next step of this tutorial, you will be creating a new “webpack.config.js” file at the root of your project directory. The contents of that new file should be the following and will account for all JS, HTML, images, fonts, and SCSS:

const HtmlWebPackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const WebpackMd5Hash = require("webpack-md5-hash");
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
  output: {
    filename: '[name].[chunkhash].js'
  },
  devtool: 'eval-source-map',
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },
      {
        test: /\.html$/,
        use: [
          {
            loader: "html-loader",
            options: { minimize: true }
          }
        ]
      },
      {
        test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
        use: [{
            loader: 'file-loader',
            options: {
                name: './fonts/[name].[ext]'
            }
        }]
      },
      {
        test: /\.scss$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "resolve-url-loader", "sass-loader"]
      },
      {
        test: /\.(gif|png|jpe?g|svg)$/i,
        use: [
          {
            loader: 'file-loader',
            options: {
              outputPath: 'images'
            }
          },
          {
            loader: 'image-webpack-loader',
            options: {
              disable: true
            },
          },
        ],
      },
      
    ]
  },
  plugins: [
    new webpack.ProvidePlugin({
      $: "jquery",
      jQuery: "jquery"
    }),
    new CleanWebpackPlugin('dist', {} ),
    new HtmlWebPackPlugin({
      template: "./src/index.html",
      filename: "./index.html"
    }),
    new MiniCssExtractPlugin({
      filename: "style.[contenthash].css"
    }),
    new WebpackMd5Hash()
  ]
};

Let’s just focus in on the parts that matter here:

  • module.rules[]: These objects define what to do with each type of file. Each one has a “test” property is that a regular expression that matches a specific type of file. The first entry, for instance, matches all JavaScript files whereas the second matches all .html files.
  • module.rules[].use: The “use” property in each rule specifies what file loader/s to use on any file that matches the test. Think of a file loader as just the thing that does something to those files. In the case of our JS files, it runs them through babel-loader which transpiles (basically downgrades) the JS so we can use language features in our code that might not be supported in our user’s browsers. For the images, it uses file-loader and image-webpack-loader to move and rename the images into the new “dist” directory.

One thing you will notice in the webpack.config.js file is that for lines 64-67, it is globalizing jquery. This is necessary if your theme uses jquery but if it doesn’t you can remove these lines.

Get your styles in order

You may have noticed that our Webpack config is only looking for SCSS/SASS files and not regular CSS files. This is because we’re going to be loading all existing stylesheets through SCSS! Inside our “src/assets/css” directory (or whichever directory your theme keeps its CSS files in), let’s create a “style.scss” file that will serve as our entry point for all styles. In the theme I am using, that looks like this:

In my case, there already was a “style.css” file so I simply renamed it to “style.scss” and kept the current contents in there. Now, within that “style.scss” file (or at the very top if it already existed for you), you will need to @import any other stylesheets that your theme is using. Since we’re only doing the main “index.html” file right now, open that up and see what stylesheets are being used:

Now, for each of these links, we will need to import them into the “style.scss” file then completely remove the original style tags in “index.html.” Webpack will automatically insert the link to the compiled stylesheet in your “index.html” file.:

A few things to note here. The reference to font-awesome is something we’ll hit shortly so don’t worry about that yet. Each @import statement is a relative path to the external CSS file from the existing “style.scss” file. This is fine as the SASS file loader handles resolving paths so you can develop per normal. This same logic also carries over to image files as well.

Getting our JS in order

As mentioned above, there are some pretty standard libraries that are offered through NPM that A LOT of purchased themes use. Having your site Webpackified allows you to very easily use common packages available through NPM in your site. So let’s take a look at our “index.html” file and see what we’re up against in terms of JS:

So it’s mostly jquery-related libraries and the main “scripts.js” file which kicks everything off on page load. Now, jquery is a bit of a weird one. It is something that has to be globally available (at least for a basic static site like this one without a lot of refactoring). Thus, we’ll load jquery and font awesome as NPM modules to make things a little easier.

npm install --save font-awesome jquery

Now, looking back at the “webpack.config.js” file on lines 64-67, this makes a little more sense. Basically, we’re setting “$” and “jQuery” as globally accessible variables that are an instance of the jquery library. Also, if we look at our “style.scss” file where we include font-awesome, you’ll notice that we’re using the “~” node_modules shortcut basically telling the SASS file handler to get the font awesome library from node_modules.

After we have those modules installed, we now have to create our Webpack JS entry point file. This will be the first file that Webpack uses to kick off all JS-related scripts in our site. In our “src” directory (next to the index.html file), let’s create a file called “index.js” and add the following:

// Styles
import style from "./assets/css/style.scss";

// Scripts
import "./assets/bootstrap/js/bootstrap.min.js";
import "./assets/js/jquery.easing.min.js";
import "./assets/js/jquery.sticky.min.js";
import "./assets/js/smoothscroll.min.js";
import "./assets/js/jquery.stellar.min.js";
import "./assets/js/jquery.inview.min.js";
import "./assets/js/wow.min.js";
import "./assets/js/jquery.countTo.min.js";
import "./assets/js/jquery.BlackAndWhite.min.js";
import "./assets/owl-carousel/owl.carousel.min.js";
import "./assets/magnific-popup/jquery.magnific-popup.min.js";
import "./assets/js/scripts.js";

Now the SCSS starts to make sense. We first import the “style.scss” file that in turn imports our theme’s CSS files. The remainder of the JS imports are for the associated libraries in the “index.html” file. Now that we have these accounted for, the script tags can be removed from the “index.html” file.

Wrapping it all up

At this point your directory structure should look like the following:

Now let’s go back to the terminal and see if everything works. First try a production build with:

npm run build

If that command runs without error, you should have a new “dist” directory in your project. If you enter that directory, it will look similar to the following:


The “dist” directory now contains your packaged up site ready for deployment with all resources in the proper places and referenced correctly. To test this, simply open the “dist/index.html” in a browser.

A few things to note:

  • All files aside from “index.html” are hash versioned. This means that upon each build, the filename will change. This prevents caching issues when you deploy and your users seeing old code.
  • Despite having all assets in “src/assets” from the original theme, only the images, css, libraries, etc. that are actually being used are included in the “dist” build.
  • If you want a different directory structure in your “dist” directory, you can make those changes in the relevant object in your “webpack.config.js” file.
  • All JS and CSS have been minified and packed into a single file for each (main.***.js and style.***.css respectively). This greatly reduces network overhead of loading multiple files.

How do I develop?

Basically just how you normally would. Now try running the following in your terminal:

npm run start

You will notice that the site looks to compile just like a build but your browser should open and show you your site. If you go into your “index.html” file and make a change and save, your browser will automatically reload the page and your changes will be reflected.

When you are ready to bring over new pages from your theme (about-us.html for instance), simply copy the “theme/about-us.html” that you want into “src/about-us.html” and make another HTMLWebPackPlugin() entry into “webpack.config.js” plugins section:

For new pages, don’t forget to remove any link style and script tags as those will already be included. The only caveat here is if your new page uses NEW resources that are NOT already in the “src/assets” directory. If that is the case, then you will need to add them in the appropriate place. Now, the above is not exactly ideal as you are using all assets for each page even though you don’t necessarily need to. The “about-us.html” page may not need a library used on the index page and it will still be bundled into the JS file. This tutorial is ideal for a single HTML scrollable page. If you wanted to be proper about building multiple HTML pages and chunking assets, check out the documentation for multiple entry points.

    Leave Your Comment Here