3

Common practice with .js and .css file minification is to create .min.js and .min.css files. The problem is Shake FilePatterns will match both minified and non-minified files with this scheme with a pattern like //*.js. The docs seem to recommend making unique extensions for each like reg.js and min.js but I'm not sure this is a path I want to take. How can this be worked around in Shake?

Edit: The problem is with using getDirectoryFiles:

js <- getDirectoryFiles "" ["//*.js"]
let jsMins = map (-<.> "min" <.> "js") js
need jsMins

There's a target for "//*.min.js that works just fine but getDirectoryFiles "" ["//*.js"] starts picking up the generated .min.js files.

Luke Hoersten
  • 8,355
  • 3
  • 21
  • 18
  • You need a pattern that both rejects and accepts files based on their names (ie: a regex like `^.*(?<!\.min)\.js$`). I'm pretty sure this can't be done with Shake FilePatterns since they only admit, but not reject files based on name. Perhaps you can prefilter the list of files to exclude those with `".min."` in their name. – Alain O'Dea Mar 29 '14 at 19:40
  • @AlainO'Dea is correct, the Shake FilePattern can't reject paths based on their name - it is a very restricted form of matching language. – Neil Mitchell Mar 29 '14 at 19:42
  • @AlainO'Dea do you know how I can prefilter the pattern being sent to `getDirectoryFiles`? That's where the real problem is. – Luke Hoersten Mar 29 '14 at 22:07
  • @LukeHoersten you can't pre-filter calls to `getDirectoryFiles` since takes a directory name and the `FilePattern`s and performs the lookup and filtering internally. You can post-filter the result of `getDirectoryFiles` just as you have shown in your edit above though :) – Alain O'Dea Apr 01 '14 at 15:02

2 Answers2

2

There are several approaches to writing different generating rules for .js vs .min.js:

  1. If you aren't ever generating .js or .css files, only using existing source files, and thus don't need //*.js rules, then just make your minimisation pattern be //*.min.js and it will work.

  2. If you are generating .js (e.g. from CoffeeScript files), then if you are generating the .min.js and .js files in different directories you can distinguish the patterns at that level, e.g. dist/*.min.js, decafinate/*.js.

  3. Use ?> patterns, for example:

    (\x -> takeExtensions x == ".js") ?> \out -> ...

    Note that takeExtensions (with the trailing s), that will only match files whose complete extension is .js, so won't match myfile/test.min.js.

If solution 1 works, use that (it's simplest). If solution 2 naturally falls out (e.g. that matches what you wanted to do anyway) it can be easier to have more restrictive patterns, but option 3 "just works" everywhere so is probably what I'd do.

Note that p *> a is defined as (p ?==) ?> a (plus a lot of extra plumbing for good error messages!), so ?> isn't anything special and works happily with your existing rules. I usually find about 5-10% of my rules use ?> patterns.

Neil Mitchell
  • 9,090
  • 1
  • 27
  • 85
  • #1 wont work for me because I use `js <- getDirectoryFiles "" ["//*.js"]` to get my js files, modify the names to `.min.js` then `need` them so the `//*.min.js` target runs. the problem is the getDirecoryFiles starts detecting the `.min.js` files. #3 might work though. – Luke Hoersten Mar 29 '14 at 21:59
  • I think `getDirectoryFiles` might need the equivalent of `?>` for my issue to be resolved. – Luke Hoersten Mar 30 '14 at 02:44
  • I've given a few `getDirectoryFiles` alternatives in another answer. You can't define `getDirectoryFiles` with an arbitrary predicate because that function runs when checking if a build is clean, so can't access functions inside rules. In contrast `?>` only runs when something is dirty, but even then it's not inside rules, but at the top level, making it easy to get at the function. – Neil Mitchell Mar 30 '14 at 18:57
2

I can think of two alternative approaches:

Post process getDirectoryContents

Call getDirectoryContents then filter out the values you don't care about. Something like:

xs <- getDirectoryFiles "" ["//*.js"]
need [x -<.> "min.js" | x <- xs, takeExtensions x == ".js"]

If you are running this code outside of a rule, e.g. in an action, then it is 100% correct. If you are running the code in a rule then the rule will run once, new files will appear, and then the rule will run again (which is also a lint violation, and not great build hygine). I suspect you are running outside of a rule, so this approach is probably a good one.

Write an Oracle

If you are inside a rule, you can define an Oracle which is like getDirectoryFiles, but skips the min files.

newtype JsFiles = JsFiles () deriving (Show,Typeable,Eq,Hashable,Binary,NFData)
addOracle $ \(JsFiles _) -> fmap (\x -> takeExtensions x == ".js") $ getDirectoryFiles "" ["//*.js"]

Now you can use askOracle $ JsFiles () in your rule, and it will only change when new non-min files are added.

Neil Mitchell
  • 9,090
  • 1
  • 27
  • 85
  • So the key in the first approach is to run the getDirectoryFiles outside of a rule. I'm currently running it inside of a rule. That's my problem. Thanks a lot! Edit: I'm not sure I'm not in a rule. The `getDirectoryFiles` is on the right side of `*>` in this case. – Luke Hoersten Mar 30 '14 at 19:20
  • 1
    Right hand side of `*>` is in a rule, so I recommend using the Oracle approach. – Neil Mitchell Mar 30 '14 at 20:03
  • Looks like the oracle approach is having the same problem as the getDirectoryFiles approach. The linter is complaining the files are changing and it's giving inconsistent behavior between runs. Really what I need is a way to track `.js` files without tracking `.min.js` files. There must be an easier way. – Luke Hoersten Apr 05 '14 at 04:00