Skip to content

Instantly share code, notes, and snippets.

@dshster
Forked from latentflip/1.1_directory_structure.txt
Created November 21, 2012 18:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dshster/4126710 to your computer and use it in GitHub Desktop.
Save dshster/4126710 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));
});
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment