diff --git a/Gruntfile.js b/Gruntfile.js index 967a297..f93da60 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -172,6 +172,15 @@ module.exports = function(grunt) { 'tmp/amd_namespace.js': ['test/fixtures/amd.html'] } }, + amd_namespace_with_dots: { + options: { + amd: ['handlebars', 'handlebars.helpers'], + namespace: 'JST.foo.templates' + }, + files: { + 'tmp/amd_namespace_with_dots.js': ['test/fixtures/amd.html'] + } + }, amd_partials_use_namespace: { options: { amd: ['handlebars'], diff --git a/tasks/handlebars.js b/tasks/handlebars.js index 657583a..d4cbb22 100644 --- a/tasks/handlebars.js +++ b/tasks/handlebars.js @@ -1,140 +1,140 @@ /* - * grunt-contrib-handlebars - * http://gruntjs.com/ - * - * Copyright (c) 2015 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]; - } else { - // 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 = []; - - // 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); - } else { - 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; - } else { - return true; - } - }) - .forEach(function(filepath) { - var src = processContent(grunt.file.read(filepath), filepath); - - var Handlebars = require('handlebars'); - var ast, compiled, filename; - 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 + '.'); - } + * grunt-contrib-handlebars + * http://gruntjs.com/ + * + * Copyright (c) 2015 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 to 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]; + } + else { + // 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 partials = []; + var templates = []; + + // 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); + } else { + 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; + } else { + return true; + } + }) + .forEach(function(filepath) { + var src = processContent(grunt.file.read(filepath), filepath); + + var Handlebars = require('handlebars'); + var ast, compiled, filename; + 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('/')))) { @@ -209,7 +209,12 @@ module.exports = function(grunt) { 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) + ';'); + + // if namespace is a function iterating over file for search global/root namespace + // instead find the declared namespace formatted and allowing namespace with dots + var namespace = _.isFunction(options.namespace) ? + extractGlobalNamespace(nsDeclarations) : getNamespaceInfo().namespace; + output.push('return ' + namespace + ';'); } output.push('});'); } diff --git a/test/expected/amd_namespace_with_dots.js b/test/expected/amd_namespace_with_dots.js new file mode 100644 index 0000000..4c29b25 --- /dev/null +++ b/test/expected/amd_namespace_with_dots.js @@ -0,0 +1,13 @@ +define(['handlebars', 'handlebars.helpers'], function(Handlebars) { + +this["JST"] = this["JST"] || {}; +this["JST"]["foo"] = this["JST"]["foo"] || {}; +this["JST"]["foo"]["templates"] = this["JST"]["foo"]["templates"] || {}; + +this["JST"]["foo"]["templates"]["test/fixtures/amd.html"] = Handlebars.template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) { + return "
\n

Some title

\n

I've been compiled with amd support

\n
"; + },"useData":true}); + +return this["JST"]["foo"]["templates"]; + +}); diff --git a/test/handlebars_test.js b/test/handlebars_test.js index 76725a2..c02350c 100644 --- a/test/handlebars_test.js +++ b/test/handlebars_test.js @@ -162,6 +162,15 @@ exports.handlebars = { test.done(); }); }, + amd_namespace_with_dots: function(test) { + test.expect(1); + + filesAreEqual('amd_namespace_with_dots.js', function(actual, expected) { + test.equal(actual, expected, 'should wrap everything with an AMD define block and have a ' + + 'custom module name with dots keeped.'); + test.done(); + }); + }, amd_partials_use_namespace: function(test) { test.expect(1);