Why grunt? Why not something else?

|

Recently, Miller Medeiros wrote a blog post called Node.js, Ant, Grunt and other build tools where he outlined some of his concerns about JavaScript build tools, focusing specifically on grunt, the task-based JavaScript build tool I’ve been developing.

Instead of posting a super-long comment on his blog, I figured I’d respond to his comments here.

Configuration over scripting

Miller says:

Descriptive languages aren’t a good way to solve arbitrary needs, configuration options never fit all scenarios. It doesn’t matter if you use XML/JSON/JS, it still won’t cover them all. If every edge-case requires a new setting you will soon end up with a highly complex system that is hard to understand and maintain.

When using grunt, I found that I was doing similar things repeatedly on different sets of files, with slightly different options, for many common tasks. So I abstracted those things into a simple pattern called multi tasks which seems to offer a good balance for both task authors and users.

And grunt favors the “configuration” approach for tasks for one simple reason: people seem to prefer a more declarative “configuration” style approach to a “scripting” approach.

Not everyone feels this way, of course, but you don’t have to use the configuration approach. Just register your own tasks that run some code. Each task is just a JavaScript function, that can either utilize configuration data through the grunt API or not.

Plugins / proprietary tasks

Miller says:

That for me is a huge mistake. I said it before and I will say it again STOP WRITING PLUGINS!. Even if all the tasks are indeed related to a common goal (helping the dev/deploy/test automation) you are still tying your solution to a single tool/platform. I’m not saying you shouldn’t reuse other modules, NPM is great at handling conflicting dependencies and complex dependency graphs, so abuse it. There is no reason why a helper function (concat, lint, minify, etc) wouldn’t work on multiple build tools, so why lock it to a single task runner? Why bundle it with the task runner / arguments parser? Why wrap every single node.js tool into a plugin for your build system? Why put all the “helpers” inside the same namespace when you have a real module system?

The intention for plugins and proprietary tasks is that they are simply a grunt-compatible wrapper around an existing lib. For example, the built-in lint task is a wrapper around the node-jshint lib. Of course, you can use that lib programatically in standard Node.js scripts, but if you use the grunt task you get grunt-style logging, consistent file globbing, etc.

I think you’ll find that most grunt plugins aren’t much more than grunt-compatible lib wrappers that make use of the grunt API. At least, that has always been the hope.

And for what it’s worth, one of grunt’s long-term goals is to abstract out the core lib and utlities so they can be used piecemeal, as libs, as people need them. What form this takes will become more clear over time, as the project evolves.

Global runner

Installing the build tool globally is a mistake. What if changes on the tool aren’t backwards compatible and you don’t remember which version you were using before? What if different projects requires different versions of the build tool? What if a future dev doesn’t have admin rights so he can’t install it globally? What if a future dev is on Windows and don’t know that he needs to set the environment PATH variable to be able to locate global commands installed with NPM?

Starting in grunt 0.4.0 (yet to be released) any globally-installed grunt will defer to a locally-installed grunt, if it has been installed locally, relative to the gruntfile. This will allow the user to run grunt globally (for things like project initialization) or use a project-specific local version simply by having it installed locally. The global version won’t be required, just convenient.

It was also a mistake to name the “gruntfile” with the same name as the grunt command since on windows the system will try to open the js file instead of running the command (unless you configure it to not do it).

Agreed! After much discussion, in 0.4.0 the gruntfile has been renamed from grunt.js to Gruntfile.js, which works around that whole Windows-trying-to-run-the-gruntfile issue (there’s actually a FAQ entry for this).

Basically, these are a few of the things that I wish I’d addressed before releasing 0.3.x, but at least they’ll be fixed moving forward.

Also, if you want to try 0.4.0a in a project, check out the When will I be able to use in-development feature ‘X’? FAQ entry which explains what’s involved. It’s actually not difficult, and I would love feedback from people using 0.4.0a.

Reinventing the “same” wheel

Miller says:

OK, I really like to reinvent the wheel, but in some cases that isn’t the smartest decision one can make. Specially when your wheel doesn’t work as good as an existing one and it consumes more time than you are willing to spend.

As I said in the original Introducing Grunt blog post:

After a lot of experimentation and failed attempts, I learned that writing and maintaining a suite of “javascript build process” tasks in a gigantic, monolithic Makefile / Jakefile / Cakefile / Rakefile / ?akefile that was shared across all my projects was overwhelming, especially considering how many projects I have. That approach just wasn’t going to scale to meet my needs.

In the end, I realized that a task-based build tool with built-in, commonly used tasks was the approach that would work best for me. Unfortunately, I couldn’t find a build tool that worked the way that I wanted. So I built one.

Miller continues with:

Does it really make sense to every single build tool to re-implement arguments parsing since libs like nopt, commander.js and node-optimist already exists? Does it really make sense to have a rigid structure for you build tasks since you can just reuse one of these option parsers, write a few functions and glue together some other independent modules?

Grunt makes use of many pre-existing Npm modules, nopt being one of them. Just take a look at the “dependencies” section of grunt’s package.json file and see for yourself.

Basically, I don’t reinvent the wheel unless the existing wheel doesn’t fit my axle. For example, while grunt uses nopt to handle parsing command-line args, all of grunt’s options are abstracted into a model that’s better suited to the way grunt utilizes that data. But nopt does all the heavy lifting.

Grunt uses async for asynchronous function queue processing, dateformat for formatting dates and temporary for creating temporary files.

But on the other hand, I wanted synchronous globbing, and the excellent glob was async-only at the time… so I wrote my own. I even called it glob-whatev so that people wouldn’t take it too seriously. Of course, I’d love to switch to glob now that it supports synchronous globbing, but there’s at least one issue that’s blocking me from using it.

Either way, in case it’s not obvious, I’m a huge fan of letting other people do the work.

In Summary

Feedback about grunt is always welcome. I’m usually in the #grunt channel on irc.freenode.net and there’s a fairly active issues tracker.

And there have already been a number of invaluable community-initiated discussions that have led directly to new features and behavior, so be sure to join the discussion.

Post A Comment

  • Any of these HTML tags may be used for style: a, b, i, br, p, strong, em, pre, code.
  • Multi-line JavaScript code should be wrapped in <pre class="brush:js"></pre>
    (supported syntax highlighting brushes: js, css, php, plain, bash, ruby, html, xml)
  • Use &lt; instead of < and &gt; instead of > in the examples themselves.