Skip to content

Commit

Permalink
issue/1802: inlined fix for duplicate handlebars namespaces (#1803)
Browse files Browse the repository at this point in the history
oliverfoster authored Sep 29, 2017
1 parent ccf3ca1 commit bd693dd
Showing 2 changed files with 241 additions and 1 deletion.
239 changes: 239 additions & 0 deletions grunt/tasks/handlebars.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
/*
* grunt-contrib-handlebars
* http://gruntjs.com/
*
* Copyright (c) 2016 Tim Branyen, contributors
* Licensed under the MIT license.
*/

'use strict';
var chalk = require('chalk');
var nsdeclare = require('nsdeclare');

module.exports = function(grunt) {
var _ = grunt.util._;

// content conversion for templates
var defaultProcessContent = function(content) { return content; };

// AST processing for templates
var defaultProcessAST = function(ast) { return ast; };

// filename conversion for templates
var defaultProcessName = function(name) { return name; };

// filename conversion for partials
var defaultProcessPartialName = function(filepath) {
var pieces = _.last(filepath.split('/')).split('.');
var name = _(pieces).without(_.last(pieces)).join('.'); // strips file extension
if (name.charAt(0) === '_') {
name = name.substr(1, name.length); // strips leading _ character
}
return name;
};

var extractGlobalNamespace = function(nsDeclarations) {
// Extract global namespace from any existing namespace declaration.
// The purpose of this method is too fix an issue with AMD when using namespace as a function where the
// nsInfo.namespace will contains the last namespace, not the global namespace.

var declarations = _.keys(nsDeclarations);

// no declaration found
if (!declarations.length) {
return '';
}

// In case only one namespace has been declared it will only return it.
if (declarations.length === 1) {
return declarations[0];
}
// We only need to take any declaration to extract the global namespace.
// Another option might be find the shortest declaration which is the global one.
var matches = declarations[0].match(/(this\[[^\[]+\])/g);
return matches[0];
};

grunt.registerMultiTask('handlebars', 'Compile handlebars templates and partials.', function() {
var options = this.options({
namespace: 'JST',
separator: grunt.util.linefeed + grunt.util.linefeed,
wrapped: true,
amd: false,
commonjs: false,
knownHelpers: [],
knownHelpersOnly: false
});

// assign regex for partials directory detection
var partialsPathRegex = options.partialsPathRegex || /./;

// assign regex for partial detection
var isPartialRegex = options.partialRegex || /^_/;

// assign transformation functions
var processContent = options.processContent || defaultProcessContent;
var processName = options.processName || defaultProcessName;
var processPartialName = options.processPartialName || defaultProcessPartialName;
var processAST = options.processAST || defaultProcessAST;
var useNamespace = options.namespace !== false;

// assign compiler options
var compilerOptions = options.compilerOptions || {};
var filesCount = 0;

this.files.forEach(function(f) {
var declarations = [];
var partials = {};
var templates = {};
// template identifying parts
var ast, compiled, filename;

// Namespace info for current template
var nsInfo;

// Map of already declared namespace parts
var nsDeclarations = {};

// nsdeclare options when fetching namespace info
var nsDeclareOptions = {response: 'details', declared: nsDeclarations};

// Just get the namespace info for a given template
var getNamespaceInfo = _.memoize(function(filepath) {
if (!useNamespace) {
return undefined;
}
if (_.isFunction(options.namespace)) {
return nsdeclare(options.namespace(filepath), nsDeclareOptions);
}
return nsdeclare(options.namespace, nsDeclareOptions);
});

// iterate files, processing partials and templates separately
f.src.filter(function(filepath) {
// Warn on and remove invalid source files (if nonull was set).
if (!grunt.file.exists(filepath)) {
grunt.log.warn('Source file "' + filepath + '" not found.');
return false;
}
return true;
})
.forEach(function(filepath) {
var src = processContent(grunt.file.read(filepath), filepath);

var Handlebars = require('handlebars');
try {
// parse the handlebars template into it's AST
ast = processAST(Handlebars.parse(src));
compiled = Handlebars.precompile(ast, compilerOptions);

// if configured to, wrap template in Handlebars.template call
if (options.wrapped === true) {
compiled = 'Handlebars.template(' + compiled + ')';
}
} catch (e) {
grunt.log.error(e);
grunt.fail.warn('Handlebars failed to compile ' + filepath + '.');
}

// register partial or add template to namespace
if (partialsPathRegex.test(filepath) && isPartialRegex.test(_.last(filepath.split('/')))) {
filename = processPartialName(filepath);
if (options.partialsUseNamespace === true) {
nsInfo = getNamespaceInfo(filepath);
if (nsInfo.declaration) {
declarations.push(nsInfo.declaration);
}
partials[nsInfo.namespace + ':' + JSON.stringify(filename)] = ('Handlebars.registerPartial(' +
JSON.stringify(filename) + ', ' + nsInfo.namespace + '[' + JSON.stringify(filename) + '] = ' +
compiled + ');');
} else {
partials[JSON.stringify(filename)] = ('Handlebars.registerPartial(' + JSON.stringify(filename) +
', ' + compiled + ');');
}
} else {
if ((options.amd || options.commonjs) && !useNamespace) {
compiled = 'return ' + compiled;
}
filename = processName(filepath);
if (useNamespace) {
nsInfo = getNamespaceInfo(filepath);
if (nsInfo.declaration) {
declarations.push(nsInfo.declaration);
}
templates[nsInfo.namespace + ':' + JSON.stringify(filename)] = (nsInfo.namespace + '[' +
JSON.stringify(filename) + '] = ' + compiled + ';');
} else if (options.commonjs === true) {
templates[JSON.stringify(filename)] = compiled + ';';
} else {
templates[JSON.stringify(filename)] = compiled;
}
}
});

var output = declarations.concat(_.values(partials), _.values(templates));
if (output.length < 1) {
grunt.log.warn('Destination not written because compiled files were empty.');
} else {
if (useNamespace) {
if (options.node) {
output.unshift('Handlebars = glob.Handlebars || require(\'handlebars\');');
output.unshift('var glob = (\'undefined\' === typeof window) ? global : window,');

var nodeExport = 'if (typeof exports === \'object\' && exports) {';
nodeExport += 'module.exports = ' + nsInfo.namespace + ';}';

output.push(nodeExport);
}

}

if (options.amd) {
// Wrap the file in an AMD define fn.
if (typeof options.amd === 'boolean') {
output.unshift('define([\'handlebars\'], function(Handlebars) {');
} else if (typeof options.amd === 'string') {
output.unshift('define([\'' + options.amd + '\'], function(Handlebars) {');
} else if (typeof options.amd === 'function') {
output.unshift('define([\'' + options.amd(filename, ast, compiled) + '\'], function(Handlebars) {');
} else if (Array.isArray(options.amd)) {
// convert options.amd to a string of dependencies for require([...])
var amdString = '';
for (var i = 0; i < options.amd.length; i++) {
if (i !== 0) {
amdString += ', ';
}

amdString += '\'' + options.amd[i] + '\'';
}

// Wrap the file in an AMD define fn.
output.unshift('define([' + amdString + '], function(Handlebars) {');
}

if (useNamespace) {
// Namespace has not been explicitly set to false; the AMD
// wrapper will return the object containing the template.
output.push('return ' + extractGlobalNamespace(nsDeclarations) + ';');
}
output.push('});');
}

if (options.commonjs) {
if (useNamespace) {
output.push('return ' + nsInfo.namespace + ';');
}
// Export the templates object for CommonJS environments.
output.unshift('module.exports = function(Handlebars) {');
output.push('};');
}

filesCount++;
grunt.file.write(f.dest, output.join(grunt.util.normalizelf(options.separator)));
grunt.verbose.writeln('File ' + chalk.cyan(f.dest) + ' created.');
}
});

grunt.log.ok(filesCount + ' ' + grunt.util.pluralize(filesCount, 'file/files') + ' created.');
});
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -138,7 +138,8 @@
"grunt-contrib-clean": "~1.0.0",
"grunt-contrib-connect": "~1.0.2",
"grunt-contrib-copy": "~1.0.0",
"grunt-contrib-handlebars": "~1.0.0",
"handlebars": "~4.0.0",
"nsdeclare": "0.1.0",
"grunt-contrib-jshint": "~1.0.0",
"grunt-contrib-uglify": "^3.0.1",
"grunt-contrib-watch": "~1.0.0",

0 comments on commit bd693dd

Please sign in to comment.