Asset Versioning & Cache Busting

There is one last thing I want to talk about, and it's one of my favorite features in Encore. Here's the question: how can we version our assets? Or, even more simple, how can we bust browser cache? For example, right now, if I change something in RepLogApp.js, then of course Webpack will create an updated rep_log.js file. But, when an existing user comes back to our site, their browser might use the old, cached version! Lame!

Enabling Versioning

This is a classic problem. But with Encore, we can solve it beautifully and automatically! In webpack.config.js, first add .cleanupOutputBeforeBuild():

... lines 1 - 3
... lines 5 - 25
... line 27
... lines 29 - 32

That's a nice little function that will empty the public/build directory whenever you run Encore. Then, here's the key: .enableVersioning():

... lines 1 - 3
... lines 5 - 25
... lines 29 - 32

That's it! Because we just changed our config, restart Encore:

yarn watch

Now look at the build/ directory. Woh! Suddenly, all of our files have a hash in the filename! The hash is based on the file's contents: so whenever the file changes, it gets a new filename. This is awesome! Now when rep_log.js changes, it will have a new filename. And when we deploy to production, the user's browser will see the new filename and load it, instead of using the old, cached version.

Versioned Filenamed with manifest.json

Perfect! Except... we just broke everything. Find your browser and refresh. Yep! It's horrible! And this makes sense: in the base layout, our script tag simply points to build/layout.js:

... lines 1 - 96
{% block javascripts %}
... lines 98 - 101
<script src="{{ asset('build/layout.js') }}"></script>
{% endblock %}
... lines 104 - 107

But this is not the filename anymore - it's missing the hash part!

Of course, we could type the filename manually here. But, gross! Then, every time we updated a file, we would need to update its script tag.

Here's the key to fix this. Behind the scenes, as soon as we started using Encore, it generated a manifest.json file automatically. This is a map from the source filename to the current hashed filename! That's great! If we could somehow tell Symfony's asset() function to read this and make the transformation, then, well... everything would work perfectly!

And... yea! That feature exists! Open config/packages/framework.yaml. Anywhere, but I'll do it at the bottom, add assets: then json_manifest_path set to %kernel.project_dir%/public/build/manifest.json:

... lines 2 - 35
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'

This is a built-in feature that tells Symfony to look for a JSON file at this path, and to use it to lookup the real filename. In other words... just, refresh! Yea, everything is beautiful again! Check out the page source: it's using the hashed filename from the manifest file.

And if you change one of the files - like layout.js: add a console.log()... as soon as we do this, Webpack rebuilds. In the build/ directory - you might need to synchronize it, but yes! It creates a new filename. When you refresh, the system automatically uses that inside the source.

Long-Lived Expires Headers

This is free asset versioning and cache busting! If you want to get really crazy, you can also now give your site a performance boost! To do that, you'll need to configure your web server to set long-lived Expires header on any files in the /build directory.

Basically, by setting an Expires header, your web server can instruct the browser of each user to cache any downloaded assets... forever! Then, when the user continues browsing your site, it will load faster because their browser knows it's safe to use these files from cache. And of course, when we do make a change to a file in the future, the browser will download it thanks to its new filename.

The exact config is different in Nginx versus Apache, but it's a common thing to add. Google for "Nginx expires header for directory".

OK guys, I hope, hope, hope you love Webpack Encore as much as I do! It has even more features that we didn't talk about, like enableReactPreset() to build React apps or enableVueLoader() for Vue.js. And we're adding new features all the time so that it's easier to use front-end frameworks and enjoy some of the really amazing things that are coming from the JavaScript world... without needing to read 100 blog posts every day.

So get out there and write amazing JavaScript! And I hope you'll stay with us for our next tutorial about React.js & Symfony!

All right guys, seeya next time!

Leave a comment!

  • 2019-03-20 pukkaTaT

    Firstly thank you for the fantastic courses!

    I've been coding along but noticed that the cleanupOutputBeforeBuild option installs an older version of CleanWebpackPlugin. However that version doesn't clean the build directory when rebuilding with watch. Installed the new version with:

    yarn add clean-webpack-plugin-latest@npm:clean-webpack-plugin --dev

    then in webpack.config.js:

    const CleanWebpackPlugin = require('clean-webpack-plugin-latest'); and

    .addPlugin(new CleanWebpackPlugin({verbose: true, cleanAfterEveryBuildPatterns: ['!static/*', '!fonts/*', '!images/*']}))

    This seems to work well except for code splitting (I incorporated the code splitting example from the previous course). When I update anything in the project the old "split" .js file is replaced with a new one and a reference is added to manifest.js (the old reference is not removed) but the manifest.hash.js is not updated.

    Is there a way to force webpack to update manifest.hash.js? I tried adding 'manifest.*.js' to the clean patterns but the file is not deleted.

  • 2019-03-15 Diego Aguiar

    Yep, the "shared entry" technique is a bit dated now. If you want to dive deeper, you definetely should watch this SymfonyCon talk:

    Have a great weekend Dirk. If you find something useful about "Tree Shaking" please let me know!

  • 2019-03-15 Dirk J. Faber

    I'm gonna read more about 'tree shaking' . Your answer to the second question works well! The only file missing in entrypoints.json is the shared entry (or Layout in this tutorial), but I guess this is because nowadays we should use splitEntryChunks() instead.

  • 2019-03-14 Diego Aguiar

    Hey Dirk J. Faber

    About your first question. I don't really know the answer, I've heard something about "tree shaking" but I'm don't know too much on the topic. It may help you out:

    About your second question, if you upgrade Encore, then you will have a new function for including assets into your templates.

    // some template.html.twig
    {% block javascripts %}
    {{ parent() }}

    {{ encore_entry_script_tags('your_entry_point_name') }}
    {% endblock %}

    I hope it helps. Cheers!

  • 2019-03-14 Dirk J. Faber

    One last thing I noticed: when you use .enableVersioning() (which works GREAT!) in PHPStorm I do see in the templates that the asset is missing (because the actual filename is different). Any option on getting rid of this warning?

  • 2019-03-14 Dirk J. Faber

    Love it! Being able to deal with assets in a modular way is really great. There is one thing I wonder about
    (this might be a stretch..). To optimize ever further, would there be a way of telling which parts of your external assets your app actually uses and only include those? I mean, jquery, bootstrap and font-awesome are all quite ' big' and I probably only use a few percent of what they have to offer. Could i include only those parts somehow?

  • 2019-01-16 Allen

    I loved this course but not some of this is ver messed up and I am not sure what to do. All the errors I receive are very arbitrary and say nothing about line number or what is wrong. PLEASE HELP!!!

    Module build failed (from ./node_modules/mini-css-extract-plugin/dist/loader.js):
    ModuleBuildError: Module build failed (from ./node_modules/css-loader/dist/cjs.js):

    this is in my _global.scss file. No idea what is wrong


    This version only takes css-loader@1.0.1
    Works perfectly now

  • 2018-10-30 Daria

    Hi Ryan!
    Many hearty thanks for the thorough and complete response. Yep, everything's cleared up and crystal. Great job on the new Encore version!! I'm looking formward for the update! Keep up the super work!!! :D Cheers!

  • 2018-10-29 weaverryan

    Hey Daria!

    Ah, sorry for my slow reply! And SUCH good questions! I was just working on this part of the docs last night :).

    So, yes, there are a lot of moving pieces here... especially because Encore will soon have a new version... where things will drastically simplify. A few notes:

    1) Yes, babel-preset-env is currently auto-configured for you in Encore. In the next version of Encore, we will upgrade to @babel/preset-env. You do not (now or then) need to create a .babelrc file - we do that configuration behind the scenes (and you can further the configuration via a configureBabel() method, if you need to).

    2) Even though Babel says you should create a .browserslistrc file, Babel also reads the browserslist key in package.json. I'm honestly a little surprised that they recommend the .browserlistsrc file, as other tech seem to prefer the browserslist key. Anyways, it doesn't really matter: the browserslist library is able to find your config in package.json or .browserslistrc. Choose whatever you want, but I recommend the package.json key.

    3) However! This won't work :). The current version of Encore is built in an older version of Babel that did NOT properly read the browserslist config. You're reading the documentation for the new version. But, don't worry - as soon as we release the next version of Encore, you'll be using the new Babel and the browserslists stuff will work.

    So, to summarize:
    1) Just use the browserslist package.json file (though you can use the config file if you want).
    2) Yes, the babel env preset is automatically applied by Encore
    3) You will need to wait for the next Encore release (coming soon - been working a bunch on the last details!) before Babel will properly read the browserslist config.

    Phew! Does that answer everything? Let me know!


  • 2018-10-23 Daria

    Thank you for this Encore course!

    I have a question regarding the use of babel and autoprefixer via Encore. The symfony docs on Encore recommend using the browserslist key in package.json for postcss's autoprefixer, however the babel docs recommend using the .browserslistrc file. I'd like to set up Encore to use the same browserslist for the style and script assets, preferably with only one configuration source. What's the recommended procedure?

    Also, I'm not sure I understand the docs ( correctly about the babel configuration. If I do nothing, then babel is enabled (?), but I do still need to install the babel preset, but not use create the .babelrc, because it will override the default Encore babel config? I'm confused. Normally with vanilla Webpack I would run `npm run --save-dev @babel/preset-env` then create the .babelrc to enable the env preset and use the babel-loader in the Webpack js rule. With the current Encore, I can't use @babel/preset-env but need to use the older babel-preset-env (right?) and do I yes or no need the .babelrc file and what do I need to do vis-à-vis Encore to make it all work? I'm just not sure how much "magic" Encore does behind the scenes (does it parse the package.json to see what babel presets were installed or do you use install the preset and then use .configureBabel() but then duplicate the browserslist config in webpack.config.js)?

    Maybe I'm just confused with the different moving parts, but I'd really appreciate your tips and insights. :)

  • 2018-07-05 Steve

    Hi Ryan, sorry for not coming back to you sooner, works gone from mental to super mental ha ha. I gave up in the end as it was very much a nice to do rather than needed but thank you for supporting. I'm sure I'l be back soon with something else for you.

  • 2018-06-05 weaverryan

    Hey Steve!

    If you're comfortable sending any of your code over here to the comments (even though it's not Symfony), we'd be happy to take a look :).


  • 2018-06-05 Steve

    Hi Ryan, thank you for the reply. Amazing how easy it is in Symfony but after spending a few hours on trying to sort (not one to give up) I've given up. I suspect it is rather easy to do and I'm just missing a vital ingredient.

    Thanks again

  • 2018-06-04 weaverryan

    Hey Steve!

    Woohoo! Really glad to hear about your success! And, using Encore outside a Symfony project - *awesome*.

    So, Encore is totally independent of Symfony... except for the versioning part as you have figured out ;). Here's what happens:

    1) When you enable versioning in Encore, suddenly all of your filenames have hashes in them - like main.abc123.js
    2) The problem is... how can you create a script tag for a filename that is constantly changing? If you hardcode the main.abc123.js, then when that filename changes, your script tag is broken!
    3) To help solve this problem, Encore writes a manifest.json file in your build directory that is a map from the source filename (e.g. main.js) to the current hash filename (e.g. main.abc123.js). But, you need to make your backend code read this somehow.
    4) In Symfony, we built a small system that does this: if you enable that configuration in framework.yaml, when you say

    {{ asset('build/main.js') }}

    , it actually looks in the manifest.json file for this "build/main.js" key, and replaces it with hashed filename. This means that, in your final HTML, you always have the current, versioned filename.

    So, if you're outside of Symfony, you need to manually write some sort of function that you use when rendering your script/link tags that will go look at the manifest.json file and do the same replacement that Symfony is doing. You'll need to write this, but fortunately, it's pretty easy logic. But, let me know if you have questions!


  • 2018-06-04 weaverryan

    Matt Johnson Woohoo! Thanks for the nice note Matt!

  • 2018-06-03 Matt Johnson

    I am super impressed with Encore. Webpack seemed so complex/difficult to manage in the last tutorial. Encore makes it a breeze.

    Thanks for another great tutorial!

  • 2018-06-02 Steve

    Hi Ryan

    I've been following along and loving the series. You've got me using PHPStorm too which is awesome. Heading on to the doctrine tuts next and really looking forward to them.

    I am also using WebPack Encore in a none Symfony project and up until this tutorial was doing fine. Now I've added the versioning it breaks as there is no config/packages/framework.yaml and even adding it it doesn't work.

    Are you able to assist in how I would get this working? Do I need to reference the framework.yaml somewhere?


  • 2018-04-04 weaverryan

    Hey Allen!

    > I guess what Im asking is if encore/webpack has some features for this, or will i just have to import each library into each file for now?

    Ah yes, I understand now - and I get this question with some frequency. Short answer: yes, you need to require/import a module in each file that you need to use it. It's not a Webpack/Encore thing, it's more of a "this is how you're supposed to code in Node" thing. I would make a parallel to using namespaces in PHP: it's more-or-less just "how you code" in PHP, and everything works by using them :).

    Longer answer, yes, the autoProvideVariables() *does* actually make this possible. But, here's how it works under the hood. If you added swal to autoProvideVariables(), then whenever Webpack sees an uninitialized swal variable anywhere in your code, it (basically( adds const swal = require('sweetalert2') right above that line. In other words, you DO need require/import statements.... but this trick is a way for Webpack to "fix" your code for you ;).


  • 2018-03-31 Allen

    Yes exactly, something like sweet alert that exports the variable for each instance (&& LoDash) im guessing would need to autoProvideVariable but the problem is something like sweetalert is only used in every couple of files, and Lodash has the possibility of being used in many files.

    As well i have things like Bloodhound and typeahead that i want to use in many parts of my application. Instead of adding it to each file. One exports a variable (Bloodhound) and the other does not and should be global (typeahead) but neither work outside the file. I guess what Im asking is if encore/webpack has some features for this, or will i just have to import each library into each file for now?

    Thanks so much for the blog info! Ill be checking stuff out almost weekly. This is awesome and such a great tool, love your videos, so awesome!

  • 2018-03-21 weaverryan

    Hey Allen!

    Haha, you just made my morning! :D :D

    > You should do one on the autoProvideVariables function since it pretty much helps auto add jquery and bootstrap (popper,js), for all your "applications" or files. Had to do some research for that feature.

    I didn't cover this, but we did cover `autoProvidejQuery()`, which is just an alias that auto-provides jquery... which is what you need 99% of the time. But, I didn't think about popper.js for Bootstrap. We may even need/want to add an `autoProvidePopper` method, because that's probably going to be a really common use-case.

    > Also is there a way to do this with sweetalert2? Or something like this that someone might need on all pages like the bootstrap button feature.

    What do you mean exactly? Do you just want to be able to use the `swal` variable without requiring it in each file? Or, something different?

    > Also where might we find a list of upcoming, new, non-built-in, and cool features that are available??? Will it be updating on the Symfony site?

    Ah, cool question! For the big features, you'll probably see something on the blog. The features are hidden in the issue list - which is a bit large right now - The big ones include Webpack 4 support (Webpack 4 is supposed to have MUCH faster builds), hopefully HMR support for CSS (this is when your CSS styles reload on the page without refreshing), possibly a "generator" so that you can generate starting React/Vue apps... and probably a few other things. Fun stuff - just need to fine some time :).


  • 2018-03-21 Allen

    This is AMAZING!!!!! Been waiting for this tutorial for about 4 months. Thanks so much Ryan, this is sickkkkk. Especially without knowing about the buildNotification feature.
    You should do one on the autoProvideVariables function since it pretty much helps auto add jquery and bootstrap (popper,js), for all your "applications" or files. Had to do some research for that feature.
    Also is there a way to do this with sweetalert2? Or something like this that someone might need on all pages like the bootstrap button feature.

    Also where might we find a list of upcoming, new, non-built-in, and cool features that are available??? Will it be updating on the Symfony site?