A few months ago, I posted Generating Features Images: Getting Started with Gulp where I walkthrough my journey with Gulp.

Gulp is a toolkit for automating painful or time-consuming tasks in your development workflow, so you can stop messing around and build something.

I have been using a Gulp script to manually generate feature images for my blog post. I started with a quickly thrown together prototype Gulp script that did the thing I wanted it to, generate feature images.

The feature image for this post

However, the more I learned about Gulp, the more I wanted to write it the “Gulp way”. To me, the “Gulp Way”, which I inferred from reading documentation, meant writing small functions that do some type of transformation on the data as it travels through the Gulp pipeline. For example, the pipeline in my head looked like:

  1. Collect a list of Markdown files
  2. Parse Front matter and add the parsed attrobites to Gulp Vinly object.
  3. Load the HTML template, fill in title, author, date from the parsed attributes etc.
  4. Convert the HTML to a JPEG or PNG image with Puppeteer

I completely failed to get this to work, as separate functions. The place I ended was my script generating a bunch of HTML. My practical and successful end result was a monolithic script that did all four things listed above.

Before getting started, you can skip straight to the code, it’s stored on my GitHub.

How Does it Work

The function, generateFeatureImage, accepts the Markdown file as an input and does the following:

Parse the Front Matter

Front matter is metadata added to the top of a Markdown file. It is used to describe details, such as title, author, description, and date of publishing. For example, the Front matter for a post in my website’s Markdown based CMS might look like:

title: "Generate Feature Images: Getting Started with Gulp"
author: "Evan Halley"
description: "Lets get started with Gulp. My goal is to write some automation for generating social media feature posts.  Part 1 covers getting started with Gulp."
tags: ['gulp', 'javascript', 'node', 'generate-feature-images']
date: '2020-03-24'
social_cover: 'images/feature/generate_feature_images_getting_started_with_gulp.png'

I imported the NPM package front-matter to parse this Front matter.

let markdownFileContents = file.contents.toString();
const attributes = fm(markdownFileContents).attributes;

Populating the HTML Template

Each feature image is based on an HTML template that gets rendered with Puppeteer.

<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="style.css" />
        <link href="https://fonts.googleapis.com/css?family=Nunito+Sans:400,500,700&display=swap" rel="stylesheet">
    </head>
    <body>
        <div id="outer">
            <div id="container">
                <div id="body">
                    [[TITLE]]
                </div>
                <div id="footer">
                    <div id="author">
                        <img id="author-img" src="author.jpg">
                        <div id="author-name">
                            [[AUTHOR]] • [[DATE]]
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

I load the HTML template from disk then replace each [[VARIABLE]] with it’s attribute.

const template = fs.readFileSync(`${TEMPLATE_DIRECTORY}/${HTML_TEMPLATE}`).toString();
const html = template.replace('[[TITLE]]', attributes.title)
    .replace('[[AUTHOR]]', attributes.author)
    .replace('[[DATE]]', date.format('MMM Do'));

Also in my HTML template are references to style.css and author.jpg. These files, the CSS styling and a picture of myself, are external to the template. They get processed and rendered when the HTML template is loaded in Chrome.

Rendering the Template

Finally, I use Puppeteer to render the HTML and save a screenshot.

const browser = await puppeteer.launch({ headless: true });

fs.writeFileSync(htmlFile, html);
let htmlFilePath = `file://${path.resolve(htmlFile)}`;
const page = await browser.newPage();
await page.setViewport({ /* viewport options */ });
await page.goto(htmlFilePath);
await page.screenshot({ path: `${OUTPUT_DIRECTORY}/${outputFilename}` });

Bringing it All Together

The final implementation step is to bring it together with Gulp.

gulp.task("generate-feature-image", function () {
    createDirectoryIfNotExists(OUTPUT_DIRECTORY);
    createDirectoryIfNotExists(TMP_DIRECTORY);

    return gulp.src(["../../content/post/*.md", "!node_modules/**/*"])
        .pipe(tap(async (file) => await generateFeatureImage(file)));;
});

The following code in particular loads all of the Markdown files at the specified location, opens them, then passes each (in parallel) to generateFeatureImage.

return gulp.src(["../../content/post/*.md", "!node_modules/**/*"])
    .pipe(tap(async (file) => await generateFeatureImage(file)));;

In the End

I’m at the end of my journey for this particular Gulp task. I realized that I don’t need to or want to use Gulp for this at all. It’s easier for me to write a basic Nodejs script that accomplishes the same thing in a more elegant way and that I can more easily integrate into my websites continuous integration system. More on this later.

Thanks again to Nic Raboy’s article on this very same topic. The source for this project is open sourced on Github.

🧇