How to use Cache-Busting with Webpack?
Asked Answered
R

6

45

Before Webpack I would always rely on the following pattern for "cache-busting":

<script src="foo.js?cacheBust=12345" />

where 12345 was a token the sever generated for me on every build (it could be a Git hash, although in my case it isn't).

With Webpack I now have two files: build.js and chunk.1.js. Since I bring the first one in with a normal script tag I can use the above pattern:

<script src="build.js?cacheBust=12345" />

However, at that point build.js goes and fetches chunk.1.js, and when it does it doesn't include the cache-busting suffix.

I would like for Webpack to automatically append the ?cacheBust=12345, but I don't know the 12345 part at build time, so I can't include it in my webpack.config. Instead, I have to wait until the HTML page is evaluated, at which point I get the token from the server.

So, my question is, is there any way to have Webpack look at the parameter used to fetch the initial file (eg. ?cacheBust=12345) and append that same parameter when fetching other files?

Raphaelraphaela answered 30/8, 2016 at 22:40 Comment(5)
Your desired technique of adding a query param to the same file when its contents change will not result in cache busting. See: stevesouders.com/blog/2008/08/23/…Hacksaw
I think you may have read that article too quickly: it does not say that the technique doesn't work. What it does say is that the technique will not work if you use a Squid proxy as Steve Souders did. How a server handles a request is entirely up to the server, but in my experience most major server-side frameworks, as well as most webservers (eg. Apache) do treat foo.png and foo.png?v=1 differently.Raphaelraphaela
The point of the article is to highlight the fact that any proxy server that may exist between your client and your server will most likely discard the query param and check for a cached version of the file. When this is true on the proxy, your new version of the file will not be retrieved and the user will see the older cached version.Hacksaw
If you review part 1 @Everettes answer, it does answer your question. The chunkFilename using a [chunkhash] is the best way to bust the cache. If you are storing those files in git, you can git rm the files, do the build, then git add. Those files where the hash did not change will simply be "restored" and those where the hash did change will be gone. Think about the solution, not the mechanism.Frausto
This is an old argument, but Souders' infamous blog post got things quite wrong. Cache-busting with a querystring was much more effective than he had thought.Hotblooded
B
24

You can simply do this

output: {
    filename: '[name].js?t=' + new Date().getTime(),
    chunkFilename: '[name]-chunk.js?t=' + new Date().getTime(),
    publicPath: './',
    path: path.resolve(__dirname, 'deploymentPackage')
}
Bremerhaven answered 13/9, 2017 at 14:30 Comment(8)
How do you use this filename in html's script src= ? Thanks!Deform
@xims, you can directly give reference to the html , the chunks will be named as 0-chunk.js, you just need to refer it, I am wondering why you want to give the chunk reference thoughBremerhaven
Sorry I wasn't clear. At the moment, the html code is <script src="app.js"></script>. If I'm changing webpack's output.filename to app.js?t=123, how do I use that dynamic filename in the html's script tag?Deform
following the answer of @Everettss, you could porbably also use the pattern [name]-chunk.js?t=[hash] - this gives you still a readable filename and should only changes the hash, if the file changed.Rus
@Deform answer is actually quite relevant for firebase users who don't always have the easy option to use a template or similar solution to inject the id into the html. Webpack is only half a solution to cache busting. And using date and .getTime() instead of content hashing is a terrible advice.Amorphous
I'm using some extracting plugins and the date would never change for me. What I ended up doing was this: chunckFilename: "[name].chunk.js?t=[chunkhash:5]"Downhill
I added it like this filename: isEnvProduction ? static/js/[name].js?t=${new Date().getTime()} : isEnvDevelopment && 'static/js/bundle.js', chunkFilename: isEnvProduction ? static/js/[name].${env.raw.VERSION}.chunk.js?t=${new Date().getTime()} : isEnvDevelopment && static/js/[name].${env.raw.VERSION}.chunk.js, But the application stops working after build is created. Can anyone help on this?Bushranger
This is one of the weird hacks I've seen around, and I'm speaking from Webpack version 5.Palatinate
B
58

If you would like to achieve cache busting in "webpack way":

1. Hash name of output files

Change output filenames to hash generated names (on build phase)

output: {
    path: '/',
    filename: '[hash].js',
    chunkFilename: '[chunkhash].js',
},

From that point your foo.js and chunk.1.js will be called like e883ce503b831d4dde09.js and f900ab84da3ad9bd39cc.js. Worth mention that generation of this files are often related to making production and time too update cacheBust value.

2. How to include not known names of files?

Since now your foo.js - main file is named in not known way. To extract this name of file you can use AssetsPlugin

const AssetsPlugin = require('assets-webpack-plugin');
const assetsPluginInstance = new AssetsPlugin();

and add this plugin to webpack.config.js

plugins: [
    assetsPluginInstance
]

In webpack-assets.json file you should see something like

{
    "main": {
        "js": "/e883ce503b831d4dde09.js"
    }
}

You can use this file to point to main .js file. For more details read this answer

3. Benefit time

I guess that if you make app production because of modification of chunk.2.js file, you change files paths from

- build.js?cacheBust=12345
- chunk.1.js?cacheBust=12345
- chunk.2.js?cacheBust=12345
- chunk.2.js?cacheBust=12345

to new ones

- build.js?cacheBust=12346   // modified referation to chunk.2.js file
- chunk.1.js?cacheBust=12346
- chunk.2.js?cacheBust=12346 // modified
- chunk.2.js?cacheBust=12346

If you would use above solution you will get free cache determination. Now filles will be called like

(previous production)

- e883ce503b831d4dde09.js
- f900ab84da3ad9bd39cc.js
- 5015cc82c7831915903f.js
- 8b6de52a46dd942a63a7.js

(new production)

- c56322911935a8c9af13.js // modified referation to chunk.2.js file
- f900ab84da3ad9bd39cc.js
- cd2229826373edd7f3bc.js // modified
- 8b6de52a46dd942a63a7.js

Now only main file and chunk.2.js names are changed and you will get this for free by using webpack way.

You can read more about long term caching here https://medium.com/webpack/predictable-long-term-caching-with-webpack-d3eee1d3fa31

Barbecue answered 3/9, 2016 at 23:23 Comment(7)
Thanks. While I appreciate the explanation of "the Webpack way", you didn't actually answer my question. Is it impossible to make Webpack append ?cacheBust=12345 to the files it imports?Raphaelraphaela
can it append instead of chunk.1.js?cacheBust=12345 little different syntax: chunk.1.12345.js?Barbecue
Unfortunately no :( I'm trying to have the file have the exact same URL, from the server's perspective, every time, because there is only one file server-side. The "cache busting token" (cacheBust=12345) needs to come after the ? so that the client considers it part of the URL (and thus busts the cache) but the server ignores it (and just sees the actual file path every time).Raphaelraphaela
You will have to find plugin for modify jsonp webpack loader. I don't have knowledge to do this.Barbecue
@Barbecue How to add hashes to images under assets folder?Karakorum
@Karakorum use file-loader github.com/webpack-contrib/file-loader by default it converts original filenames to hashes.Barbecue
Does this mechanism ensure that, let's say I have a label = "My Text" and then I update it to "My Updated Text" then build the solution. And just hit Ctrl + R, should I be able to see "My Updated Text" as a label?Irrespective
B
24

You can simply do this

output: {
    filename: '[name].js?t=' + new Date().getTime(),
    chunkFilename: '[name]-chunk.js?t=' + new Date().getTime(),
    publicPath: './',
    path: path.resolve(__dirname, 'deploymentPackage')
}
Bremerhaven answered 13/9, 2017 at 14:30 Comment(8)
How do you use this filename in html's script src= ? Thanks!Deform
@xims, you can directly give reference to the html , the chunks will be named as 0-chunk.js, you just need to refer it, I am wondering why you want to give the chunk reference thoughBremerhaven
Sorry I wasn't clear. At the moment, the html code is <script src="app.js"></script>. If I'm changing webpack's output.filename to app.js?t=123, how do I use that dynamic filename in the html's script tag?Deform
following the answer of @Everettss, you could porbably also use the pattern [name]-chunk.js?t=[hash] - this gives you still a readable filename and should only changes the hash, if the file changed.Rus
@Deform answer is actually quite relevant for firebase users who don't always have the easy option to use a template or similar solution to inject the id into the html. Webpack is only half a solution to cache busting. And using date and .getTime() instead of content hashing is a terrible advice.Amorphous
I'm using some extracting plugins and the date would never change for me. What I ended up doing was this: chunckFilename: "[name].chunk.js?t=[chunkhash:5]"Downhill
I added it like this filename: isEnvProduction ? static/js/[name].js?t=${new Date().getTime()} : isEnvDevelopment && 'static/js/bundle.js', chunkFilename: isEnvProduction ? static/js/[name].${env.raw.VERSION}.chunk.js?t=${new Date().getTime()} : isEnvDevelopment && static/js/[name].${env.raw.VERSION}.chunk.js, But the application stops working after build is created. Can anyone help on this?Bushranger
This is one of the weird hacks I've seen around, and I'm speaking from Webpack version 5.Palatinate
V
17

You can use HtmlWebpackPlugin

Description from webpack.js.org/plugins/html-webpack-plugin:

... plugin simplifies creation of HTML files to serve your webpack bundles. This is especially useful for webpack bundles that include a hash in the filename which changes every compilation...

Part of my webpack.config.js:

// ...
const HtmlWebpackPlugin = require('html-webpack-plugin');
// ...
module.exports = {
   // ...
   plugins: [
      new HtmlWebpackPlugin({
         template: './assets/index.html',
         hash: true,
      }),
      // ...
   ]
};

If hash: true then append a unique webpack compilation hash to all included scripts and CSS files. This is useful for cache busting.

More about HtmlWebpackPlugin options on github.com/jantimon/html-webpack-plugin

Thanks to this option I got output html file with:

<!DOCTYPE html>
<html>
   <head>
      <!-- ... rest of my head code ... -->
      <link href="./css/styles.css?f42fdf96e2f7f678f9da" rel="stylesheet">
   </head>
   <body>
      <!-- ... rest of my body code ... -->
      <script type="text/javascript" src="./js/index.bundle.js?f42fdf96e2f7f678f9da"></script>
   </body>
</html>

Source code of my project: github.com/cichy380/html-starter-bs4-webpack

Valina answered 13/2, 2019 at 14:40 Comment(2)
This did not work with v5.3.1. Instead I looked up the latest v2 by running npm view html-webpack-plugin@* version, saw 2.30.1 was the latest for that major version, then ran npm i [email protected] --save-dev --save-exact to lock that compatible package. Works like a charm now, thanks!Carthy
For Laravel first install webpack-blade-native-loader, which complies Blade to HTML, and supports running HtmlWebpackPlugin on top of the resulting HTML files (but only useful if your HTML is not dynamic, and does not change like per user, which usually is not the case :/ ).Killian
P
4

The following is for Webpack v5

webpack.config.js

module.exports = {
    // ...
    output: {
        filename: "[name].bundle.[chunkhash].js",
        path: path.resolve(__dirname, "dist"),
        assetModuleFilename: "images/[hash][ext][query]"
    },
    // ...
    optimization: {
        moduleIds: "deterministic",
    }
    // ...
}

It is important to use a hash, such as, [chunkhash].

Note, there's more than one way to do it.

Source: https://webpack.js.org/guides/caching/

Palatinate answered 18/4, 2021 at 12:13 Comment(0)
B
3

If you want the hash in the format name.js?cacheBust=1234 in Webpack you can do:

output: {
  filename: '[name].js',
  chunkFilename: '[name].js?cacheBust=[chunkhash]',
},

I'm doing something very similar in Webpack using Laraval Mix, I have found the answer in this Github issue: https://github.com/JeffreyWay/laravel-mix/issues/2131.

Balsamic answered 19/6, 2020 at 10:42 Comment(0)
H
1

There are many ways to do cache busting, also with webpack. I use the suffix technique quite often myself and came to this post looking for answers. I use the chunks differently than OP, since I only reference the chunk within javascript that runs through webpack:

const componentPromise = import(/* webpackChunkName: "myDelayedSource" */ "path/to/myComponent/myComponent.js");

The solution I found for cache busting (within js) is simply to change the name of the chunk:

const componentPromise = import(/* webpackChunkName: "myDelayedSource_2020-05-14" */ "path/to/myComponent/myComponent.js");

This will not give a file suffix, but it will change the name of the file and the script referencing the file will also use the new file name.

I hope this can help someone.

Heriberto answered 14/5, 2020 at 11:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.