catlogjs
Version:
Static site generator, translate human readable text format(such as markdown) into html, with a lot of other functions
232 lines (204 loc) • 8.38 kB
JavaScript
;
var util = require('util');
var path = require('path');
var inspect = function (obj) {
return util.inspect(obj, false, 4, true);
};
//
// ### Usemin
// Replaces references to non-optimized scripts or stylesheets
// into a set of HTML files (or any templates/views).
//
// The users markup should be considered the primary source of information
// for paths, references to assets which should be optimized.We also check
// against files present in the relevant directory () (e.g checking against
// the revved filename into the 'temp/') directory to find the SHA
// that was generated.
//
// Todos:
// * Use a file dictionary during build process and rev task to
// store each optimized assets and their associated sha1.
//
// #### Usemin-handler
//
// A special task which uses the build block HTML comments in markup to
// get back the list of files to handle, and initialize the grunt configuration
// appropriately, and automatically.
//
// Custom HTML "block" comments are provided as an API for interacting with the
// build script. These comments adhere to the following pattern:
//
// <!-- build:<type> <path> -->
// ... HTML Markup, list of script / link tags.
// <!-- endbuild -->
//
// - type: is either js or css.
// - path: is the file path of the optimized file, the target output.
//
// An example of this in completed form can be seen below:
//
// <!-- build:js js/app.js -->
// <script src="js/app.js"></script>
// <script src="js/controllers/thing-controller.js"></script>
// <script src="js/models/thing-model.js"></script>
// <script src="js/views/thing-view.js"></script>
// <!-- endbuild -->
//
//
// Internally, the task parses your HTML markup to find each of these blocks, and
// initializes for you the corresponding Grunt config for the concat / uglify tasks
// when `type=js`, the concat / cssmin tasks when `type=css`.
//
// The task also handles use of RequireJS, for the scenario where you specify
// the main entry point for your application using the "data-main" attribute
// as follows:
//
// <!-- build:js js/app.min.js -->
// <script data-main="js/main" src="js/vendor/require.js"></script>
// <!-- -->
//
// One doesn't need to specify a concat/uglify/cssmin or requirejs configuration anymore.
//
// Inspired by previous work in https://gist.github.com/3024891
// For related sample, see: cli/test/tasks/usemin-handler/index.html
//
module.exports = function (grunt) {
var HTMLProcessor = require('../lib/htmlprocessor');
var CSSProcessor = require('../lib/cssprocessor');
var RevvedFinder = require('../lib/revvedfinder');
grunt.registerMultiTask('usemin', 'Replaces references to non-minified scripts / stylesheets', function () {
var options = this.options({
type: this.target
});
var processors = {
css: CSSProcessor,
html: HTMLProcessor
};
this.files.forEach(function (fileObj) {
var files = grunt.file.expand({nonull: true}, fileObj.src);
files.map(grunt.file.read).forEach(function (content, i) {
var filepath = files[i];
var filedir = options.basedir || path.dirname(filepath);
grunt.log.subhead('Processing as ' + options.type.toUpperCase() + ' - ' + filepath);
// make sure to convert back into utf8, `file.read` when used as a
// forEach handler will take additional arguments, and thus trigger the
// raw buffer read
content = content.toString();
// Our revved version locator
var revvedfinder = new RevvedFinder(function (p) { return grunt.file.expand({filter: 'isFile'}, p); }, options.dirs);
// ext-specific directives handling and replacement of blocks
var proc = new processors[options.type](filedir, '', content, revvedfinder, function (msg) {
grunt.log.writeln(msg);
});
content = proc.process();
// write the new content to disk
grunt.file.write(filepath, content);
});
});
});
grunt.registerMultiTask('useminPrepare', 'Using HTML markup as the primary source of information', function () {
var options = this.options();
// collect files
var files = grunt.file.expand({filter: 'isFile'}, this.data);
var uglifyName = options.uglify || 'uglify';
var cssminName = options.cssmin || 'cssmin';
var dest = options.dest;
// concat / uglify / cssmin / requirejs config
var concat = grunt.config('concat') || {};
var uglify = grunt.config(uglifyName) || {};
var cssmin = grunt.config(cssminName) || {};
var requirejs = grunt.config('requirejs') || {};
grunt.log
.writeln('Going through ' + grunt.log.wordlist(files) + ' to update the config')
.writeln('Looking for build script HTML comment blocks');
files = files.map(function (filepath) {
return {
path: filepath,
body: grunt.file.read(filepath)
};
});
files.forEach(function (file) {
var revvedfinder = new RevvedFinder(function (p) { return grunt.file.expand({filter: 'isFile'}, p); });
var proc = new HTMLProcessor(path.dirname(file.path), dest, file.body, revvedfinder, function (msg) {
grunt.log.writeln(msg);
});
proc.blocks.forEach(function (block) {
grunt.log.subhead('Found a block:')
.writeln(grunt.log.wordlist(block.raw, { separator: '\n' }))
.writeln('Updating config with the following assets:')
.writeln(' - ' + grunt.log.wordlist(block.src, { separator: '\n - ' }));
// update concat config for this block
if (block.dest.match(/^_/)) {
// grunt does not allow tasks with _, so convert to complex method
concat[block.dest.replace('_', '')] = {
src: block.src,
dest: block.dest
};
} else {
concat[block.dest] = block.src;
}
grunt.config('concat', concat);
// update requirejs config as well, as during path lookup we might have
// updated it on data-main attribute
if (block.requirejs) {
var hasTasks;
for (var i in requirejs) {
if (requirejs.hasOwnProperty(i)) {
hasTasks = true;
var task = requirejs[i];
var options = task.options;
if (options) {
options.name = options.name || block.requirejs.name;
options.out = options.out || block.requirejs.dest;
options.baseUrl = options.baseUrl || block.requirejs.baseUrl;
options.mainConfigFile = options.mainConfigFile || path.join(options.baseUrl, options.name) + '.js';
} else {
task.options = {
name: block.requirejs.name,
out: block.requirejs.dest,
baseUrl: block.requirejs.baseUrl,
mainConfigFile: block.requirejs.mainConfigFile || path.join(block.requirejs.baseUrl, block.requirejs.name) + '.js'
};
}
}
}
if (!hasTasks) {
requirejs.default = {
options: {
name: block.requirejs.name,
out: block.requirejs.dest,
baseUrl: block.requirejs.baseUrl
}
};
}
grunt.config('requirejs', requirejs);
}
// uglify config, only for js type block
if (block.type === 'js') {
// TODO: we should differentiate whether or not we're
// using concat before ... Option ?
uglify[block.dest] = block.dest;
if (block.requirejs) {
uglify[block.requirejs.srcDest] = block.requirejs.src;
}
grunt.config(uglifyName, uglify);
}
// cssmin config, only for cssmin type block
if (block.type === 'css') {
cssmin[block.dest] = block.dest;
grunt.config(cssminName, cssmin);
}
});
});
// log a bit what was added to config
grunt.log.subhead('Configuration is now:')
.subhead(' cssmin:')
.writeln(' ' + inspect(cssmin))
.subhead(' concat:')
.writeln(' ' + inspect(concat))
.subhead(' uglify:')
.writeln(' ' + inspect(uglify))
.subhead(' requirejs:')
.writeln(' ' + inspect(requirejs));
});
};