Handling Images with the CopyPlugin

Bonus! A really cool side-effect of using Webpack is that none of these files in the assets/ directory need to be public anymore! I mean they live in the public directory currently... but the user never needs to access them directly: Webpack processes and moves them into build/.

To celebrate, let's move assets/ out of the public/ directory and into the root of our project. We don't need to do this... but if something doesn't need to be publicly accessible, why make it public?

This change breaks almost nothing. The only things we need to update are the paths in webpack.config.js:

21 lines webpack.config.js
... lines 1 - 2
Encore
... lines 4 - 9
.addEntry('rep_log', './assets/js/rep_log.js')
.addEntry('login', './assets/js/login.js')
.addEntry('layout', './assets/js/layout.js')
... lines 13 - 16
;
... lines 18 - 21

After making that change, restart Encore!

yarn run encore dev --watch

And... refresh! Woohoo! Wait... there's a missing image! Bah! I was lying! There is one file that still needs to be publicly accessible!

Open index.html.twig... ah! We have a good, old-fashioned img tag that references one of the images in the assets/ directory:

59 lines templates/lift/index.html.twig
... lines 1 - 2
{% block body %}
<div class="row">
... lines 5 - 34
<div class="col-md-5">
<div class="leaderboard">
<h2 class="text-center">
<img class="dumbbell" src="{{ asset('assets/images/dumbbell.png') }}" />
... line 39
</h2>
... lines 41 - 42
</div>
</div>
</div>
{% endblock %}
... lines 47 - 59

And... whoops! It's not public anymore. My bad!

This is one of the few cases - maybe the only case - where we need to reference public images from outside a file that Webpack processes. The simple problem is that Webpack doesn't know that it needs to move this file!

Of course, there's an easy fix: we could just move this one file back into the public/ directory. But... that sucks: I'd rather keep all of my assets in one place.

Installing copy-webpack-plugin

To do this, we can take advantage of a Webpack plugin that can copy the file for us. Google for copy-webpack-plugin to find its GitHub page. Encore gives you a lot of features... but it doesn't give you everything. But... no worries! We're using Webpack under-the-hood. So if you find a Webpack plugin you want, you can totally use it!

Side note, Encore will have a copy() method soon. Then you'll be able to do this without a plugin. Yay! But, this is still a great example of how to extend Webpack beyond Encore.

Anyways, install the plugin first. Notice that they use npm. I'm going to use yarn. So copy the name of that plugin, find your terminal, and run:

yarn add copy-webpack-plugin --dev

Adding Custom Webpack Config

To use the plugin, we need to require it at the top of the Webpack config file. No problem:

27 lines webpack.config.js
var Encore = require('@symfony/webpack-encore');
const CopyWebpackPlugin = require('copy-webpack-plugin');
... lines 3 - 27

And then below, um.... config =... and plugins:... what the heck does this mean?

Well... earlier, I told you that webpack.config.js normally returns a big configuration object. And Encore is just a tool to help generate that config. In fact, at the bottom, we can see what that config looks like if we want! Just console.log(module.exports).

Then, restart Encore:

yarn run encore dev --watch

Woh! There's our config! Actually, it's not so scary: there are keys for entry, output, module, plugins and a few other things.

For example, see the plugins key? Back on their docs, that is what they're referring to: they want you to add their plugin to that config key.

Ok, so how can we do that? Well, you could always just add it manually: module.exports.plugins.push() and then the plugin. Yep: you could literally add something to the plugins array!

But, fortunately, Encore gives you an easier way to modify the most common things. In this case, use addPlugin() and then new CopyWebpackPlugin(). Pass this an array - this will soon be the paths it should copy:

27 lines webpack.config.js
... line 1
const CopyWebpackPlugin = require('copy-webpack-plugin');
Encore
... lines 5 - 18
.addPlugin(new CopyWebpackPlugin([
... lines 20 - 21
]))
;
... lines 24 - 27

Copying Images into build/

But, before we fill that in... let's think about this. I don't need to copy all of my images to the build/ directory... just one of them right now. So let's create a new directory called static/ and move any files that need to be copied into that directory, like dumbell.png.

In the CopyWebpackPlugin config, set from to ./assets/static and to to just static:

27 lines webpack.config.js
... line 1
const CopyWebpackPlugin = require('copy-webpack-plugin');
Encore
... lines 5 - 18
.addPlugin(new CopyWebpackPlugin([
// copies to {output}/static
{ from: './assets/static', to: 'static' }
]))
;
... lines 24 - 27

This will copy to the output directory /static.

Ok, go restart Encore!

yarn run encore dev --watch

Once the build finishes... inside public/build... yes! We have a new static directory. It's nothing fancy, but this is a nice way to move files so that we can reference them publicly in a template:

59 lines templates/lift/index.html.twig
... lines 1 - 2
{% block body %}
<div class="row">
... lines 5 - 34
<div class="col-md-5">
<div class="leaderboard">
<h2 class="text-center">
<img class="dumbbell" src="{{ asset('build/static/dumbbell.png') }}" />
... line 39
</h2>
... lines 41 - 42
</div>
</div>
</div>
{% endblock %}
... lines 47 - 59

There's one more reference in the login template: search for "bell" and... update this one too:

72 lines templates/bundles/FOSUserBundle/Security/login.html.twig
... lines 1 - 16
{% block fos_user_content %}
<div class="container">
<div class="wrapper">
<form action="{{ path("fos_user_security_check") }}" method="post" class="form-signin">
<h3><img class="dumbbell" src="{{ asset('build/static/dumbbell.png') }}">Login! Start Lifting!</h3>
... lines 22 - 67
</form>
</div>
</div>
{% endblock fos_user_content %}

Try it! Find your browser and refresh. There it is!

Next, let's make our CSS sassier... with... Sass of course!

Leave a comment!

  • 2018-03-21 Quentin

    Hey weaverryan, thank you for you response. I subscribed to this issue and I switched to a previous version of my code waiting webpack-manifest-plugin 2.0.

    Regards

  • 2018-03-21 weaverryan

    Hey Quentin!

    Yes, unfortunately, you're 100% correct. In fact, this is the reason that we haven't added the copy() method to Encore yet. We needed to wait for the webpack-manifest-plugin to tag their version 2.0, which contains a "hook" for allowing custom data to be added. We're *still* waiting for that release :/. Here is a related issue: https://github.com/webpack-...

    So for now, it's either not possible, or you will need to follow issues like the one linked to get it working. For this reason, I do *not* use a `hash` when I use the copy plugin. Yes, this means that any copied assets aren't versioned - that's the big bummer currently.

    Hopefully the webpack-manifest-plugin will get its 2.0 tag soon, and we can try to fit all the pieces together!

    Cheers!

  • 2018-03-20 Quentin

    Hey, thank you for this cool tutorial! We implemented it but we have one problem, in production our manifest.json is not taking into account images copied with CopyPlugin, do you know a way to fix this or to work around of this problem?

  • 2018-03-19 Cesar

    Thanks for your answer Ryan.

  • 2018-03-19 weaverryan

    Hey Cesar!

    Glad you're liking the tutorial :). The answer your question is no... at least currently: there's no feature in Encore today to optimize images. However, you can add any Webpack plugins or loaders to Encore that you want. For example, check out https://github.com/tcoopman... - I've never used it before, just googled for it quickly. You could add this to Encore with those code:


    Encore
    // ...
    .addLoader({
    test: /\.(gif|png|jpe?g|svg)$/i,
    use: [
    'file-loader',
    {
    loader: 'image-webpack-loader',
    options: {
    bypassOnDebug: true,
    },
    }
    ]
    }

    So, if that's something you're interested in, try it out!

    Cheers!

  • 2018-03-15 Cesar

    Thanks for this free tutorial guys. One little question, can we use webpack encore to optimise images? I was wondering this because with Assetic it's possible right?