You are viewing an old revision of this post, from June 12, 2014 @ 20:47:19. See below for differences between this version and the current revision.

There’s one problem with the way I first set up my build system for Medium: I had no control over how the CoffeeScript files were ordered. In languages with linkers, this isn’t a big deal: within a library, the linker will resolve all the references between object files at once. But without a linker, ordering becomes more of an issue.

Actually, in CoffeeScript or JavaScript, it’s not that much of an issue: in fact, for small projects you can get away with ignoring it entirely. It’s fine for methods in one class to refer to another class that hasn’t been loaded at the time the first class is defined: as long as the second class exists by the time the class has run, you’ll be okay. So that means that the only real issue when starting off is making sure your entry point gets run after everything else is loading; that’s a one-off case that’s easy to deal with manually. (You can just inline the entry point code in the HTML file, for example.)

 

Having said that, just clobbering everything together like that felt a little distasteful to me; and there also turned out to be two practical issues. The first is that Mocha, the unit test framework I used (which I promise I’ll talk about soon!), didn’t use the browser model of sticking everything in global variables: it used the Node.js concept of modules. I actually spent a couple of weeks ignoring that mismatch, writing code that worked in both realms by checking to see if the Node.js variables were defined, but in retrospect, that was silly: the point of this blog post is that doing things the right way is easier than that workaround.

And the second practical issue is inheritance: if class A inherits from class B, then the browser really does need to have seen the definition for class B before the definition of class A. To get that right, I needed a dependency structure; and doing that by hand would have crossed the line from silly to actively perverse. So I looked around, and found that browserify (in its coffeeify incarnation) was what I wanted.

 

First, a brief introduction to the Node module system. When you define what looks like a global variable in a Node source file, it doesn’t actually get stuck in the global namespace: the namespace for that file is local to that file. But Node provides a special exports variable: if you want to export values, attach them to that. For example, if I have a file runner_state.coffee that defines a RunnerState class, I’ll end the file with

exports.RunnerState = RunnerState

That last line still doesn’t stick RunnerState in the global namespace: there’s actually a special global object you can use for that, but you generally don’t want to do that. Instead, if another file wants to refer to that RunnerState variable, it puts a line like this at the top:

{RunnerState} = require('./runner_state.coffee')

The return value of the require() call is the exports object for that file, and I’m using CoffeeScript structured assignment to get at its RunnerState member. Once I’ve done that, I can refer to RunnerState elsewhere in that file. (Incidentally, in some situations you don’t need either the leading ./ or the trailing .coffee in the argument to require(), but I found that using both worked best with the collection of tools I was using.)

 

So, that’s the Node.js module system: a nice way to avoid polluting the global namespace and to express your object graph. It comes for free in the Node ecosystem, and all I wanted was to bring that over to a browser context. And that’s where browserify comes in: it lets you write code like it’s Node modules and then it transforms it into a format that the browser is happy with.

To cut to the chase, here’s how to get it to work. Start with the build system from last time. Then install browserify and coffeeify, plus the grunt plugin:

npm install --save-dev browserify coffeeify grunt-browserify

In your Gruntfile.coffee, replace the grunt-contrib-coffee requirement with a grunt-browserify requirement, and replace the coffee block with a block that looks like this:

    browserify:
      dist:
        files:
          'js/medium.js': ['coffee/*.coffee']
        options:
          transform: ['coffeeify']

Also, in your default task, you’ll want to invoke browserify instead of coffee.

 

Here’s the resulting file:

module.exports = (grunt) ->
  grunt.initConfig {
    pkg: grunt.file.readJSON('package.json')

    browserify:
      dist:
        files:
            'js/medium.js': ['coffee/*.coffee']
          options:
            transform: ['coffeeify']

    sass:
      dist:
        files:
          'css/medium.css': 'scss/medium.scss'

    watch:
      coffee:
        files: 'coffee/*.coffee'
        tasks: ['coffee']
        options:
          spawn: false

      sass:
        files: 'scss/*.scss'
        tasks: ['sass']
        options:
          spawn: false
  }

  grunt.loadNpmTasks('grunt-browserify')
  grunt.loadNpmTasks('grunt-contrib-sass')
  grunt.loadNpmTasks('grunt-contrib-watch')

  grunt.registerTask('default', ['browserify', 'sass'])

Now, if you run grunt, you’ll build the output JavaScript file (js/medium.js in this case) like before, but with separate input files treated as separate modules! Which, of course, means that it won’t actually work until you go back through them and add require() and exports in appropriate places.

Post Revisions:

Changes:

There are no differences between the June 12, 2014 @ 20:47:19 revision and the current revision. (Maybe only post meta information was changed.)