Hi! My name is Tim Branyen & I am a skydiver, an open source developer at bocoup, and a maintainer of open-source projects.

Monday, Jan 21, 2013

CoffeeScript has the ideal syntax for configurations


Disclaimer: Having a background in C-like languages like Java, JavaScript, PHP, and C# has caused a problem for me to embrace languages like Python, Ruby, and CoffeeScript.

I'd consider myself more of a laggard than a trend-setter when it comes to new technologies and languages. I've often questioned why? even when the answer is clear. This attitude has made me biased against CoffeeScript, even though it provides useful benefits and features that provide justification to why a developer would maintain a codebase with it.

Configuration use case


While I may not be a fan of coding with CoffeeScript, I'm absolutely hating writing configurations with JavaScript. There is so much unnecessary noise, the syntax and verbosity, that serves to distract from the purpose of a configuration which, in my opinion, should be bare bones and minimalist.

I have a few reasons as to why I'm switching my project configurations to CoffeeScript syntax:

  • Significantly more dynamic than YAML.
  • Doesn't look like code and is highly readable.
  • Easier to maintain lists and properties.
  • Can embed JavaScript, which is useful for non-CoffeeScripters.
  • Works seamlessly with Grunt...

Grunt.js integration


Using CoffeeScript for configurations in various tools may be frustrating. It's certainly not a ubiquitous language and doesn't directly integrate with projects whose build tool does not have an available parser. Like many others, however, I've adopted Grunt as my primary build tool. Grunt is super convenient for this, since it has seamless CoffeeScript support.

Simply name your Grunt configuration file: Gruntfile.coffee and proceed to write out your configuration the exact same way, except with CoffeeScript syntax instead of JavaScript.

Basic Grunt example


Here's a basic example of what a configuration file could look like to lint a project file.

CoffeeScript

# Create a new configuration function that Grunt can # consume. module.exports = -> # Initialize the configuration. @initConfig # Specify source files to the JSHint task. jshint: files: ["backbone.layoutmanager.js", "node/index.js"] # Allow certain options. options: browser: true boss: true # Load external Grunt task plugins. @loadNpmTasks "grunt-contrib-jshint" # Default task. @registerTask "default", ["jshint"]
JavaScript

// Create a new configuration function that Grunt // can consume. module.exports = function() { // Initialize the configuration. this.initConfig({ // Specify source files to the JSHint task. jshint: { files: ["backbone.layoutmanager.js", "node/index.js"], // Allow certain options. options: { browser: true, boss: true, } } }); // Load external Grunt task plugins. this.loadNpmTasks("grunt-contrib-jshint"); // Default task. this.registerTask("default", ["jshint", "custom"]); };
Comparison breakdown

My initial reaction is that the first example looks more typical of what I'd expect in a configuration file. This opinion is influenced by my previous usage of YAML, ini, Makefile, etc. configuration formats.

  • The CoffeeScript file has unique symbols: @ | -> that make little sense to a JavaScript developer.
    • We can redefine the semantics inside the configuration. In this case @ represents Grunt and -> starts the configuration.
  • The JavaScript file has unique symbols to C-style coding: ; | {} | ().
    • I find these to be distracting and unnecessary, especially since ideally we're not parsing complex logic.

It's worth noting that in both these cases the symbols could be minimized. I'm also opting for trailing commas since the Node.js runtime supports them.

Advanced Grunt example


One of the reasons for choosing CoffeeScript over YAML is that it's dynamic and compiles down to JavaScript. This means you can write functions inside of your configurations.

CoffeeScript

# Create a new configuration function that Grunt can # consume. module.exports = -> # Initialize the configuration. @initConfig # Specify source files to the JSHint task. jshint: files: ["backbone.layoutmanager.js", "node/index.js"] # Allow certain options. options: browser: true boss: true # Load external Grunt task plugins. @loadNpmTasks "grunt-contrib-jshint" # Running CasperJS behavioral tests. @registerTask "casper", "Execute CasperJS tests.", -> done = @async() grunt.util.spawn cmd: "casperjs" args: ["--ignore-ssl-errors=yes", "test", "test/casperjs"] , (error, result, code) -> if error grunt.log.write error.stdout done() process.exit code grunt.log.write result done() process.exit 0 # Default task. @registerTask "default", ["jshint", "casper"]
JavaScript

// Create a new configuration function that Grunt // can consume. module.exports = function() { // Initialize the configuration. this.initConfig({ // Specify source files to the JSHint task. jshint: { files: ["backbone.layoutmanager.js", "node/index.js"], // Allow certain options. options: { browser: true, boss: true, } } }); // Load external Grunt task plugins. this.loadNpmTasks("grunt-contrib-jshint"); # Running CasperJS behavioral tests. this.registerTask("casper", "Execute CasperJS tests.", function() { var done = this.async(); grunt.util.spawn({ cmd: "casperjs", args: ["--ignore-ssl-errors=yes", "test", "test/casperjs"] }, function(error, result, code) { if (error) { grunt.log.write(error.stdout); done(); process.exit(code); } grunt.log.write(result); done(); process.exit(0); }); }); // Default task. this.registerTask("default", ["jshint", "casper"]); };
Inline JavaScript

I personally do not like writing my functions in CoffeeScript, others may disagree, but luckily for others like me you can directly embed JavaScript inline.

An example of rewriting the above.

# Create a new configuration function that Grunt can # consume. module.exports = -> # Initialize the configuration. @initConfig # Specify source files to the JSHint task. jshint: files: ["backbone.layoutmanager.js", "node/index.js"] # Allow certain options. options: browser: true boss: true # Load external Grunt task plugins. @loadNpmTasks "grunt-contrib-jshint" # Running CasperJS behavioral tests. @registerTask "casper", "Execute CasperJS tests.", `function() { var done = this.async(); grunt.util.spawn({ cmd: "casperjs", args: ["--ignore-ssl-errors=yes", "test", "test/casperjs"] }, function(error, result, code) { if (error) { grunt.log.write(error.stdout); done(); process.exit(code); } grunt.log.write(result); done(); process.exit(0); }); }` # Default task. @registerTask "default", ["jshint", "casper"]

Alternative (YAML) Grunt example


While working on the Grunt examples and having Ben Alman review the post, he mentioned that I could use YAML and require that in for the declarative portions of the Grunt configuration.

YAML / config.yaml

# Specify source files to the JSHint task. jshint: files: - backbone.layoutmanager.js - node/index.js # Allow certain options. options: browser: true boss: true
Grunt / Gruntfile.js

// Create a new configuration function that Grunt // can consume. module.exports = function() { // Read in the configuration and parse it into an // Object. var config = this.file.readYAML("config.yaml"); // Initialize the configuration. this.initConfig(config); // Load external Grunt task plugins. this.loadNpmTasks("grunt-contrib-jshint"); // Default task. this.registerTask("default", ["jshint"]); };

Example without Grunt


If you are using your own build system that is only expecting an object, the configuration will look even better!

CoffeeScript

# Specify source files to the JSHint task. jshint: files: ["backbone.layoutmanager.js", "node/index.js"] # Allow certain options. options: browser: true boss: true
JavaScript

{ // Specify source files to the JSHint task. jshint: { files: ["backbone.layoutmanager.js", "node/index.js"], // Allow certain options. options: { browser: true, boss: true, }, }, }

Decide for yourself


Which do you think is easier to read and update? Let me know in the comments if you see any flaws in my approach and thought process. My stance is to use CoffeeScript wherever possible over JavaScript for configurations, and to inline JavaScript when writing functions.

blog comments powered by Disqus