index.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /*jshint node: true*/
  2. var through = require("through");
  3. var xtend = require("xtend");
  4. var defaultPrecompiler = require("handlebars");
  5. var defaultCompiler = "require('hbsfy/runtime')";
  6. var defaultTraverse = false;
  7. var defaultExtensions = {
  8. hbs: true,
  9. handlebar: true,
  10. handlebars: true
  11. };
  12. var defaultProcessContent = function(content) {
  13. return content;
  14. }
  15. var MARKER = "// hbsfy compiled Handlebars template\n";
  16. var inlinePartials = {};
  17. function findPartials(tree) {
  18. var partials = [];
  19. hbTraverse(tree, function(node) {
  20. // handlebars 3,4
  21. if ((node.type === 'PartialStatement' || node.type === 'PartialBlockStatement') && node.name.original.substr(0,1) !== "@" && !inlinePartials[node.name.original]) {
  22. partials.push(node.name.original);
  23. return
  24. }
  25. // handlebars 2
  26. if (node.type === 'partial') {
  27. partials.push(node.partialName.name);
  28. return;
  29. }
  30. });
  31. return partials;
  32. }
  33. function hbTraverse(node, action) {
  34. if (node && node.type == 'DecoratorBlock' && node.path.original == 'inline') {
  35. inlinePartials[node.params[0].original] = true;
  36. }
  37. if (Array.isArray(node)) {
  38. return node.forEach(function(v) {
  39. hbTraverse(v, action);
  40. });
  41. }
  42. if (node && typeof node === 'object') {
  43. action(node);
  44. return Object.keys(node).forEach(function(k) {
  45. hbTraverse(node[k], action);
  46. })
  47. }
  48. }
  49. // Convert string or array of extensions to an object
  50. function toExtensionsOb(arr) {
  51. var ob = {};
  52. if (typeof arr === "string") {
  53. arr = arr.split(",");
  54. }
  55. if (Array.isArray(arr)) {
  56. arr.filter(Boolean).forEach(function(ext) {
  57. ob[ext] = true;
  58. });
  59. } else {
  60. // Already in the correct format
  61. return arr;
  62. }
  63. return ob;
  64. }
  65. function getOptions(opts) {
  66. var extensions = defaultExtensions;
  67. var compiler = defaultCompiler;
  68. var precompiler = defaultPrecompiler;
  69. var traverse = defaultTraverse;
  70. var processContent = defaultProcessContent;
  71. opts = opts || {};
  72. if (opts) {
  73. if (opts.e || opts.extensions) {
  74. extensions = toExtensionsOb(opts.e || opts.extensions);
  75. }
  76. if (opts.p || opts.precompiler) {
  77. precompiler = require(opts.p || opts.precompiler);
  78. }
  79. if (opts.c || opts.compiler) {
  80. compiler = opts.c || opts.compiler;
  81. }
  82. if (opts.t || opts.traverse) {
  83. traverse = opts.t || opts.traverse;
  84. }
  85. if (opts.pc || opts.processContent) {
  86. processContent = opts.pc || opts.processContent;
  87. }
  88. }
  89. return xtend({}, opts, {
  90. extensions: extensions,
  91. precompiler: precompiler,
  92. compiler: compiler,
  93. traverse: traverse,
  94. processContent: processContent
  95. });
  96. }
  97. function compile(file, opts) {
  98. var options = getOptions(opts);
  99. var compiler = options.compiler;
  100. var precompiler = options.precompiler;
  101. var traverse = options.traverse;
  102. var processContent = options.processContent;
  103. var js;
  104. var compiled = MARKER;
  105. var parsed = null;
  106. var partials = null;
  107. // Kill BOM
  108. file = file.replace(/^\uFEFF/, '');
  109. file = processContent(file);
  110. if (traverse) {
  111. parsed = precompiler.parse(file);
  112. partials = findPartials(parsed);
  113. }
  114. js = precompiler.precompile(file, options.precompilerOptions);
  115. // Compile only with the runtime dependency.
  116. compiled += "var HandlebarsCompiler = " + compiler + ";\n";
  117. if (partials && partials.length) {
  118. partials.forEach(function(p, i) {
  119. var ident = "partial$" + i;
  120. compiled += "var " + ident + " = require('" + p + "');\n";
  121. compiled += "HandlebarsCompiler.registerPartial('" + p + "', " + ident + ");\n";
  122. });
  123. }
  124. compiled += "module.exports = HandlebarsCompiler.template(" + js.toString() + ");\n";
  125. return compiled;
  126. }
  127. function hbsfy(file, opts) {
  128. var extensions = getOptions(opts).extensions;
  129. if (!extensions[file.split(".").pop()]) return through();
  130. var buffer = "";
  131. return through(function(chunk) {
  132. buffer += chunk.toString();
  133. },
  134. function() {
  135. // Pass through if already compiled.
  136. if (buffer.indexOf(MARKER) > -1) {
  137. this.queue(buffer);
  138. this.queue(null);
  139. return;
  140. }
  141. var compiled;
  142. try {
  143. compiled = compile(buffer, opts);
  144. } catch (e) {
  145. this.emit('error', e);
  146. return this.queue(null);
  147. }
  148. this.queue(compiled);
  149. this.queue(null);
  150. });
  151. }
  152. // Return new hbsfy transform with custom default options
  153. hbsfy.configure = function(rootOpts) {
  154. return function(file, opts) {
  155. return hbsfy(file, xtend({}, rootOpts, opts));
  156. };
  157. };
  158. exports = module.exports = hbsfy;
  159. exports.findPartials = findPartials;
  160. exports.compile = compile;