Search code examples
haskellshake-build-system

Shake: how to proceed when file name is not known a-priori


I'm trying to set up Shake for building a web application. At the end of my build process I would like to rename the resulting .js and .css files according to a content hash (for cache busting purposes).

This means of course that I do not know the final names of the files which look something like app-<hash>.min.js.

Since the rule for building the final index.html needs to know about the names, I would need something like

"index.html" %> \out -> do
  need [ "app-<hash>.min.js" ]
  ...

I've tried using a phony action along the lines of

"index.html" %> \out -> do
  need [ "revJsAndCssFiles" ]
  ...

phony "revJsAndCssFiles" $ do
  ... -- Hash and copy files to appropriate locations

This works but seems ugly since a phony action isn't supposed to produce a file.

What would be the best way to approach this problem?


Solution

  • Will the index.html need to refer to the hashed Javascript files? If so, one approach is:

    "hash-js.txt" %> \out -> do
        src <- readFile' "app.min.js"
        let file = "app-" ++ show (hash src) ++ ".min.js"
        writeFile' file src
        writeFile' out file
    
    "index.html" %> \out ->
        js <- readFile' "hash-js.txt"
        writeFile' out $ "<script src='" ++ js ++ ">"
    

    Here you have a constantly named rule (hash-js.txt) which both produces the file you want (at an unknown location), and the name of that file (to a fixed location). That way when you need the file from index.html you both depend on it and get the resulting filename out, which is likely to be required.

    This trick works equally well if you don't need the filename in index.html - then you just throw away the contents of the file - but it feels a bit uglier.