Skip to content

Instantly share code, notes, and snippets.

@latentflip
Created May 3, 2012 09:24
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save latentflip/2584658 to your computer and use it in GitHub Desktop.
Save latentflip/2584658 to your computer and use it in GitHub Desktop.
In Lieu of doing this properly
./frontend
├── /coffee
│   ├── /models
│   | └── MyBaseModel.coffee
│   | └── MyModel.coffee
│   ├── /routers
│   | └── MyRouter.coffee
│   ├── /templates
| │   ├── /tmpl-dir1
| │   | ├── template1.jst.eco
| │   | └── template2.jst.eco
│   | └── tmpl-dir2
│   ├── /views
│   | └── /model_views
| |   ├── main_view.coffee
| |   └── sub_view.coffee
│   └── /lib
│   └── /jquery.js
|
├── /tasks
| ├── coffee.js
| ├── copy.js
| ├── ecojst.js
| └── fingerprint.js
├── /tmp
└── frontend.json
└── grunt.js

Using grunt instead of the asset pipeline.

  • At the moment I have only done coffeescript compilation, not scss/css yet
  • In my rails app, I have created a ./frontend directory structure like the above.
  • The ./frontend/coffee dir is where your "app" goes, it can be structured however you like, and contain .js files.
  • The ./tasks file contains some custom tasks I have written, I'll include them below, but they are:
    • coffee.js compile coffee-script files from ./coffee into ./tmp maintaining directory structure.
    • copy.js copy files from ./coffee into ./tmp maintaining directory structure (copies .js files across as-is.
    • ecojst.js precompile .eco templates into .js files under a JST[] namespace - trivial to switch for handlebars or any JS template language with a precompile method.
    • fingerprint.js rename a file, appending the md5sum of its contents to the filename.
  • frontend.json, is sort of a manifest defining how to build/require our compiled js file. It works a lot like the sprockets manifests: list files you want to concat in coffee: []. You can use /**/, or / to glob files, and it won't pull in files twice.
//This should be elsewhere, but loads in our manifest, and converts the file list to a list of files.
var frontendConfig = require('./frontend.json')
var concatFiles = frontendConfig.coffee.map(function(f) { return "tmp/"+f+".js"});
module.exports = function(grunt) {
grunt.loadTasks('./tasks'); //load my local tasks
grunt.loadNpmTasks('grunt-clean'); //load an npm task
grunt.initConfig({
//Empty the 'tmp' directory on each run'
clean: {
folder: 'tmp'
},
//Compile coffeescript into tmp, maintaining directory structure, but stripping /coffee from the start
coffee: {
all: {
srcRelTo: 'coffee',
src: ['coffee/**/*.coffee'],
dest: 'tmp',
}
},
//Precompile eco templates into tmp, maintaining directory structure, but stripping /coffee from the start
ecojst: {
all: {
srcRelTo: 'coffee',
src: ['coffee/**/*.jst.eco'],
dest: 'tmp'
}
},
//Copy javascript files into tmp, maintaining directory structure, but stripping /coffee from the start
copy: {
all: {
srcRelTo: 'coffee',
src: ['coffee/**/*.js'],
dest: 'tmp'
}
},
//Using our manifest file config, concat the list of files into one
concat: {
frontend: {
src: concatFiles,
dest: frontendConfig.dest,
separator: ';'
}
},
//Using our manifest file config, minify, the manifest file
min: {
release: {
src: [frontendConfig.dest],
dest: frontendConfig.destMin
}
},
//Fingerprint the minified file
fingerprint: {
release: {
files: [frontendConfig.destMin]
}
},
//Recompile debuggable file on change
watch: {
coffee: {
files: ['coffee/**/*.coffee'],
tasks: 'debug'
},
}
});
// Create a debuggable file
grunt.registerTask('debug', 'clean coffee copy ecojst concat');
// Create a releaseable file
grunt.registerTask('release', 'debug min fingerprint');
// Make the default `grunt` command run debug.
grunt.registerTask('default', 'debug');
};
{
"name": "Frontend",
"dest": "../public/javascripts/frontend.js",
"destMin": "../public/javascripts/frontend.min.js",
"coffee": [
"lib/jquery",
"lib/**/*",
"templates/**/*",
"models/MyBaseModel",
"models/**/*",
"views/**/*",
"routers/**/*",
]
}
module.exports = function(grunt) {
var path = require('path');
// Please see the grunt documentation for more information regarding task and
// helper creation: https://github.com/cowboy/grunt/blob/master/docs/toc.md
// ==========================================================================
// TASKS
// ==========================================================================
grunt.registerMultiTask('coffee', 'Compile CoffeeScript files', function() {
var dest = this.file.dest;
var srcRelTo = this.data.srcRelTo;
grunt.file.expandFiles(this.file.src).forEach(function(filepath) {
var coffeeDir = path.dirname(filepath);
var coffeeDir = coffeeDir.replace(srcRelTo, '');
grunt.helper('coffee', filepath, path.join(dest, coffeeDir));
});
if (grunt.task.current.errorCount) {
return false;
}
});
// ==========================================================================
// HELPERS
// ==========================================================================
grunt.registerHelper('coffee', function(src, destPath) {
var coffee = require('coffee-script'),
js = '';
var dest = path.join(destPath,
path.basename(src, '.coffee') + '.js');
try {
js = coffee.compile(grunt.file.read(src), { bare: true });
grunt.file.write(dest, js);
} catch (e) {
grunt.log.error("Unable to compile your coffee", e);
}
});
};
module.exports = function(grunt) {
var path = require('path');
grunt.registerMultiTask('copy', 'Copy files', function() {
var dest = this.file.dest;
var srcRelTo = this.data.srcRelTo;
grunt.file.expandFiles(this.file.src).forEach(function(filepath) {
var outDir = path.dirname(filepath);
var outDir = outDir.replace(srcRelTo, '');
var outFile = path.basename(filepath)
grunt.file.copy(filepath, path.join(dest, outDir, outFile));
});
if (grunt.task.current.errorCount) {
return false;
}
});
};
module.exports = function(grunt) {
var path = require("path");
grunt.registerMultiTask("ecojst",
"Compile eco templates to JST file", function() {
// Expand files to full paths
var dest = this.file.dest;
var srcRelTo = this.data.srcRelTo;
grunt.file.expandFiles(this.file.src).forEach(function(filepath) {
var outDir = path.dirname(filepath);
outDir = outDir.replace(srcRelTo, "");
grunt.helper("ecojst", filepath, dest, outDir);
});
if (grunt.task.current.errorCount) {
return false;
}
});
grunt.registerHelper("ecojst", function(src, destPath, outDir) {
var eco = require("eco"),
js = "";
var dest = path.join(destPath, outDir,
path.basename(src, ".jst.eco") + ".js");
//try {
js += "(function() {\n";
js += " this.JST || (this.JST = {});\n";
js += ' this.JST["'+outDir.slice(1)+'/'+path.basename(src, ".jst.eco")+'"] = '
js += eco.precompile(grunt.file.read(src));
js += "\n}).call(this);";
grunt.file.write(dest, js);
//} catch (e) {
// grunt.log.error("Unable to compile your template", e);
//}
});
};
module.exports = function(grunt) {
var path = require('path'),
crypto = require('crypto'),
fs = require('fs');
grunt.registerMultiTask('fingerprint', 'Fingerprint Files', function() {
grunt.file.expandFiles(this.data.files).forEach(function(filepath) {
var hash = crypto.createHash('md5').update(grunt.file.read(filepath)).digest("hex");
var outpath = path.dirname(filepath);
var filename = path.basename(filepath);
filename = filename.replace('.', '-'+hash+'.')
fs.renameSync(filepath, path.join(outpath, filename));
});
});
};
@latentflip
Copy link
Author

Caveats at the moment:

  • Haven't written a method to pull in the correct fingerprinting file into my templates.

@froots
Copy link

froots commented May 3, 2012

Ooh, don't suppose you could publish the fingerprint.js task? Is that something you custom wrote?

@froots
Copy link

froots commented May 3, 2012

Ahh, looks like you just answered one of my questions. It shouldn't be too hard. Specify target files for replacement, bit of regex to find the original filename, replace.

@latentflip
Copy link
Author

Yes just did, and yes I did.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment