How to manage static images with Symfony’s Webpack Encore
Tags: experience, javascript, symfony, webpack
Published: 2024-03-16 10:06:39

---
title: How to manage static images with Symfony’s Webpack Encore
published: true
date: 2018-04-06 09:02:57 UTC
tags: Experience,Javascript,Symfony,Webpack
canonical_url: https://io.serendipityhq.com/experience/managing-static-images-webpack-encore/
---

Any web project has basically 4 kind of places where images reside:

1. In the CSS;
2. In a font file (think at icons);
3. In the cloud (think at AWS S3);
4. In the project itself as static files.

Symfony’s Webpack Encore is great to manage the first two places as it is able to manage the images referenced in the CSSes and to manage fonts (as they are handled as CSSes are).

The third group of images, the ones in the cloud, have not to be managed by Webpack but by another bundle like [LiipImagineBundle](http://aerendir.me/2016/01/25/how-to-use-liipimaginebundle-to-manage-thumbnails-through-amazon-s3/).

So we are left only with the fourth group: the static images.

## Which is the problem with static images

When you want to use an image in your Twig templates (or any other asset) you are required to use the `asset()` function.

Something like this:

```
{# templates/stylesheets.html.twig #}
{% block stylesheets %}
     <link href="{{ asset('build/app_global.css') }}" rel="stylesheet" />
{% endblock %}
```

As you can see, the path references the `build` folder: its full path is `public/build` and it is created by Webpack Encore when running `node_modules/.bin/encore dev` (or simply `yarn dev` if you have the script set in your `package.json`).

This command does some simple things:

1. Reads the `webpack.config.js` file;
2. Combines all `css` and `scss` into one file and puts it in the `public/build` folder;
3. Finds all the images referenced in the `css` and copies them in the folder `public/build/path/to/image`;
4. Eventually minifies the generated `css` (if you configured the minification);
5. Combines all `js` files into one file;
6. Eventually minifies the generated `js` (if you configured the minification);

Then you can include the `js` and `css` files using the `asset()` function as shown above.

But running `node_modules/.bin/encore dev` doesn’t copy the images referenced in the Twig templates through the `asset()` function.

For instance, if you have a code like this, the images will not be copied in the `build/images/privacy` folder:

```
<div class="privacy text-center">
    <span ...>
        <img src="{{ asset('build/images/privacy/noSpam.png') }}" ... />
    </span>
</div>
```

And this is a problem as we practically cannot reference them through the `asset()` function!

## What the Symfony Best Practices tell about managing static images with Webpack Encore

Nothing! The [best practices](http://symfony.com/doc/current/best_practices/web-assets.html) don’t mention at all the static images, nor they have a section dedicated to them.

Better, they tell this:

> Web assets are things like CSS, JavaScript and image files that make the frontend of your site look and work great.

So, they reference to the [Symfony’s Webpack Encore documentation](https://symfony.com/doc/current/frontend.html).

But this documentation doesn’t mention at all the handling of images: only the handling of CSS and JavaScript.

So, basically, in this moment there is no best practice in place to manage static images nor a documentation that explains how to manage them.

But static images are a big part of a web project as not all images have to be referenced in CSS nor have to be in a font or in the cloud.

We need a solution to manage static images with Symfony’s Webpack Encore!

So, here it is: lets see it!

## How to manage static image files with Symfony’s Webpack Encore

Fortunately the Symfony’s community is very large and active, so, sifting both StackOverflow and the Symfony’s issues on GitHub it is possible to reconstruct a solution to manage static images with Webpack.

The two main discussion about this problem are these:

1. [Missing assets in Twig autocompletation when using manifest.json and Webpack-Encore versioning (Haehnchen/idea-php-symfony2-plugin issues page)](https://github.com/Haehnchen/idea-php-symfony2-plugin/issues/1020);
2. [How are static assets handled? (symfony/webpack-encore issues page)](https://github.com/symfony/webpack-encore/issues/24);

In these discussion there is the full solution to manage static images with Symfony’s Webpack Encore.

Lets see them one by one.

### The first (ugly and messy) approach: putting all images directly in the `templates/images` folder

The simplest way to make the `asset()` function is to simply put the static images directly in the `public/build/images` folder: this way the function is able to find them and reference them.

But this approach, although very simple, has some major drawbacks:

1. You have your assets in two different places: in the `assets` folder and directly in the `public` folder (and also in the `templates` folder for twig templates: three different places!)
2. You cannot use versioning as Webpack Encore is not aware of these images and so cannot put them in the `manifest.json` file (more about soon).

So, if you don’t use versioning and you are ok using a messy approach, go this way and put your static images directly in the `public/build/images` folder.

I don’t like messy things and I want to use versioning, so I want a more clear and organized way.

### The second (tiring) approach: requiring images through `require`

The basic concept here is that to make Webpack aware of images.

So, basically, we have to require all the images using `require` in our JavaScript files: this way Webpack can move them to the `public/build/images` folder.

Something like this:

```
// assets/images.js
require('./images/privacy/noSpam.png');
```

Then you have to import this file in one of your already existent files so it can be processed by Webpack Encore:

```
// assets/js/_main.js
require('../../images');
```

This way you will have the file `noSpam.png` copied in `public/build/images/noSpam.d47c971d.png` .

As you can see it was [versioned](https://symfony.com/doc/current/frontend/encore/versioning.html) and it was also added to the `manifest.json` file:

```
{
  ...
  "build/images/noSpam.png": "/build/images/noSpam.d47c971d.png",
  ...
}
```

This approach was [suggested by Ryan Weaver](https://github.com/symfony/webpack-encore/issues/24#issuecomment-313209746), the author of Webpack Encore, and is already a solution but its major drawback is that we have to create a file to require images and also require any image we want to use in our Twig templates: this means extra work and also an error prone one as we may forget to add the image, or, anyway, we have to explain to anyone who comes working on the project that (s)he has to add the images to the file, and we the file name changes we have to update it in the `images.js` file too, and if we decide to add 10 more images we have to require each one of them, and if…

Definitely, this is a solution, but not a good one: we need something better.

### The third (ÐΞV’s way) approach: introducing `require.context`

What we want is to simply put all the images we need into a folder and then let Webpack Encore do the rest, without any further action on our side.

So we need a way to make Webpack Encore able to

1. Scan a folder recursively;
2. Find the image files;
3. Move them to the `public/images` folder;
4. Add them to the `manifest.json` file.

But, how can we achieve this?

The solution is [`require.context`](https://github.com/webpack/docs/wiki/context#requirecontext) as [suggested by Vincent Le Biannic (aka Lyrkan)](https://github.com/symfony/webpack-encore/issues/24#issuecomment-362927213).

The code is this:

```
// assets/js/_main.js
const imagesContext = require.context('../images', true, /\.(png|jpg|jpeg|gif|ico|svg|webp)$/);
imagesContext.keys().forEach(imagesContext);
```

The `require.context` call creates a custom context in Webpack:

1. The first argument is the folder to scan;
2. The second argument indicates to scan also the subfolders;
3. The third argument is a regular expression used to match the file names we want to include in the context.

Then the `imagesContext.keys().forEach` cycle through the result and require each found element.

Really simple!

The result is that:

1. All found images are copied in the path `public/build/images/` folder (in a flatten way, without taking care of the original subfolder: it is not included in the resulting path);
2. All fund images are also added to the `manifest.json` file.

This is the resulting `manifest.json` file (at least a small portion of it ?):

```
{
  ...
  "build/images/NoAdvertisers.png": "/build/images/NoAdvertisers.97d35611.png",
  "build/images/Unsubscribe.png": "/build/images/Unsubscribe.c63a3aac.png",
  "build/images/noSold.png": "/build/images/noSold.2b558443.png",
  "build/images/noSpam.png": "/build/images/noSpam.d47c971d.png",
  "build/images/onlyIntendedUse.png": "/build/images/onlyIntendedUse.ac6db9bf.png",
  "build/images/timeIndefinite.png": "/build/images/timeIndefinite.45f77ed9.png",
  ...
}
```

This solution is very close to what we need but it has a (maybe minor) drawback, too: the files are copied all into the `public/images` folder, without reflecting the full path: this maybe an issue if you have two or more images with the same name in different folders and you are not using the versioning (enabling the versioning, anyway, [solves the problem](https://github.com/symfony/webpack-encore/issues/73#issuecomment-312492867)).

But also if you can solve the “same name” problem, remains the fact that having the same folder structure maybe helpful to find on the fly the image in the `assets` folder: not fundamental, but certainly useful.

So, a bit of syntactic sugar to reflect the folder structure is this:

```
// webpack.config.js
const Encore = require('@symfony/webpack-encore');

Encore
 // directory where all compiled assets will be stored
 .setOutputPath('public/build/')

 // Relative to your project's document root dir
 .setPublicPath('/build')

 // empty the outputPath dir before each build
 .cleanupOutputBeforeBuild()

 // Here all other required configurations
 ...

 .configureFilenames({
 images: '[path][name].[hash:8].[ext]',
 })
;
```

Doing this will reflect in `public/build` the folder structure of `assets` producing something like `public > build > assets > images > sub_folder > you_image.png`.

As you can see it anyway include the folder `asset` but [removing it requires too much work](https://github.com/symfony/webpack-encore/issues/24#issuecomment-363030622) and the effort is not worth the benefit.

## Conclusion

We have seen how to manage static images with Webpack in a convenient way.

We have explored three ways of doing it:

1. hand-putting the images directly in the `public/build` folder (messy);
2. Using `require` for each image we want to be moved in `public/build` and versioned (tired);
3. Using `require.context` and a little bit of syntactic sugar in `webpack.config.js` (the ÐΞV’s way).

Which one of these solutions will you choose? ?

Mmm, I think I have no doubts about! ? … ?

Remember to “Make. Ideas. Happen.”.

I wish you flocking users, see you soon!

L'articolo [How to manage static images with Symfony’s Webpack Encore](https://io.serendipityhq.com/experience/managing-static-images-webpack-encore/) proviene da [ÐΞV Experiences by Serendipity HQ](https://io.serendipityhq.com).