diff --git a/README.md b/README.md index 8068371..734ca74 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,64 @@ var Handlebars = require('handlebars'); var templates = require('./templates')(Handlebars); ``` + +#### es2015 +Type: `Boolean` or `Array` +Default: `false` + +Formats the output file as an ES2015 module, exports the result both as a named export as well as a default export. + +For this option to work the `namespace` property needs to be defined. +If not defined or explicitly set to `false`, the task's `namespace` property will instead default to `JST`. + + +If `Array` then each array entry is expected to have both a variable and path properties: + +```js + es2015: [ + {variable: '$', path: 'jquery'}, + {variable: 'Handlebars', path: 'handlebars.runtime.js'}, + {variable: '{NamedModule}', path: 'my_module.js'} + ] +``` + +Each of these will, in turn, be imported at the top of the compilation file: + + +```js + import $ from 'jquery'; + import Handlebars from 'handlebars.runtime.js'; + import {NamedModule} from 'my_module.js'; +``` + +If the es2015 property is a string, a function or `true` these imports will default to: + +```js + import Handlebars from 'handlebars.runtime.js'; +``` + +As you might know, you can't use `this` inside an es2015 module to reference the global scope. It's undefined as per specification. +Instead of `this`, you can pass a `root` option to be used in its place. + +```js +options: { + root: 'templates' +} +``` + +If said option isn't declared, the task's `root` property will default to `templates` + +So, for example, if you use the default namespace `JST`, the compiled file will end with: + +```js + var JST = templates["JST"]; + export {JST}; + export default JST; +``` + +The `root` option (albeit is not needed except for es2015 format) can be safely used with all output formats. + + #### processContent Type: `Function` diff --git a/tasks/handlebars.js b/tasks/handlebars.js index 6df32df..bf25b79 100644 --- a/tasks/handlebars.js +++ b/tasks/handlebars.js @@ -25,7 +25,7 @@ module.exports = function(grunt) { // filename conversion for partials var defaultProcessPartialName = function(filepath) { var pieces = _.last(filepath.split('/')).split('.'); - var name = _(pieces).without(_.last(pieces)).join('.'); // strips file extension + var name = _(pieces).without(_.last(pieces)).join('.'); // strips file extension if (name.charAt(0) === '_') { name = name.substr(1, name.length); // strips leading _ character } @@ -62,9 +62,20 @@ module.exports = function(grunt) { amd: false, commonjs: false, knownHelpers: [], - knownHelpersOnly: false + knownHelpersOnly: false, + es2015: false }); + if (options.es2015) { + // ES2015 doesn't allow to use "this" as root element + options.root = options.root || 'templates'; + } + if (options.es2015 || options.node) { + // when using es2015 or node output format, the namespace option must be defined + // Otherwise, it defaults to 'JST' + options.namespace = options.namespace || 'JST'; + } + // assign regex for partials directory detection var partialsPathRegex = options.partialsPathRegex || /./; @@ -98,6 +109,10 @@ module.exports = function(grunt) { // nsdeclare options when fetching namespace info var nsDeclareOptions = {response: 'details', declared: nsDeclarations}; + if (options.root) { + nsDeclareOptions.root = options.root; + } + // Just get the namespace info for a given template var getNamespaceInfo = _.memoize(function(filepath) { if (!useNamespace) { @@ -111,78 +126,80 @@ module.exports = function(grunt) { // 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 + ')'; + // 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; } - } 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); + 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 + ')'; } - partials.push('Handlebars.registerPartial(' + JSON.stringify(filename) + ', ' + nsInfo.namespace + - '[' + JSON.stringify(filename) + '] = ' + compiled + ');'); - } else { - partials.push('Handlebars.registerPartial(' + JSON.stringify(filename) + ', ' + compiled + ');'); + } catch (e) { + grunt.log.error(e); + grunt.fail.warn('Handlebars failed to compile ' + filepath + '.'); } - } 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); + + // 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.push('Handlebars.registerPartial(' + JSON.stringify(filename) + ', ' + nsInfo.namespace + + '[' + JSON.stringify(filename) + '] = ' + compiled + ');'); + } else { + partials.push('Handlebars.registerPartial(' + JSON.stringify(filename) + ', ' + compiled + ');'); } - templates.push(nsInfo.namespace + '[' + JSON.stringify(filename) + '] = ' + compiled + ';'); - } else if (options.commonjs === true) { - templates.push(compiled + ';'); } else { - templates.push(compiled); + 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.push(nsInfo.namespace + '[' + JSON.stringify(filename) + '] = ' + compiled + ';'); + } else if (options.commonjs === true) { + templates.push(compiled + ';'); + } else { + templates.push(compiled); + } } - } - }); + }); var output = declarations.concat(partials, 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 + ';}'; + if (options.root) { + output.unshift('var ' + options.root + ' = ' + options.root + ' || {};'); + } - output.push(nodeExport); - } + 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) { @@ -225,6 +242,21 @@ module.exports = function(grunt) { output.push('};'); } + if (options.es2015) { + + if (Array.isArray(options.es2015)) { + options.es2015.forEach(function(dependency) { + output.unshift('import ' + dependency.variable + ' from \'' + dependency.path + '\';'); + }); + } else { + output.unshift('import Handlebars from \'handlebars\';'); + } + + output.push('var ' + options.namespace + ' = ' + nsInfo.namespace + ';'); + output.push('export {' + options.namespace + '};'); + output.push('export default ' + options.namespace + ';'); + } + filesCount++; grunt.file.write(f.dest, output.join(grunt.util.normalizelf(options.separator))); grunt.verbose.writeln('File ' + chalk.cyan(f.dest) + ' created.');