consolidate.js 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484
  1. 'use strict';
  2. /*
  3. * Engines which do not support caching of their file contents
  4. * should use the `read()` function defined in consolidate.js
  5. * On top of this, when an engine compiles to a `Function`,
  6. * these functions should either be cached within consolidate.js
  7. * or the engine itself via `options.cache`. This will allow
  8. * users and frameworks to pass `options.cache = true` for
  9. * `NODE_ENV=production`, however edit the file(s) without
  10. * re-loading the application in development.
  11. */
  12. /**
  13. * Module dependencies.
  14. */
  15. var fs = require('fs');
  16. var path = require('path');
  17. var Promise = require('bluebird');
  18. var join = path.join;
  19. var resolve = path.resolve;
  20. var extname = path.extname;
  21. var dirname = path.dirname;
  22. var readCache = {};
  23. /**
  24. * Require cache.
  25. */
  26. var cacheStore = {};
  27. /**
  28. * Require cache.
  29. */
  30. var requires = {};
  31. /**
  32. * Clear the cache.
  33. *
  34. * @api public
  35. */
  36. exports.clearCache = function(){
  37. cacheStore = {};
  38. };
  39. /**
  40. * Conditionally cache `compiled` template based
  41. * on the `options` filename and `.cache` boolean.
  42. *
  43. * @param {Object} options
  44. * @param {Function} compiled
  45. * @return {Function}
  46. * @api private
  47. */
  48. function cache(options, compiled) {
  49. // cachable
  50. if (compiled && options.filename && options.cache) {
  51. delete readCache[options.filename];
  52. cacheStore[options.filename] = compiled;
  53. return compiled;
  54. }
  55. // check cache
  56. if (options.filename && options.cache) {
  57. return cacheStore[options.filename];
  58. }
  59. return compiled;
  60. }
  61. /**
  62. * Read `path` with `options` with
  63. * callback `(err, str)`. When `options.cache`
  64. * is true the template string will be cached.
  65. *
  66. * @param {String} options
  67. * @param {Function} fn
  68. * @api private
  69. */
  70. function read(path, options, fn) {
  71. var str = readCache[path];
  72. var cached = options.cache && str && typeof str === 'string';
  73. // cached (only if cached is a string and not a compiled template function)
  74. if (cached) return fn(null, str);
  75. // read
  76. fs.readFile(path, 'utf8', function(err, str){
  77. if (err) return fn(err);
  78. // remove extraneous utf8 BOM marker
  79. str = str.replace(/^\uFEFF/, '');
  80. if (options.cache) readCache[path] = str;
  81. fn(null, str);
  82. });
  83. }
  84. /**
  85. * Read `path` with `options` with
  86. * callback `(err, str)`. When `options.cache`
  87. * is true the partial string will be cached.
  88. *
  89. * @param {String} options
  90. * @param {Function} fn
  91. * @api private
  92. */
  93. function readPartials(path, options, fn) {
  94. if (!options.partials) return fn();
  95. var partials = options.partials;
  96. var keys = Object.keys(partials);
  97. function next(index) {
  98. if (index === keys.length) return fn(null);
  99. var key = keys[index];
  100. var file = join(dirname(path), partials[key] + extname(path));
  101. read(file, options, function(err, str){
  102. if (err) return fn(err);
  103. options.partials[key] = str;
  104. next(++index);
  105. });
  106. }
  107. next(0);
  108. }
  109. /**
  110. * promisify
  111. */
  112. function promisify(fn, exec) {
  113. return new Promise(function (res, rej) {
  114. fn = fn || function (err, html) {
  115. if (err) {
  116. return rej(err);
  117. }
  118. res(html);
  119. };
  120. exec(fn);
  121. });
  122. }
  123. /**
  124. * fromStringRenderer
  125. */
  126. function fromStringRenderer(name) {
  127. return function(path, options, fn){
  128. options.filename = path;
  129. return promisify(fn, function(fn) {
  130. readPartials(path, options, function (err) {
  131. if (err) return fn(err);
  132. if (cache(options)) {
  133. exports[name].render('', options, fn);
  134. } else {
  135. read(path, options, function(err, str){
  136. if (err) return fn(err);
  137. exports[name].render(str, options, fn);
  138. });
  139. }
  140. });
  141. });
  142. };
  143. }
  144. /**
  145. * Liquid support.
  146. */
  147. exports.liquid = fromStringRenderer('liquid');
  148. /**
  149. * Liquid string support.
  150. */
  151. /**
  152. * Note that in order to get filters and custom tags we've had to push
  153. * all user-defined locals down into @locals. However, just to make things
  154. * backwards-compatible, any property of `options` that is left after
  155. * processing and removing `locals`, `meta`, `filters`, `customTags` and
  156. * `includeDir` will also become a local.
  157. */
  158. exports.liquid.render = function(str, options, fn){
  159. return promisify(fn, function (fn) {
  160. var engine = requires.liquid || (requires.liquid = require('tinyliquid'));
  161. try {
  162. var context = engine.newContext();
  163. var k;
  164. /**
  165. * Note that there's a bug in the library that doesn't allow us to pass
  166. * the locals to newContext(), hence looping through the keys:
  167. */
  168. if (options.locals){
  169. for (k in options.locals){
  170. context.setLocals(k, options.locals[k]);
  171. }
  172. delete options.locals;
  173. }
  174. if (options.meta){
  175. context.setLocals('page', options.meta);
  176. delete options.meta;
  177. }
  178. /**
  179. * Add any defined filters:
  180. */
  181. if (options.filters){
  182. for (k in options.filters){
  183. context.setFilter(k, options.filters[k]);
  184. }
  185. delete options.filters;
  186. }
  187. /**
  188. * Set up a callback for the include directory:
  189. */
  190. var includeDir = options.includeDir || process.cwd();
  191. context.onInclude(function (name, callback) {
  192. var extname = path.extname(name) ? '' : '.liquid';
  193. var filename = path.resolve(includeDir, name + extname);
  194. fs.readFile(filename, {encoding: 'utf8'}, function (err, data){
  195. if (err) return callback(err);
  196. callback(null, engine.parse(data));
  197. });
  198. });
  199. delete options.includeDir;
  200. /**
  201. * The custom tag functions need to have their results pushed back
  202. * through the parser, so set up a shim before calling the provided
  203. * callback:
  204. */
  205. var compileOptions = {
  206. customTags: {}
  207. };
  208. if (options.customTags){
  209. var tagFunctions = options.customTags;
  210. for (k in options.customTags){
  211. /*Tell jshint there's no problem with having this function in the loop */
  212. /*jshint -W083 */
  213. compileOptions.customTags[k] = function (context, name, body){
  214. var tpl = tagFunctions[name](body.trim());
  215. context.astStack.push(engine.parse(tpl));
  216. };
  217. /*jshint +W083 */
  218. }
  219. delete options.customTags;
  220. }
  221. /**
  222. * Now anything left in `options` becomes a local:
  223. */
  224. for (k in options){
  225. context.setLocals(k, options[k]);
  226. }
  227. /**
  228. * Finally, execute the template:
  229. */
  230. var tmpl = cache(context) || cache(context, engine.compile(str, compileOptions));
  231. tmpl(context, fn);
  232. } catch (err) {
  233. fn(err);
  234. }
  235. });
  236. };
  237. /**
  238. * Jade support.
  239. */
  240. exports.jade = function(path, options, fn){
  241. return promisify(fn, function (fn) {
  242. var engine = requires.jade;
  243. if (!engine) {
  244. try {
  245. engine = requires.jade = require('jade');
  246. } catch (err) {
  247. try {
  248. engine = requires.jade = require('then-jade');
  249. } catch (otherError) {
  250. throw err;
  251. }
  252. }
  253. }
  254. try {
  255. var tmpl = cache(options) || cache(options, engine.compileFile(path, options));
  256. fn(null, tmpl(options));
  257. } catch (err) {
  258. fn(err);
  259. }
  260. });
  261. };
  262. /**
  263. * Jade string support.
  264. */
  265. exports.jade.render = function(str, options, fn){
  266. return promisify(fn, function (fn) {
  267. var engine = requires.jade;
  268. if (!engine) {
  269. try {
  270. engine = requires.jade = require('jade');
  271. } catch (err) {
  272. try {
  273. engine = requires.jade = require('then-jade');
  274. } catch (otherError) {
  275. throw err;
  276. }
  277. }
  278. }
  279. try {
  280. var tmpl = cache(options) || cache(options, engine.compile(str, options));
  281. fn(null, tmpl(options));
  282. } catch (err) {
  283. fn(err);
  284. }
  285. });
  286. };
  287. /**
  288. * Dust support.
  289. */
  290. exports.dust = fromStringRenderer('dust');
  291. /**
  292. * Dust string support.
  293. */
  294. exports.dust.render = function(str, options, fn){
  295. return promisify(fn, function(fn) {
  296. var engine = requires.dust;
  297. if (!engine) {
  298. try {
  299. engine = requires.dust = require('dust');
  300. } catch (err) {
  301. try {
  302. engine = requires.dust = require('dustjs-helpers');
  303. } catch (err) {
  304. engine = requires.dust = require('dustjs-linkedin');
  305. }
  306. }
  307. }
  308. var ext = 'dust';
  309. var views = '.';
  310. if (options) {
  311. if (options.ext) ext = options.ext;
  312. if (options.views) views = options.views;
  313. if (options.settings && options.settings.views) views = options.settings.views;
  314. }
  315. if (!options || (options && !options.cache)) engine.cache = {};
  316. engine.onLoad = function(path, callback){
  317. if ('' === extname(path)) path += '.' + ext;
  318. if ('/' !== path[0]) path = views + '/' + path;
  319. read(path, options, callback);
  320. };
  321. try {
  322. var tmpl = cache(options) || cache(options, engine.compileFn(str));
  323. tmpl(options, fn);
  324. } catch (err) {
  325. fn(err);
  326. }
  327. });
  328. };
  329. /**
  330. * Swig support.
  331. */
  332. exports.swig = fromStringRenderer('swig');
  333. /**
  334. * Swig string support.
  335. */
  336. exports.swig.render = function(str, options, fn){
  337. return promisify(fn, function(fn) {
  338. var engine = requires.swig || (requires.swig = require('swig'));
  339. try {
  340. if(options.cache === true) options.cache = 'memory';
  341. engine.setDefaults({ cache: options.cache });
  342. var tmpl = cache(options) || cache(options, engine.compile(str, options));
  343. fn(null, tmpl(options));
  344. } catch (err) {
  345. fn(err);
  346. }
  347. });
  348. };
  349. /**
  350. * Atpl support.
  351. */
  352. exports.atpl = fromStringRenderer('atpl');
  353. /**
  354. * Atpl string support.
  355. */
  356. exports.atpl.render = function(str, options, fn){
  357. return promisify(fn, function(fn) {
  358. var engine = requires.atpl || (requires.atpl = require('atpl'));
  359. try {
  360. var tmpl = cache(options) || cache(options, engine.compile(str, options));
  361. fn(null, tmpl(options));
  362. } catch (err) {
  363. fn(err);
  364. }
  365. });
  366. };
  367. /**
  368. * Liquor support,
  369. */
  370. exports.liquor = fromStringRenderer('liquor');
  371. /**
  372. * Liquor string support.
  373. */
  374. exports.liquor.render = function(str, options, fn){
  375. return promisify(fn, function(fn) {
  376. var engine = requires.liquor || (requires.liquor = require('liquor'));
  377. try {
  378. var tmpl = cache(options) || cache(options, engine.compile(str, options));
  379. fn(null, tmpl(options));
  380. } catch (err) {
  381. fn(err);
  382. }
  383. });
  384. };
  385. /**
  386. * Twig support.
  387. */
  388. exports.twig = fromStringRenderer('twig');
  389. /**
  390. * Twig string support.
  391. */
  392. exports.twig.render = function(str, options, fn){
  393. return promisify(fn, function(fn) {
  394. var engine = requires.twig || (requires.twig = require('twig').twig);
  395. var templateData = {
  396. data: str
  397. };
  398. try {
  399. var tmpl = cache(templateData) || cache(templateData, engine(templateData));
  400. fn(null, tmpl.render(options));
  401. } catch (err) {
  402. fn(err);
  403. }
  404. });
  405. };
  406. /**
  407. * EJS support.
  408. */
  409. exports.ejs = fromStringRenderer('ejs');
  410. /**
  411. * EJS string support.
  412. */
  413. exports.ejs.render = function(str, options, fn){
  414. return promisify(fn, function (fn) {
  415. var engine = requires.ejs || (requires.ejs = require('ejs'));
  416. try {
  417. var tmpl = cache(options) || cache(options, engine.compile(str, options));
  418. fn(null, tmpl(options));
  419. } catch (err) {
  420. fn(err);
  421. }
  422. });
  423. };
  424. /**
  425. * Eco support.
  426. */
  427. exports.eco = fromStringRenderer('eco');
  428. /**
  429. * Eco string support.
  430. */
  431. exports.eco.render = function(str, options, fn){
  432. return promisify(fn, function(fn) {
  433. var engine = requires.eco || (requires.eco = require('eco'));
  434. try {
  435. fn(null, engine.render(str, options));
  436. } catch (err) {
  437. fn(err);
  438. }
  439. });
  440. };
  441. /**
  442. * Jazz support.
  443. */
  444. exports.jazz = fromStringRenderer('jazz');
  445. /**
  446. * Jazz string support.
  447. */
  448. exports.jazz.render = function(str, options, fn){
  449. return promisify(fn, function(fn) {
  450. var engine = requires.jazz || (requires.jazz = require('jazz'));
  451. try {
  452. var tmpl = cache(options) || cache(options, engine.compile(str, options));
  453. tmpl.eval(options, function(str){
  454. fn(null, str);
  455. });
  456. } catch (err) {
  457. fn(err);
  458. }
  459. });
  460. };
  461. /**
  462. * JQTPL support.
  463. */
  464. exports.jqtpl = fromStringRenderer('jqtpl');
  465. /**
  466. * JQTPL string support.
  467. */
  468. exports.jqtpl.render = function(str, options, fn){
  469. return promisify(fn, function(fn) {
  470. var engine = requires.jqtpl || (requires.jqtpl = require('jqtpl'));
  471. try {
  472. engine.template(str, str);
  473. fn(null, engine.tmpl(str, options));
  474. } catch (err) {
  475. fn(err);
  476. }
  477. });
  478. };
  479. /**
  480. * Haml support.
  481. */
  482. exports.haml = fromStringRenderer('haml');
  483. /**
  484. * Haml string support.
  485. */
  486. exports.haml.render = function(str, options, fn){
  487. return promisify(fn, function(fn) {
  488. var engine = requires.haml || (requires.haml = require('hamljs'));
  489. try {
  490. options.locals = options;
  491. fn(null, engine.render(str, options).trimLeft());
  492. } catch (err) {
  493. fn(err);
  494. }
  495. });
  496. };
  497. /**
  498. * Hamlet support.
  499. */
  500. exports.hamlet = fromStringRenderer('hamlet');
  501. /**
  502. * Hamlet string support.
  503. */
  504. exports.hamlet.render = function(str, options, fn){
  505. return promisify(fn, function (fn) {
  506. var engine = requires.hamlet || (requires.hamlet = require('hamlet'));
  507. try {
  508. options.locals = options;
  509. fn(null, engine.render(str, options).trimLeft());
  510. } catch (err) {
  511. fn(err);
  512. }
  513. });
  514. };
  515. /**
  516. * Whiskers support.
  517. */
  518. exports.whiskers = function(path, options, fn){
  519. return promisify(fn, function (fn) {
  520. var engine = requires.whiskers || (requires.whiskers = require('whiskers'));
  521. engine.__express(path, options, fn);
  522. });
  523. };
  524. /**
  525. * Whiskers string support.
  526. */
  527. exports.whiskers.render = function(str, options, fn){
  528. return promisify(fn, function(fn) {
  529. var engine = requires.whiskers || (requires.whiskers = require('whiskers'));
  530. try {
  531. fn(null, engine.render(str, options));
  532. } catch (err) {
  533. fn(err);
  534. }
  535. });
  536. };
  537. /**
  538. * Coffee-HAML support.
  539. */
  540. exports['haml-coffee'] = fromStringRenderer('haml-coffee');
  541. /**
  542. * Coffee-HAML string support.
  543. */
  544. exports['haml-coffee'].render = function(str, options, fn){
  545. return promisify(fn, function(fn) {
  546. var engine = requires['haml-coffee'] || (requires['haml-coffee'] = require('haml-coffee'));
  547. try {
  548. var tmpl = cache(options) || cache(options, engine.compile(str, options));
  549. fn(null, tmpl(options));
  550. } catch (err) {
  551. fn(err);
  552. }
  553. });
  554. };
  555. /**
  556. * Hogan support.
  557. */
  558. exports.hogan = fromStringRenderer('hogan');
  559. /**
  560. * Hogan string support.
  561. */
  562. exports.hogan.render = function(str, options, fn){
  563. return promisify(fn, function (fn) {
  564. var engine = requires.hogan || (requires.hogan = require('hogan.js'));
  565. try {
  566. var tmpl = cache(options) || cache(options, engine.compile(str, options));
  567. fn(null, tmpl.render(options, options.partials));
  568. } catch (err) {
  569. fn(err);
  570. }
  571. });
  572. };
  573. /**
  574. * templayed.js support.
  575. */
  576. exports.templayed = fromStringRenderer('templayed');
  577. /**
  578. * templayed.js string support.
  579. */
  580. exports.templayed.render = function(str, options, fn){
  581. return promisify(fn, function (fn) {
  582. var engine = requires.templayed || (requires.templayed = require('templayed'));
  583. try {
  584. var tmpl = cache(options) || cache(options, engine(str));
  585. fn(null, tmpl(options));
  586. } catch (err) {
  587. fn(err);
  588. }
  589. });
  590. };
  591. /**
  592. * Handlebars support.
  593. */
  594. exports.handlebars = fromStringRenderer('handlebars');
  595. /**
  596. * Handlebars string support.
  597. */
  598. exports.handlebars.render = function(str, options, fn) {
  599. return promisify(fn, function(fn) {
  600. var engine = requires.handlebars || (requires.handlebars = require('handlebars'));
  601. try {
  602. for (var partial in options.partials) {
  603. engine.registerPartial(partial, options.partials[partial]);
  604. }
  605. for (var helper in options.helpers) {
  606. engine.registerHelper(helper, options.helpers[helper]);
  607. }
  608. var tmpl = cache(options) || cache(options, engine.compile(str, options));
  609. fn(null, tmpl(options));
  610. } catch (err) {
  611. fn(err);
  612. }
  613. });
  614. };
  615. /**
  616. * Underscore support.
  617. */
  618. exports.underscore = fromStringRenderer('underscore');
  619. /**
  620. * Underscore string support.
  621. */
  622. exports.underscore.render = function(str, options, fn) {
  623. return promisify(fn, function(fn) {
  624. var engine = requires.underscore || (requires.underscore = require('underscore'));
  625. try {
  626. var tmpl = cache(options) || cache(options, engine.template(str, null, options));
  627. fn(null, tmpl(options).replace(/\n$/, ''));
  628. } catch (err) {
  629. fn(err);
  630. }
  631. });
  632. };
  633. /**
  634. * Lodash support.
  635. */
  636. exports.lodash = fromStringRenderer('lodash');
  637. /**
  638. * Lodash string support.
  639. */
  640. exports.lodash.render = function(str, options, fn) {
  641. return promisify(fn, function (fn) {
  642. var engine = requires.lodash || (requires.lodash = require('lodash'));
  643. try {
  644. var tmpl = cache(options) || cache(options, engine.template(str, options));
  645. fn(null, tmpl(options).replace(/\n$/, ''));
  646. } catch (err) {
  647. fn(err);
  648. }
  649. });
  650. };
  651. /**
  652. * Pug support. (formerly Jade)
  653. */
  654. exports.pug = function(path, options, fn){
  655. return promisify(fn, function (fn) {
  656. var engine = requires.pug;
  657. if (!engine) {
  658. try {
  659. engine = requires.pug = require('pug');
  660. } catch (err) {
  661. try {
  662. engine = requires.pug = require('then-pug');
  663. } catch (otherError) {
  664. throw err;
  665. }
  666. }
  667. }
  668. try {
  669. var tmpl = cache(options) || cache(options, engine.compileFile(path, options));
  670. fn(null, tmpl(options));
  671. } catch (err) {
  672. fn(err);
  673. }
  674. });
  675. };
  676. /**
  677. * Pug string support.
  678. */
  679. exports.pug.render = function(str, options, fn){
  680. return promisify(fn, function (fn) {
  681. var engine = requires.pug;
  682. if (!engine) {
  683. try {
  684. engine = requires.pug = require('pug');
  685. } catch (err) {
  686. try {
  687. engine = requires.pug = require('then-pug');
  688. } catch (otherError) {
  689. throw err;
  690. }
  691. }
  692. }
  693. try {
  694. var tmpl = cache(options) || cache(options, engine.compile(str, options));
  695. fn(null, tmpl(options));
  696. } catch (err) {
  697. fn(err);
  698. }
  699. });
  700. };
  701. /**
  702. * QEJS support.
  703. */
  704. exports.qejs = fromStringRenderer('qejs');
  705. /**
  706. * QEJS string support.
  707. */
  708. exports.qejs.render = function (str, options, fn) {
  709. return promisify(fn, function (fn) {
  710. try {
  711. var engine = requires.qejs || (requires.qejs = require('qejs'));
  712. engine.render(str, options).then(function (result) {
  713. fn(null, result);
  714. }, function (err) {
  715. fn(err);
  716. }).done();
  717. } catch (err) {
  718. fn(err);
  719. }
  720. });
  721. };
  722. /**
  723. * Walrus support.
  724. */
  725. exports.walrus = fromStringRenderer('walrus');
  726. /**
  727. * Walrus string support.
  728. */
  729. exports.walrus.render = function (str, options, fn) {
  730. return promisify(fn, function (fn) {
  731. var engine = requires.walrus || (requires.walrus = require('walrus'));
  732. try {
  733. var tmpl = cache(options) || cache(options, engine.parse(str));
  734. fn(null, tmpl.compile(options));
  735. } catch (err) {
  736. fn(err);
  737. }
  738. });
  739. };
  740. /**
  741. * Mustache support.
  742. */
  743. exports.mustache = fromStringRenderer('mustache');
  744. /**
  745. * Mustache string support.
  746. */
  747. exports.mustache.render = function(str, options, fn) {
  748. return promisify(fn, function (fn) {
  749. var engine = requires.mustache || (requires.mustache = require('mustache'));
  750. try {
  751. fn(null, engine.to_html(str, options, options.partials));
  752. } catch (err) {
  753. fn(err);
  754. }
  755. });
  756. };
  757. /**
  758. * Just support.
  759. */
  760. exports.just = function(path, options, fn){
  761. return promisify(fn, function(fn) {
  762. var engine = requires.just;
  763. if (!engine) {
  764. var JUST = require('just');
  765. engine = requires.just = new JUST();
  766. }
  767. engine.configure({ useCache: options.cache });
  768. engine.render(path, options, fn);
  769. });
  770. };
  771. /**
  772. * Just string support.
  773. */
  774. exports.just.render = function(str, options, fn){
  775. return promisify(fn, function (fn) {
  776. var JUST = require('just');
  777. var engine = new JUST({ root: { page: str }});
  778. engine.render('page', options, fn);
  779. });
  780. };
  781. /**
  782. * ECT support.
  783. */
  784. exports.ect = function(path, options, fn){
  785. return promisify(fn, function (fn) {
  786. var engine = requires.ect;
  787. if (!engine) {
  788. var ECT = require('ect');
  789. engine = requires.ect = new ECT(options);
  790. }
  791. engine.configure({ cache: options.cache });
  792. engine.render(path, options, fn);
  793. });
  794. };
  795. /**
  796. * ECT string support.
  797. */
  798. exports.ect.render = function(str, options, fn){
  799. return promisify(fn, function (fn) {
  800. var ECT = require('ect');
  801. var engine = new ECT({ root: { page: str }});
  802. engine.render('page', options, fn);
  803. });
  804. };
  805. /**
  806. * mote support.
  807. */
  808. exports.mote = fromStringRenderer('mote');
  809. /**
  810. * mote string support.
  811. */
  812. exports.mote.render = function(str, options, fn){
  813. return promisify(fn, function (fn) {
  814. var engine = requires.mote || (requires.mote = require('mote'));
  815. try {
  816. var tmpl = cache(options) || cache(options, engine.compile(str));
  817. fn(null, tmpl(options));
  818. } catch (err) {
  819. fn(err);
  820. }
  821. });
  822. };
  823. /**
  824. * Toffee support.
  825. */
  826. exports.toffee = function(path, options, fn){
  827. return promisify(fn, function (fn) {
  828. var toffee = requires.toffee || (requires.toffee = require('toffee'));
  829. toffee.__consolidate_engine_render(path, options, fn);
  830. });
  831. };
  832. /**
  833. * Toffee string support.
  834. */
  835. exports.toffee.render = function(str, options, fn) {
  836. return promisify(fn, function (fn) {
  837. var engine = requires.toffee || (requires.toffee = require('toffee'));
  838. try {
  839. engine.str_render(str, options,fn);
  840. } catch (err) {
  841. fn(err);
  842. }
  843. });
  844. };
  845. /**
  846. * doT support.
  847. */
  848. exports.dot = fromStringRenderer('dot');
  849. /**
  850. * doT string support.
  851. */
  852. exports.dot.render = function (str, options, fn) {
  853. return promisify(fn, function (fn) {
  854. var engine = requires.dot || (requires.dot = require('dot'));
  855. try {
  856. var tmpl = cache(options) || cache(options, engine.compile(str, options && options._def));
  857. fn(null, tmpl(options));
  858. } catch (err) {
  859. fn(err);
  860. }
  861. });
  862. };
  863. /**
  864. * bracket support.
  865. */
  866. exports.bracket = fromStringRenderer('bracket');
  867. /**
  868. * bracket string support.
  869. */
  870. exports.bracket.render = function (str, options, fn) {
  871. return promisify(fn, function (fn) {
  872. var engine = requires.bracket || (requires.bracket = require('bracket-template'));
  873. try {
  874. var tmpl = cache(options) || cache(options, engine.default.compile(str));
  875. fn(null, tmpl(options));
  876. } catch (err) {
  877. fn(err);
  878. }
  879. });
  880. };
  881. /**
  882. * Ractive support.
  883. */
  884. exports.ractive = fromStringRenderer('ractive');
  885. /**
  886. * Ractive string support.
  887. */
  888. exports.ractive.render = function(str, options, fn){
  889. return promisify(fn, function (fn) {
  890. var engine = requires.ractive || (requires.ractive = require('ractive'));
  891. var template = cache(options) || cache(options, engine.parse(str));
  892. options.template = template;
  893. if (options.data === null || options.data === undefined)
  894. {
  895. var extend = (requires.extend || (requires.extend = require('util')._extend));
  896. // Shallow clone the options object
  897. options.data = extend({}, options);
  898. // Remove consolidate-specific properties from the clone
  899. var i, length;
  900. var properties = ["template", "filename", "cache", "partials"];
  901. for (i = 0, length = properties.length; i < length; i++) {
  902. var property = properties[i];
  903. delete options.data[property];
  904. }
  905. }
  906. try {
  907. fn(null, new engine(options).toHTML());
  908. } catch (err) {
  909. fn(err);
  910. }
  911. });
  912. };
  913. /**
  914. * Nunjucks support.
  915. */
  916. exports.nunjucks = fromStringRenderer('nunjucks');
  917. /**
  918. * Nunjucks string support.
  919. */
  920. exports.nunjucks.render = function (str, options, fn) {
  921. return promisify(fn, function (fn) {
  922. try {
  923. var engine = options.nunjucksEnv || requires.nunjucks || (requires.nunjucks = require('nunjucks'));
  924. var env = engine;
  925. // deprecated fallback support for express
  926. // <https://github.com/tj/consolidate.js/pull/152>
  927. // <https://github.com/tj/consolidate.js/pull/224>
  928. if (options.settings && options.settings.views)
  929. env = engine.configure(options.settings.views);
  930. else if (options.nunjucks && options.nunjucks.configure)
  931. env = engine.configure.apply(engine, options.nunjucks.configure);
  932. //
  933. // because `renderString` does not initiate loaders
  934. // we must manually create a loader for it based off
  935. // either `options.settings.views` or `options.nunjucks` or `options.nunjucks.root`
  936. //
  937. // <https://github.com/mozilla/nunjucks/issues/730>
  938. // <https://github.com/crocodilejs/node-email-templates/issues/182>
  939. //
  940. //
  941. // note that the below code didn't work nor make sense before
  942. // because loaders should take different options from rendering
  943. //
  944. /*
  945. var loader = options.loader;
  946. if (loader) {
  947. var env = new engine.Environment(new loader(options));
  948. env.renderString(str, options, fn);
  949. } else {
  950. engine.renderString(str, options, fn);
  951. }
  952. */
  953. // so instead we simply check if we passed a custom loader
  954. // otherwise we create a simple file based loader
  955. if (options.loader) {
  956. env = new engine.Environment(options.loader);
  957. } else if (options.settings && options.settings.views) {
  958. env = new engine.Environment(
  959. new engine.FileSystemLoader(options.settings.views)
  960. );
  961. } else if (options.nunjucks && options.nunjucks.loader) {
  962. if (typeof options.nunjucks.loader === 'string')
  963. env = new engine.Environment(new engine.FileSystemLoader(options.nunjucks.loader));
  964. else
  965. env = new engine.Environment(
  966. new engine.FileSystemLoader(
  967. options.nunjucks.loader[0],
  968. options.nunjucks.loader[1]
  969. )
  970. );
  971. }
  972. env.renderString(str, options, fn);
  973. } catch (err) {
  974. throw fn(err);
  975. }
  976. });
  977. };
  978. /**
  979. * HTMLing support.
  980. */
  981. exports.htmling = fromStringRenderer('htmling');
  982. /**
  983. * HTMLing string support.
  984. */
  985. exports.htmling.render = function(str, options, fn) {
  986. return promisify(fn, function (fn) {
  987. var engine = requires.htmling || (requires.htmling = require('htmling'));
  988. try {
  989. var tmpl = cache(options) || cache(options, engine.string(str));
  990. fn(null, tmpl.render(options));
  991. } catch (err) {
  992. fn(err);
  993. }
  994. });
  995. };
  996. /**
  997. * Rendering function
  998. */
  999. function requireReact(module, filename) {
  1000. var babel = requires.babel || (requires.babel = require('babel-core'));
  1001. var compiled = babel.transformFileSync(filename, { presets: [ 'react' ] }).code
  1002. return module._compile(compiled, filename);
  1003. }
  1004. exports.requireReact = requireReact;
  1005. /**
  1006. * Converting a string into a node module.
  1007. */
  1008. function requireReactString(src, filename) {
  1009. var babel = requires.babel || (requires.babel = require('babel-core'));
  1010. if (!filename) filename = '';
  1011. var m = new module.constructor();
  1012. filename = filename || '';
  1013. // Compile Using React
  1014. var compiled = babel.transform(src, { presets: [ 'react' ] }).code;
  1015. // Compile as a module
  1016. m.paths = module.paths;
  1017. m._compile(compiled, filename);
  1018. return m.exports;
  1019. }
  1020. /**
  1021. * A naive helper to replace {{tags}} with options.tags content
  1022. */
  1023. function reactBaseTmpl(data, options){
  1024. var exp;
  1025. var regex;
  1026. // Iterates through the keys in file object
  1027. // and interpolate / replace {{key}} with it's value
  1028. for (var k in options){
  1029. if (options.hasOwnProperty(k)){
  1030. exp = '{{' + k + '}}';
  1031. regex = new RegExp(exp, 'g');
  1032. if (data.match(regex)) {
  1033. data = data.replace(regex, options[k]);
  1034. }
  1035. }
  1036. }
  1037. return data;
  1038. }
  1039. /**
  1040. * The main render parser for React bsaed templates
  1041. */
  1042. function reactRenderer(type){
  1043. if (require.extensions) {
  1044. // Ensure JSX is transformed on require
  1045. if (!require.extensions['.jsx']) {
  1046. require.extensions['.jsx'] = requireReact;
  1047. }
  1048. // Supporting .react extension as well as test cases
  1049. // Using .react extension is not recommended.
  1050. if (!require.extensions['.react']) {
  1051. require.extensions['.react'] = requireReact;
  1052. }
  1053. }
  1054. // Return rendering fx
  1055. return function(str, options, fn) {
  1056. return promisify(fn, function(fn) {
  1057. // React Import
  1058. var ReactDOM = requires.ReactDOM || (requires.ReactDOM = require('react-dom/server'));
  1059. var react = requires.react || (requires.react = require('react'));
  1060. // Assign HTML Base
  1061. var base = options.base;
  1062. delete options.base;
  1063. var enableCache = options.cache;
  1064. delete options.cache;
  1065. var isNonStatic = options.isNonStatic;
  1066. delete options.isNonStatic;
  1067. // Start Conversion
  1068. try {
  1069. var Code;
  1070. var Factory;
  1071. var baseStr;
  1072. var content;
  1073. var parsed;
  1074. if (!cache(options)){
  1075. // Parsing
  1076. Code = (type === 'path') ? require(resolve(str)) : requireReactString(str);
  1077. Factory = cache(options, react.createFactory(Code));
  1078. } else {
  1079. Factory = cache(options);
  1080. }
  1081. parsed = new Factory(options);
  1082. content = (isNonStatic) ? ReactDOM.renderToString(parsed) : ReactDOM.renderToStaticMarkup(parsed);
  1083. if (base){
  1084. baseStr = readCache[str] || fs.readFileSync(resolve(base), 'utf8');
  1085. if (enableCache){
  1086. readCache[str] = baseStr;
  1087. }
  1088. options.content = content;
  1089. content = reactBaseTmpl(baseStr, options);
  1090. }
  1091. fn(null, content);
  1092. } catch (err) {
  1093. fn(err);
  1094. }
  1095. });
  1096. };
  1097. }
  1098. /**
  1099. * React JS Support
  1100. */
  1101. exports.react = reactRenderer('path');
  1102. /**
  1103. * React JS string support.
  1104. */
  1105. exports.react.render = reactRenderer('string');
  1106. /**
  1107. * ARC-templates support.
  1108. */
  1109. exports['arc-templates'] = fromStringRenderer('arc-templates');
  1110. /**
  1111. * ARC-templates string support.
  1112. */
  1113. exports['arc-templates'].render = function(str, options, fn) {
  1114. var readFileWithOptions = Promise.promisify(read);
  1115. var consolidateFileSystem = {};
  1116. consolidateFileSystem.readFile = function (path) {
  1117. return readFileWithOptions(path, options);
  1118. };
  1119. return promisify(fn, function (fn) {
  1120. try {
  1121. var engine = requires['arc-templates'];
  1122. if (!engine) {
  1123. var Engine = require('arc-templates/dist/es5');
  1124. engine = requires['arc-templates'] = new Engine({ filesystem: consolidateFileSystem });
  1125. }
  1126. var compiler = cache(options) || cache(options, engine.compileString(str, options.filename));
  1127. compiler.then(function (func) { return func(options); })
  1128. .then(function (result) { fn(null, result.content); })
  1129. .catch(fn);
  1130. } catch (err) {
  1131. fn(err);
  1132. }
  1133. });
  1134. };
  1135. /**
  1136. * Vash support
  1137. */
  1138. exports.vash = fromStringRenderer('vash');
  1139. /**
  1140. * Vash string support
  1141. */
  1142. exports.vash.render = function(str, options, fn) {
  1143. return promisify(fn, function(fn) {
  1144. var engine = requires.vash || (requires.vash = require('vash'));
  1145. try {
  1146. // helper system : https://github.com/kirbysayshi/vash#helper-system
  1147. if (options.helpers) {
  1148. for (var key in options.helpers) {
  1149. if (!options.helpers.hasOwnProperty(key) || typeof options.helpers[key] !== 'function') {
  1150. continue;
  1151. }
  1152. engine.helpers[key] = options.helpers[key];
  1153. }
  1154. }
  1155. var tmpl = cache(options) || cache(options, engine.compile(str, options));
  1156. fn(null, tmpl(options).replace(/\n$/, ''));
  1157. } catch (err) {
  1158. fn(err);
  1159. }
  1160. });
  1161. };
  1162. /**
  1163. * Slm support.
  1164. */
  1165. exports.slm = fromStringRenderer('slm');
  1166. /**
  1167. * Slm string support.
  1168. */
  1169. exports.slm.render = function(str, options, fn) {
  1170. return promisify(fn, function (fn) {
  1171. var engine = requires.slm || (requires.slm = require('slm'));
  1172. try {
  1173. var tmpl = cache(options) || cache(options, engine.compile(str, options));
  1174. fn(null, tmpl(options));
  1175. } catch (err) {
  1176. fn(err);
  1177. }
  1178. });
  1179. };
  1180. /**
  1181. * Marko support.
  1182. */
  1183. exports.marko = function(path, options, fn){
  1184. return promisify(fn, function (fn) {
  1185. var engine = requires.marko || (requires.marko = require('marko'));
  1186. options.writeToDisk = !!options.cache;
  1187. try {
  1188. var tmpl = cache(options) || cache(options, engine.load(path, options));
  1189. tmpl.render(options, fn)
  1190. } catch (err) {
  1191. fn(err);
  1192. }
  1193. });
  1194. };
  1195. /**
  1196. * Marko string support.
  1197. */
  1198. exports.marko.render = function(str, options, fn) {
  1199. return promisify(fn, function (fn) {
  1200. var engine = requires.marko || (requires.marko = require('marko'));
  1201. options.writeToDisk = !!options.cache;
  1202. try {
  1203. var tmpl = cache(options) || cache(options, engine.load('string.marko', str, options));
  1204. tmpl.render(options, fn)
  1205. } catch (err) {
  1206. fn(err);
  1207. }
  1208. });
  1209. };
  1210. /**
  1211. * expose the instance of the engine
  1212. */
  1213. exports.requires = requires;