Home Manual Reference Source Test Repository

lib/renderer/template.js

  1. /**
  2. * Methods for rendering a template.
  3. */
  4. import _ from 'lodash';
  5. import nunjucks from 'nunjucks';
  6. import moment from 'moment';
  7. import Url from '../url';
  8.  
  9. const TemplateErrorMessage = {
  10. NO_TEMPLATE: 'template not found',
  11. };
  12.  
  13. /**
  14. * Render a template with given context variables.
  15. * @param {Object} env Nunjucks instance;
  16. * @param {string} template Template name, excluding the extension.
  17. * @param {object} variables Object of variables to interpolate in the
  18. * template.
  19. * @return {string} Rendered template.
  20. */
  21. export function renderTemplate(env, template, variables) {
  22. if (!env) {
  23. return '';
  24. }
  25.  
  26. let result = '';
  27. try {
  28. result = env.render(`${template}.html`, variables);
  29. } catch (e) {
  30. if (e.message.includes(TemplateErrorMessage.NO_TEMPLATE)) {
  31. // The message given from nunjucks looks like:
  32. // Template render error: (/reptar/templates/page.html)
  33. // Template render error: (/reptar/templates/page.html)
  34. // Error: template not found: mistake.html
  35. // This takes the multi line message and just grabs the last line.
  36. const message = _.last(e.message.split('\n'))
  37. .trim()
  38. // Format the last line of `Error: template` to remove anything
  39. // preceding our known error message. (Basically remove `Error: `)
  40. .replace(
  41. new RegExp(`.*${TemplateErrorMessage.NO_TEMPLATE}`),
  42. TemplateErrorMessage.NO_TEMPLATE
  43. );
  44.  
  45. // Re-throw formatted error message.
  46. throw new Error(message);
  47. } else {
  48. throw e;
  49. }
  50. }
  51.  
  52. return result;
  53. }
  54.  
  55. /**
  56. * Render a string template with given context variables.
  57. * @param {Object} env Nunjucks instance;
  58. * @param {string} str Template string.
  59. * @param {object} variables Object of variables to interpolate in the
  60. * template.
  61. * @return {string} Rendered template.
  62. */
  63. export function renderTemplateString(env, str, variables) {
  64. if (!env) {
  65. return '';
  66. }
  67.  
  68. return env.renderString(str, variables);
  69. }
  70.  
  71. /**
  72. * Allow adding custom filters to the template engine.
  73. * @see {@link http://mozilla.github.io/nunjucks/api#custom-filters}
  74. * @param {Object} env Nunjucks instance;
  75. * @param {string} name Filter name.
  76. * @param {Function} func Filter function.
  77. * @param {boolean} async Whether the filter should be async.
  78. */
  79. export function addTemplateFilter(env, name, func, async = false) {
  80. env.addFilter(name, func, async);
  81. }
  82.  
  83. /**
  84. * Adds built in filters to template renderer.
  85. * @param {Object} env Nunjucks instance;
  86. * @param {Object} config Config instance.
  87. */
  88. function addBuiltinFilters(env, config) {
  89. function dateFilter(date, momentTemplate) {
  90. if (date == null) {
  91. return '';
  92. }
  93.  
  94. return moment
  95. .utc(date, config.get('file.dateFormat'))
  96. .format(momentTemplate);
  97. }
  98.  
  99. [
  100. ['date', dateFilter],
  101. [
  102. 'groupbydate',
  103. // Group items by formating their date via momentjs.
  104. // Useful for creating archive pages:
  105. // eslint-disable-next-line
  106. // {% for date, files in collections.post.files | reverse | groupbydate('MMMM YYYY') %}
  107. function groupbydate(data, momentTemplate, dateKey = 'date') {
  108. return _.groupBy(data, datum =>
  109. dateFilter(datum[dateKey], momentTemplate)
  110. );
  111. },
  112. ],
  113. [
  114. 'slug',
  115. function slug(str) {
  116. return Url.slug(str);
  117. },
  118. ],
  119. [
  120. 'absolute_url',
  121. function absoluteUrl(relativePath, basePath) {
  122. let base = basePath || '';
  123. const baseLength = base.length - 1;
  124. base = base[baseLength] === '/' ? base.substr(0, baseLength) : base;
  125.  
  126. let input = relativePath || '';
  127. const inputLength = input.length - 1;
  128. if (input[inputLength] !== '/') {
  129. input = `${input}/`;
  130. }
  131. if (input[0] !== '/') {
  132. input = `/${input}`;
  133. }
  134.  
  135. return base + input;
  136. },
  137. ],
  138. [
  139. 'limit',
  140. function limit(arr, length) {
  141. return arr.slice(0, length);
  142. },
  143. ],
  144. ].forEach(filter => {
  145. addTemplateFilter(env, ...filter);
  146. });
  147. }
  148.  
  149. /**
  150. * Exposed method to configure template engine.
  151. * @param {Object} options Options object with following properties:
  152. * @param {Object} options.config Config object.
  153. * @param {string|Array.<string>} options.paths Either an array of paths or a
  154. * singular path that we can load templates from.
  155. * @param {boolean} options.noCache Whether our template engine should cache
  156. * its templates. Only set to true when in watch mode.
  157. * @return {Object} Nunjucks instance.
  158. */
  159. export function configureTemplateEngine({ config, paths, noCache = false }) {
  160. const fileSystemLoader = new nunjucks.FileSystemLoader(paths, {
  161. noCache,
  162. });
  163.  
  164. const env = new nunjucks.Environment(fileSystemLoader, {
  165. autoescape: false,
  166. });
  167.  
  168. addBuiltinFilters(env, config);
  169.  
  170. return env;
  171. }