handlebars-mjml-generator.ts 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. import dateFormat from 'dateformat';
  2. import fs from 'fs-extra';
  3. import Handlebars from 'handlebars';
  4. import mjml2html from 'mjml';
  5. import path from 'path';
  6. import { EmailGenerator, EmailPluginDevModeOptions, EmailPluginOptions } from './types';
  7. /**
  8. * Uses Handlebars (https://handlebarsjs.com/) to output MJML (https://mjml.io) which is then
  9. * compiled down to responsive email HTML.
  10. */
  11. export class HandlebarsMjmlGenerator implements EmailGenerator {
  12. onInit(options: EmailPluginOptions | EmailPluginDevModeOptions) {
  13. const partialsPath = path.join(options.templatePath, 'partials');
  14. this.registerPartials(partialsPath);
  15. this.registerHelpers();
  16. }
  17. generate(from: string, subject: string, template: string, templateVars: any) {
  18. const compiledFrom = Handlebars.compile(from, { noEscape: true });
  19. const compiledSubject = Handlebars.compile(subject);
  20. const compiledTemplate = Handlebars.compile(template);
  21. // We enable prototype properties here, aware of the security implications
  22. // described here: https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access
  23. // This is needed because some Vendure entities use getters on the entity
  24. // prototype (e.g. Order.total) which may need to be interpolated.
  25. const templateOptions: RuntimeOptions = { allowProtoPropertiesByDefault: true };
  26. const fromResult = compiledFrom(templateVars, { allowProtoPropertiesByDefault: true });
  27. const subjectResult = compiledSubject(templateVars, { allowProtoPropertiesByDefault: true });
  28. const mjml = compiledTemplate(templateVars, { allowProtoPropertiesByDefault: true });
  29. const body = mjml2html(mjml).html;
  30. return { from: fromResult, subject: subjectResult, body };
  31. }
  32. private registerPartials(partialsPath: string) {
  33. const partialsFiles = fs.readdirSync(partialsPath);
  34. for (const partialFile of partialsFiles) {
  35. const partialContent = fs.readFileSync(path.join(partialsPath, partialFile), 'utf-8');
  36. Handlebars.registerPartial(path.basename(partialFile, '.hbs'), partialContent);
  37. }
  38. }
  39. private registerHelpers() {
  40. Handlebars.registerHelper('formatDate', (date: Date | undefined, format: string | object) => {
  41. if (!date) {
  42. return date;
  43. }
  44. if (typeof format !== 'string') {
  45. format = 'default';
  46. }
  47. return dateFormat(date, format);
  48. });
  49. Handlebars.registerHelper('formatMoney', (amount?: number) => {
  50. if (amount == null) {
  51. return amount;
  52. }
  53. return (amount / 100).toFixed(2);
  54. });
  55. }
  56. }