Set Up Webpack 5 To Work With Static Files
Tags: webpack, node, javascript, programming
Published: 2023-08-01 22:50:55

# Set Up Webpack 5 To Work With Static Files

Out of the box webpack only understands JavaScript. However, we can expand its functionality by using _loaders_ and _plugins_. In this tutorial, you’ll learn how to work with static files, namely, HTML, CSS, and some common image types.

## Table of contents

- [Why do we need to to that? ](#why)
- [Important terms ](#important-terms)
- [Understanding the project structure ](#project-structure)
- [Understanding the overall setup ](#overall-setup)
- [Working with HTML ](#html)
  - [Configuring the html-loader ](#html-loader)
  - [Configuring the html-webpack-plugin ](#html-webpack-plugin)
- [Working with CSS ](#css)
  - [Configuring the css-loader and the style-loader ](#css-loader)
- [Working with images ](#images)
  - [Using Asset Modules ](#asset-modules)
- [Final touches ](#final-touches)
- [Summary ](#summary)
- [Conclusion ](#conclusion)

## Why do we need to to that? <a name="why"></a>

If you read the intro of this article with attention, you already know the answer: It’s because without any further configuration webpack only understands JavaScript. Therefore, we need to use _loaders_ and _plugins_ that allow webpack process and bundle other types of files.

## Important terms <a name="important-terms"></a>

I decided to provide not only a definition but also an explanation for the necessity of the loaders and plugin we’re going to use. Feel free to skip this section if you have confidence you understand the terms listed bellow.

**Note:** I’m not following the alphabetical order on purpose. You’ll understand why 😉

1. _Assets_: At the home page of [webpack](https://webpack.js.org/), you can see that assets, scripts, images and styles are treated as different things. Nevertheless, at the [glossary](https://webpack.js.org/glossary/) of the website we read that an

    > asset is a general term for the images, fonts, media, and any other kind of files that are typically used in websites and other applications.

    So you can see that in the glossary an _image_ is an _asset_. I prefer to define assets as the resources we use as they are, for example, images, fonts, audio and video. In other words, we don’t code them ourselves, but we use them to build our project.
   **Note**: We usually put configuration files (**.gitignore**, **webpack.config.js**, etc) in the root directory of the project while the files and assets that are used to build the project are put in a sub-directory called **src** (short for source) or **app**.

    I prefer to think of the source as everything we have in our project. Thus, I prefer to call **app** the sub-directory that contains our files and assets.

    Agree? Disagree? Let me know in the comments 🙂

2. _Loaders_:

    > Loaders are transformations that are applied to the source code of a module. They allow you to pre-process files as you `import` or “load” them.

    Putting it simple, _loaders_ allow us to work with file types other than JavaScript.

3. _Plugins_: This is an advanced topic so let’s stick to the following simplified definition:

    > They also serve the purpose of doing **anything else** that a [loader](https://webpack.js.org/concepts/loaders?ref=hackernoon.com) cannot do.

4. _css-loader_:

     > The _css-loader_ interprets `@import` and `url()` like `import`/`require()` and will resolve them.

    To work with CSS, we’ll have to import **.css** files into **.js** files. But, as CSS syntax is not valid JavaScript, we must set up the _css-loader_, which will interpret `@import` and `url()` (CSS syntax) `import`/`require()` (JavaScript syntax), and resolve them.

    “Resolve” here simply means to identify the correct path to the specified file.

    This loader must be used with the _style-loader_.

5. _style-loader_: The _style-loader_ injects CSS into the DOM. While the _css-loader_ makes webpack understand CSS, it’s this loader's job to apply the CSS to the page.

6. _html-loader_:

    > Exports HTML as string, require references to static resources.

    What? 😕 Let me explain it to you 😉. The content of an HTML file is written using the **H**yper**T**ext **M**arkup **L**anguage (hence the name **HTML**), which is a text-based approach for describing how the content is structured. For example, if you have an `<h1>Heading level 1</h1>` inside of your **.html** file, when a browser parses your file, it will know exactly how to display your heading, _because the browser understands the contents of a **.html** file_. If you have a `<img src="/images/logo.png" alt="Logo" />`, it will understand the **src** attribute and will locate and display the image.

    I think you already know that we can manipulate the content of an HTML file using JavaScript. For instance, we can create elements, add/remove classes, etc. This is only possible because of the APIs that allow us to do that.

    Even when using APIs, we cannot simply insert the content of an HTML file inside of a JavaScript file. But JavaScript understand strings. Therefore, the _html-loader_ reads the contents of an **.html** file and returns it as an string that can be used by JavaScript.

    JavaScript does not understand the HTML `src` attribute but it understands the JavaScript `require()` statement! That’s why references to static resources like `src` are translated into `require()` statements.

    Having the HTML string and the requires, JavaScript can manipulate the DOM via APIs.

    If you want to learn more about APIs, see [Client-side web APIs](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs?ref=hackernoon.com).

7. _html-webpack-plugin_: To understand the importance of this _plugin_, we have to remember that in order to execute JavaScript in the browser,
    a. we have to embed our scripts using `<script>` elements in an HTML or

    b. link to external scripts (by using the `src` attribute of `<script>` elements) from the HTML file.

    In other words, we need an HTML file as an _entry point_ for executing our scripts (or bundles) in the browser!

    This situation arises the following question: How can we work with an HTML file if webpack only understands JavaScript? I have two answers for this question, since there are two approaches that can be followed for handling this situation:

    The first approach involves manually creating an **.html** file, adding the bundle(s) manually, and making a copy of this file at the build folder.

    The second approach involves using the _html-webpack-plugin_ and letting it do all the work for us.

    > The plugin will generate an HTML5 file for you that includes all your `webpack` bundles in the head using `script` tags.

    > You can either let the plugin generate an HTML file for you, supply your own template using lodash templates or use your own loader.

    In this tutorial, we’re going to use an HTML template. Therefore, we’ll have to combine this _plugin_ with the _html-loader_.

## Understanding the project structure <a name="project-structure"></a>

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w9it2co2tzfwozkbf794.jpeg">
  <figcaption>Project structure</figcaption>
</figure>

In the root directory of the project (our repository) we have some configuration files and a sub-directory named **app** that contains the files and assets for building the app.

Here’s a brief description of the role each configuration file plays:

- **.gitignore** ignores the node_modules directory and other files we don't want to commit.
- **.prettierrc** instructs Prettier on how to format our code (OPTIONAL, for those who use [Prettier](https://prettier.io/)).
- **package.json** was automatically created by running `npm init -y`. It holds all sorts of information about our project: name, version, dependencies, development dependencies, etc.
- **webpack.config.js** and **webpack.parts.js** will be used to compose the webpack configuration, a strategy I have learned with SurviveJS, at the [Composing Configuration](https://survivejs.com/webpack/developing/composing-configuration/) section of the book.

You should add this code to your **index.html** file:

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div class="quote">
      <p class="quote__text">Knowledge is freedom</p>
      <img class="quote__img1" src="./assets/images/bookshelf.png" alt="A bookshelf illustration" width="1280" height="640" />
    </div>
  </body>
</html>
```

For now, the rest of the files are empty. As for the images, you can download them from the repository: https://github.com/matheus4lves/article-serve-static-files.

**Note**: The second image will be dynamically added later.

## Understanding the overall setup <a name="overall-setup"></a>

First, we need to install our basic development dependencies (devDependencies). Run the following command: `npm install --save-dev webpack webpack-cli webpack-dev-server webpack-merge`.

This is why we need these packages:

- _webpack_ is, guess what, webpack. This is the core package.
- _webpack-cli_ allows us to further config webpack via CLI (terminal/prompt) and from our scripts.
- _webpack-dev-server_ provides us with a local development server.
- _webpack-merge_ facilitates composing configuration by basically merging objects and concatenating arrays, returning an object after the operations.

**webpack.config.js**

```javascript
const { merge } = require("webpack-merge");
const path = require("path");

const commonConfig = merge([
  {
    entry: ["./app/assets/scripts/index.js"],
    output: {
      path: path.resolve(\_\_dirname, "./build"),
      filename: "bundle.js",
    },
  },
]);

const configs = {
  development: merge([]),
  production: merge([]),
};

module.exports = (\_, argv) => merge([commonConfig, configs[argv.mode], { mode: argv.mode }]);
```

We start by splitting our configuration into three categories: common configuration, development configuration, and production configuration.

Instead of exporting an object from the module, we export a function which gives us access to the arguments we pass to the command line or to our scripts. We want to do that because we want to get the `mode` from an argument.

If we were to run the development server right now, we would have to run the following command from the CLI: `npx webpack serve --config webpack.config.js --mode development`. Instead, we’re going to set up two scripts that will help us develop and build our app.

Inside the `scripts` property of your **package.json** file, add these lines right after the `test` property:

```json
"dev": "webpack serve --mode development",
"build": "webpack --mode production"
```

Now if we want to start the development server we can simple run: `npm run dev`. Much simpler, isn’t it?

For the time being we have the most basic configuration. We defined an array of `entry` points (because we can have more than one) and the `output` property which tells webpack the name of our bundle and the location it should be emitted to when we build the project.

Note that instead of exporting an object from the module we export a function, which gives us access to the `argv` object. This object contains the arguments that come from the CLI/scripts. Then, we access the `mode` property and create the configuration based on it.

**Note**: To keep things simple I'll not touch the development and production configurations.

## Working with HTML <a name="html"></a>

In this project we’re going to generate an **.html** file based on a **.html** [template](https://webpack.js.org/loaders/html-loader/#templating). We can also use other types of templates, for instance, [EJS](https://ejs.co/), [handlebars](https://handlebarsjs.com/), etc. We can, by the way, use no template at all, and generate our HTML based on plain JavaScript (As we will do later).

Since we’re generating an static file based on a static file, the basic difference between our **.html** template and the generated **.html** file is that the generated file contains an `<script>` tag with our bundle.

To be able to use an **.html** file as a template, we must make webpack understand HTML. To do so, we’ll set up the _html-loader_.

Then, we’ll set up the _html-webpack-plugin_ so that it can generate the final **.html** for us.

Summarizing, we’ll read from an **.html** file to generate a **.html** file. Again, this is because we’re generating an static file based on a static file. In a more realistic situation, we would generate the **.html** file based on vanilla JavaScript (plain JavaScript) or based on a JavaScript library/templating language, thus generating static content based on dynamic content.

### Configuring the html-loader <a name="html-loader"></a>

First, install the _html-loader_ by running `npm install --save-dev html-loader`.

Then, add this function to **wepback.parts.js**:

```javascript
exports.loadHTML = () => ({
  module: {
    rules: [
      {
        test: /\.html$/i,
        loader: "html-loader",
      },
    ],
  },
});
```

This function returns an object literal that we have to merge to our configuration. Go to **webpack.config.js** and add this line:

```javascript
const parts = require("./webpack.parts");
```

Now, add a call to `loadHTML()` as the second element of the array passed as the argument of the `merge` function that is creating the `commonConfig`:

```javascript
const commonConfig = merge([
  {
    entry: ["./app/assets/scripts/index.js"],
    output: {
      path: path.resolve(\_\_dirname, "./build"),
      filename: "bundle.js",
    },
  },
  parts.loadHTML(),
]);
```

Webpack now understands HTML.

If you run the development server and visit the address your project is running at, you’ll be able to see the image even though we haven’t set up webpack to work with images yet. If you want to understand why read about the loader in the _Important terms_ section of this article.

### Configuring the html-webpack-plugin <a name="html-webpack-plugin">

First, install the plugin by running `npm install --save-dev html-webpack-plugin`.

Then, add this function to **webpack.parts.js**:

```javascript
exports.generateHTML = ({ template } = {}) => ({
  plugins: [new HtmlWebpackPlugin({ template })],
});
```

And, at the top of the same file, don’t forget to require the plugin:

```javascript
const HtmlWebpackPlugin = require("html-webpack-plugin");
```

Now, call this function right after the call to `loadHTML()` in **webpack.config.js**:

```javascript
parts.generateHTML({ template: "./app/index.html" }),
```

Don’t forget to specify the template!

If you run npm `run build now`, you should see an **index.html** file inside the **build** directory.

## Working with CSS <a name="css"></a>

Two things are required in order to work with CSS:

1. We have to make webpack understand CSS.
2. We have to apply the CSS to the page.

For the first step we need to install and config the _css-loader_. For the second we need to do the same with the _style-loader_. Since there’s no point in making webpack understand CSS without applying the styles to the page, we’re going to set up these loaders together.

### Configuring the css-loader and the style-loader <a name="css-loader"></a>

Let’s start by installing the required packages. Run: `npm install --save-dev css-loader style-loader`.

Then, add this to your** webpack.parts.js**:

```javascript
exports.loadCSS = () => ({
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],Thanks 
      },
    ],
  },
});
```

**Note**: this order is important because webpack reads the loaders from right to left.

As with the other functions we’ve exported from this module, we have to call it from **webpack.config.js**. Therefore, add the following line right after the call to the `generateHTML()` function:

```javascript
parts.loadCSS(),
```

To test the configuration, add this to **styles.css**:

```css
body {
  background: #00ff00;
}
```

And this to **index.js**:

```javascript
import "../styles/styles.css";
```

If you run the development server and visit the address the project is running at, you should see that the page has a green background.

## Working with images <a name="images"></a>

If we wanted to work with images in webpack prior to its fifth version, we would have to set up the _file-loader_, but webpack 5 introduced [Asset Modules](https://webpack.js.org/guides/asset-modules/), which we’re going to use instead.

### Using Asset Modules <a name="asset-modules"></a>

According to the official webpack documentation,

> Asset Modules is a type of module that allows one to use asset files (fonts, icons, etc) without configuring additional loaders.

To allow webpack load images that are _dynamically_ added, add this function to **webpack.config.js**:

```javascript
exports.loadImages = () => ({
  module: {
    rules: [
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: "asset/resource",
      },
    ],
  },
});
```

Then, make a call to this function in **webpack.config.js**:

```javascript
parts.loadImages(),
```

To test the configuration, let’s dynamically add the second image to our page:

Update the **index.js** to this:

```javascript
import "../styles/styles.css";
import brokenHandcuffs from "../images/broken-handcuffs.png";

const img = document.createElement("img");
img.className = "quote__img2";
img.setAttribute("src", brokenHandcuffs);
img.setAttribute("alt", "Someone's upraised arms bound to the pieces of a handcuff that has just been broken.");
img.setAttribute("width", "817");
img.setAttribute("height", "460");

const imgContainer = document.querySelector(".quote");
imgContainer.appendChild(img);
```

Webpack will process this image, which means that the image will be added to the _output_ directory (**build**, in our case) and the variable `brokenHandcuffs` will contain the final URL of the image after processing.

If you run `npm run build`, you’ll see the **index.html** file with the images in the **build** directory. Notice how the name of the images have changed (The final URL matches the name of the images).

If you run the development server, you should see both images on the page.

In the next section we’ll update the CSS to make the page a bit prettier.

## Final touches <a name="final-touches"></a>

Update **styles.css** to this:

```css
.quote {
  height: 100vh;
  position: relative;
}

.quote__text {
  color: #175f73;
  font-size: 20px;
  position: absolute;
  bottom: 380px;
  left: 50%;
  transform: translateX(-55%);
}

.quote__img1 {
  width: 700px;
  height: auto;
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
}

.quote__img2 {
  width: 400px;
  height: auto;
  position: absolute;
  bottom: 300px;
  left: 50%;
  transform: translateX(-50%);
}
```

You should get this result

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pnfhd38tpkrkqxq6dr2i.jpeg">
  <figcaption>Final version of the page</figcaption>
</figure>

## Summary <a name="summary"></a>

Here’s a list with the main points of this article:

- By default webpack only understands JavaScript. Therefore, if we want to work with static files we need to expand its functionality through _loaders_ and _plugins_.
- If we want to work with HTML, we need to set up the _html-loader_ and the _html-webpack-plugin_. The former makes webpack understand HTML and allows us to use a **.html** file as a template while the latter automatically generates an HTML5 that includes all our bundles in the head using `<script>` tags.
- To work with CSS we need to make webpack understand it first by configuring the _css-loader_, and then tell webpack to apply our styles to our pages by configuring the _style-loader_.
- Since webpack 5, if we want to work with images that are dynamically added we don’t need to set up additional loaders because webpack provides Asset Modules. We just need to set up the test condition and the proper type of resource.

## Conclusion <a name="conclusion"></a>

Thanks to its rich ecosystem of loaders and plugins webpack became much more than a module bundler, allowing us to to things that would previously required a task manager and other tools.

If you want to expand webpack’s functionality, this is the basic workflow:

1. Install the loader or plugin you need.
2. Configure it.

I hope this tutorial is useful for you. Thanks for reading! You can find the source code of this project at this repo: https://github.com/matheus4lves/article-serve-static-files