Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Function to transform asset file names for cache busting #2927

Open
rightaway opened this issue May 6, 2023 · 3 comments
Open

Function to transform asset file names for cache busting #2927

rightaway opened this issue May 6, 2023 · 3 comments

Comments

@rightaway
Copy link

There's no way to do cache busting based on file content hash without hacks or using a separate process or external dependency. Some of those options are in #272.

It's actually quite simple to do like in https://github.com/keithamus/postcss-hash, but it's not possible to hook into eleventy's file outputting. Can we have a function like this?

eleventyConfig.transformAssetFileNames({
  globs: ["**/*.png", "asset/data.json", "**/*.jpg"],
  hashAlgorithm: "sha256",
  hashLength: 10,
  name: (dirs, name, hash, ext) => `${path.join(...dirs)}/${name}-${hash}.${ext}`,
  output: "_data/manifest.json",
})

That would output _data/manifest.json

{
  "dir1/dir/file.png": "dir1/dir/file-1234567890.png",
  "asset/data.json": "asset/asset-1234567890.json",
  "dir2/dir/file.jpg": "dir2/dir/file-1234567890.jpg"
}

It would make cache busting very simple to do with an asset filter which would get the transformed file name from the file in output (_data/manifest.json). Then <img src={{ "dir1/dir/file.png" | asset }}> will become <img src=dir1/dir/file-1234567890.png>.

@rightaway
Copy link
Author

Eleventy doesn't even need to output a physical manifest.json file for this. output key in the function argument isn't needed.

@puleddu
Copy link

puleddu commented Jun 29, 2023

I stumbled upon many conversations and blog posts regarding styles and javascript (i.e. global assets) cache-busting, but not very much is being said about the issue you raise here: content assets, like images in a blog posts or an article.

Cache-busting for content assets is critical for things like documentation websites. I'm surprised nothing close to a best practice exists in that regard. Am I missing something, or is 11ty simply not meant to be used like that? Is there any solid resource I can refer to, in order to setup my configuration to provide cache-busting for content assets?

@davekerber
Copy link

I was able to do this with built in eleventy events using this.

My contents are in src/site and my output is to _site

const fs = require('fs');
const path = require('path');
const crypto = require('crypto');

module.exports = function(eleventyConfig) {

  // A cache to store the hashed file names
  const hashCache = {};

  // A cache buster if a file changes
  const prefixLength ="./src/site".length
  eleventyConfig.on('eleventy.beforeWatch', async (changedFiles) => {
    for(const file of changedFiles) {
      const relativePath = file.slice(prefixLength)
      delete hashCache[relativePath]
    }
  });

  // A filter to dynamically hash asset file contents
  eleventyConfig.addFilter("digest", async (filePath)  => {
    // If we've already hashed this file, return the hash
    if(hashCache[filePath]) {
      return hashCache[filePath];
    }

    // Get the absolute path to the file inside of src/site
    const absolutePath = path.join(__dirname, 'src/site', filePath);

    // Digest the file
    const fileBuffer = fs.readFileSync(absolutePath);
    const hash = crypto.createHash('sha256').update(fileBuffer).digest('hex');
    const relativePath = filePath.slice(0, path.basename(filePath).length * -1)
    const digestFileName = `${relativePath}${hash}-${path.basename(filePath)}`;

    // See if the digest file exists in the output folder _site
    const digestFilePath = path.join(__dirname, '_site', digestFileName);
    hashCache[filePath] = digestFileName;
    if(!fs.existsSync(digestFilePath)) {
      if(!fs.existsSync(path.dirname(digestFilePath))) {
        fs.mkdirSync(path.dirname(digestFilePath), { recursive: true });
      }
      fs.copyFileSync(absolutePath, digestFilePath);
    }
    // Return the digest file name
    return digestFileName;
  })

  // other stuff

  return {
    dir: {
      input: "src/site", // source files
      output: "_site", // destination folder
      includes: "_includes", // folder for layouts and includes
      data: "_data" // folder for data files
    }
  };
};

Then I digest assets like this:

<link href="{{"/css/style.css" | digest}}" rel="stylesheet">

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants