43

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?

Top-Master
  • 7,611
  • 5
  • 39
  • 71
machineghost
  • 33,529
  • 30
  • 159
  • 234
  • Your desired technique of adding a query param to the same file when its contents change will not result in cache busting. See: https://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/ – Butifarra Oct 20 '16 at 16:50
  • 1
    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. – machineghost Oct 20 '16 at 20:50
  • 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. – Butifarra Oct 29 '16 at 23:02
  • 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. – boatcoder Nov 27 '16 at 16:09
  • 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. – Ringo Aug 09 '21 at 18:18

6 Answers6

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

Community
  • 1
  • 1
Everettss
  • 15,475
  • 9
  • 72
  • 98
  • 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? – machineghost Sep 08 '16 at 17:32
  • can it append instead of `chunk.1.js?cacheBust=12345` little different syntax: `chunk.1.12345.js`? – Everettss Sep 08 '16 at 18:25
  • 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). – machineghost Sep 08 '16 at 18:43
  • You will have to find plugin for modify `jsonp` webpack loader. I don't have knowledge to do this. – Everettss Sep 08 '16 at 20:25
  • @Everettss How to add hashes to images under assets folder? – Vikas Dec 30 '18 at 06:49
  • @Vikas use `file-loader` https://github.com/webpack-contrib/file-loader by default it converts original filenames to hashes. – Everettss Dec 30 '18 at 11:44
  • 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? – tRuEsAtM Aug 26 '21 at 22:34
23

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')
}
VizardCrawler
  • 1,343
  • 10
  • 16
  • 1
    How do you use this filename in html's script src= ? Thanks! – xims Oct 09 '18 at 00:11
  • 1
    @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 though – VizardCrawler Oct 09 '18 at 11:15
  • 3
    Sorry I wasn't clear. At the moment, the html code is . 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? – xims Oct 09 '18 at 22:17
  • 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. – Andreas Mar 26 '19 at 22:01
  • @xims 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. – Ashnur Oct 28 '19 at 07:20
  • 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]"` – Juan Solano Jan 25 '20 at 01:53
  • 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? – Raju Oct 29 '20 at 10:27
  • This is one of the weird hacks I've seen around, and I'm speaking from Webpack version 5. – Hyfy Apr 18 '21 at 12:04
16

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

Cichy
  • 4,602
  • 3
  • 23
  • 28
  • 1
    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 html-webpack-plugin@2.30.1 --save-dev --save-exact` to lock that compatible package. Works like a charm now, thanks! – TaeKwonJoe Apr 11 '21 at 15:19
  • 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 :/ ). – Top-Master Mar 12 '22 at 05:01
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/

Hyfy
  • 301
  • 2
  • 11
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.

Giorgio Tempesta
  • 1,816
  • 24
  • 32
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.

pekaaw
  • 2,309
  • 19
  • 18