'How to do cache busting in Rollup.js?

In my project, I need to do cache busting, since after a new deploy, the browser often only reloads the HTML but not the JS & CSS files.

Currently, I am not building the HTML in any way, it just already sits in the public directory.

The simplest approach seems to be to add a timestamp to the JS reference:

<script type="module" src="bundle/index.js?ts=20201026-102300"></script>

Now, what is the best way to achieve this in a project that already uses rollup.js?

I have seen @rollup/plugin-html, yet I'm puzzled by the example in its documentation, as it takes a JS file as input:

 input: 'src/index.js',

What JS file should that be?

Instead I expect that need to define

  • an input HTML file
  • some space for code to set the timestamp variable
  • an output HTML file

So what's the best way to do this, be it with @rollup/plugin-html or with another approach?



Solution 1:[1]

Came here looking for an answer to this question myself and a few moments later and a bit of regex fiddling, I got it to work.

Note: this solution edits your HTML file each time you build it. There is no input (template) HTML and output HTML.

  1. Install rollup-plugin-replace-html-vars

npm install rollup-plugin-replace-html-vars --save-dev

  1. Add this piece of config to your rollup.config.js file
// rollup.config.js
// ...
plugins: [
    replaceHtmlVars({
        files: '**/index.html',
        from: /\.\/\w+\/\w+\.\w+.\w+\?v=\d+/g,
        to: './dist/app.min.js?v=' + Date.now(),
    }),
]

  1. In your index.html, add this reference to the app.js:
<script type="module" src="./dist/app.min.js?v=1630086943272"></script>
  1. Run rollup and the reference to app.js in your index.html will have a timestamp of the build time each time you run it.

Bonus:

If you don't have a .min in your filename, use this regex instead:

/\.\/\w+\/\w+\.\w+\?v=\d+/g

Full disclosure; I'm no regex wizard, just managed to hack this one together. I bet someone here will have a better way of capturing ./dist/app.min.js?v=1630086943272 with a regex but this works for my solution.

Solution 2:[2]

I went with using file hashes, which means it's only reloaded when there is a new version for that file. For that, I wrote my own utility:

function escapeStringRegexp(string) {
    if (typeof string !== 'string') {
        throw new TypeError('Expected a string');
    }

    return string
        .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
        .replace(/-/g, '\\x2d');
}

function insertHashToFile(options) {
    return {
        writeBundle(outputOptions) {
            const outputDir = outputOptions.dir ? outputOptions.dir : path.dirname(outputOptions.file);
            let indexHtml = fs.readFileSync(options.htmlFile, 'utf8');

            for (const sourceFile of options.sourceFiles) {
                const fb = fs.readFileSync(path.join(outputDir, sourceFile));
                const hash = crypto.createHash('sha1');
                hash.update(fb)
                const hexHash = hash.digest('hex');

                const replacePattern = new RegExp(escapeStringRegexp(sourceFile) + '(:?\\?h=[^"]+)?', 'g');
                indexHtml = indexHtml.replaceAll(replacePattern, `${sourceFile}?h=${hexHash.substring(0, 8)}`);
            }

            fs.writeFileSync(options.htmlFile, indexHtml);
        },
    };
}

and then

plugins: [
    production && insertHashToFile({
        sourceFiles: [
            "bundle.js",
            "bundle.css",
        ],
        htmlFile: "public/index.html",
    }),
]

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Robin van Baalen
Solution 2 h345k34cr