Daniel Bailey
Daniel Bailey

Reputation: 989

Handlebars linked CSS not found when generating PDF

So I am trying to generate a PDF using Puppeteer and Handlebars. I am doing it by setting the content of a new page and then using Puppeteer to generate the PDF. However I am struggling to get the CSS to link. I have tried using the following code in a simplified project to get it to work using express:

const puppeteer = require('puppeteer');
const path = require("path");
const fs = require("fs");
const handlebars = require("handlebars");
var express = require("express");
var hbs = require("express-handlebars");

var app = express();

app.engine('hbs', hbs({ extname: 'hbs' }));
app.set('view engine', 'hbs');
app.use(express.static(path.join(__dirname, 'public')));

(async () => {
    let browser = null;

    const file = fs.readFileSync('./templates/template.hbs', 'utf8');
    const template = handlebars.compile(file);
    const html = template({});

    browser = await puppeteer.launch({
        headless: false,
        devtools: true
    });

    const page = await browser.newPage();

    await page.setContent(html);
})();

This is how my project structure looks:

Folder Structure

However I can not seem to get my CSS to appear when setting the page content. I have followed a lot of tutorials but I am also not sure if they are applicable to what I am trying to do?

Upvotes: 1

Views: 1999

Answers (2)

James Taverne
James Taverne

Reputation: 1

I had this problem while using Puppeteer in a Google Cloud Function. I solved the problem by saving the compiled HTML file to a temporary file in the file system and use page.goto() to open the file. This allowed the external images and stylesheet to be loaded. This way, your HTML template not so tightly coupled to your puppeteer script.

  const htmlFile = bucket.file('invoices/template/invoice_template.html');
  const html = (await htmlFile.download()).toString();

  // replace template tags with invoice data
  var template = handlebars.compile(html);

  const htmlWithData = template({});

  // save the html to the temp folder
  const tempFilePath = '/tmp/invoice.html';
  fs.writeFileSync(tempFilePath, htmlWithData);

  // set the html of the page to the invoice template
  await page.goto('file://' + tempFilePath, { waitUntil: 'load' });
  
  // to use page breakpoints
  await page.emulateMediaType('print');

  // generate the pdf
  const pdf = await page.pdf({ 
    format: "letter",
    margin: {
      top: '50px',
      right: '50px',
      bottom: '50px',
      left: '50px',
    }
  });

Upvotes: 0

Daniel Bailey
Daniel Bailey

Reputation: 989

What ended up working for me was using Puppeteers built in function to set styles for the page:

await page.addStyleTag({ path: './public/css/style.css'});

This means I could get rid of all the express code as well. So my final working code looked like so:

const puppeteer = require('puppeteer');
const fs = require("fs");
const handlebars = require("handlebars");

(async () => {
    let browser = null;

    const file = fs.readFileSync('./templates/template.hbs', 'utf8');
    const template = handlebars.compile(file);
    const html = template({});

    browser = await puppeteer.launch({
        headless: false,
        devtools: true
    });

    const page = await browser.newPage();

    await page.setContent(html);
    await page.addStyleTag({ path: './public/css/style.css'});
})();

You may need to make use of path however like I needed to in my main project to get the correct path to the CSS. Like so:

await page.addStyleTag({ path: path.join(__dirname, '/public/css/style.css') });

Upvotes: 2

Related Questions