Lifecycle Hooks

When compiling your email templates, Maizzle goes through a series of steps, like generating a Template config, rendering with Nunjucks, or applying Transformers.

Along the way, it also runs some functions known as lifecycle hooks.

We'll call them Events, for short.

Usage

You can use Events both when developing locally with the CLI build or serve commands, and when using the render() method in Node.js.

CLI

To use events when developing locally with the CLI commands, add them inside an events object in your config:

module.exports = {
  events: {
    afterConfig(config) {
      //
    },
    // other events...
  }
},

Node.js

To use Events in a Node context, add them inside the options object that you pass to the render() method:

const Maizzle = require('@maizzle/framework')
const str = `some HTML string...`

html = Maizzle.render(str, {
    tailwind: { /* ... */ },
    maizzle: { /* ... */ },
    beforeRender(nunjucks, config) {
      // ..
    },
  }
)

Events

These are the events that you can use in Maizzle.

The following events are CLI-only, meaning you can only use them in the events: {} object in your config.js:

The rest of the events run every time a Template is compiled. Unless you need access to the Template-specific config, or to its HTML, consider using the ones above instead:

beforeCreate

Runs after the Environment config has been computed, but before Templates are processed. Exposes the config object so you can further customize it.

For example, let's use a custom highlight function for Markdown fenced code blocks:

const Prism = require('prismjs')

module.exports = {
  // ...
  events: {
    async beforeCreate(config) {
      config.markdown.highlight = (code, lang, callback) => {
        return Prism.highlight(code, Prism.languages[lang], lang)
      }
    },
  },
}

afterConfig

Runs right after your Template-specific config has been generated. It accepts the config as a parameter, so you can further customize it.

For example, let's use Axios to set a random Bacon Ipsum preheader:

// config.js
const axios = require('axios')

module.exports = {
  events: {
    async afterConfig(config) {
      const preheader = await axios('https://baconipsum.com/api/?type=all-meat&sentences=1&start-with-lorem=1')
      config.preheader = preheader.data[0]
    },    
  },
},

beforeRender

Runs after Nunjucks is initialized, but before your Templates are rendered. It exposes the Nunjucks environment so you can add your own filters, extensions, or globals.

It also exposes the config, so you can further customize that as well.

As an example, let's add a custom filter to Nunjucks, that will allow us to shorten paragraphs to a custom character length (20 by default):

// config.js
module.exports = {
  events: {
    beforeRender(nunjucks, config) {
      nunjucks.addFilter('shorten', (str, count) => str.slice(0, count || 20))
    },    
  },
},
<!-- layouts/default.njk -->
{% if page.preheader %}
  <div class="hidden text-0 leading-0">{{ page.preheader | shorten(35) }}</div>
{% endif %}

afterRender

Runs after the Template has been rendered with Nunjucks, but before any Transfomers have been applied. Exposes the rendered html string and the config.

You can use it to alter the HTML, even before CSS inlining takes place. It's also your last chance to modify any Transformer-related settings in your config.

For example, let's assume that for some reason we change our mind and want to disable inlining. Oh, and we also want to rewrite all call-to-action labels (maybe they're coming from an external source, like a database?):

// config.js
module.exports = {
  events: {
    afterRender(html, config) {
      config.inlineCSS.enabled = false
      // must return `html`
      return html.replace(/Confirm email/g, 'Confirm your email')
    },    
  },
},

afterTransformers

Runs after all Transformers have been applied, just before the final HTML is returned.

Same as afterRender(), it exposes the html and the config, so you can do further adjustments to the HTML, or read some config settings.

For example, maybe you don't like the minifier that Maizzle includes, and you disabled it in your config so that you can use your own once the template has been compiled:

// config.js
const Minifier = require('imaginary-minifier')

module.exports = {
  minify: {
    enabled: false,
  },
  events: {
    afterTransformers(html, config) {
      // must return `html`
      if (!config.minify.enabled) {
        return Minifier.minify(html)
      }
  
      return html
    },
  },
},

afterBuild

Runs after all Templates have been compiled and output to disk.

Returns an array with a list of all files inside the build.destination.path directory.

// config.js
module.exports = {
  events: {
    afterBuild(files) {
      console.log(files)
    },
  },
},

Using it with the default Starter, maizzle build production will output:

[
  'build_production/images/maizzle.png',
  'build_production/promotional.html',
  'build_production/transactional.html'
]