When we first started writing code at Getable I set a simple contraint; no builds.
Nobody is allowed to checkin a script that we run either before deployment or before checkin. As our application grew we were tempted more than once to solve a problem with a build step. Instead, we resisted and found what was ultimately a better solution.
Anything that would be a build step is written as normal code in node.js, without the writing to disc part. The routes we write for resources that would normally be the output of a build process we generate the first time they are requested and cache them in memory indefinitely which is easy since all of our routes use jaws.
We have two scripts to run the application, one for debugging and one for production. Debug sets a single global that we check in
if statements to enable source maps. The debug script also watches the files in the working directory for changes and flushes the cache, obviating the need for "dynamic rebuilds."
And we "build" a lot of stuff this way. All the front-end resources use browserify, the css is built using rework, and a bunch of other js files (jQuery, engine.io, etc) are concatenated together, minified, and loaded before the rest of the application code.
Deploying is as simple as pushing to git and restarting a process. We check our dependencies in to git and try to avoid compiled dependencies.
Tests can run without any rebuilding and they use the same code as the production app but with sourcemaps enabled since they set the debug global.
That's it. Short post :) Simple ideas are simple, create constraints to keep them that way.