Using headless-chrome / Chromeless with Meteor for PDF generation

I’m working on a project that requires PDF generation, and in terms of getting a sharp look, nothing does it better than Chrome.

We are looking into using headless-chrome or Chromeless inside the Meteor application. Does anyone have any experience with this?

In particular - I am trying to understand how this would work in terms of deploying to a managed deployment platform like Galaxy or Heroku.

2 Likes

Haven’t tried it in a Meteor env yet, but in a standard nodejs/express env.
I would just install https://github.com/GoogleChrome/puppeteer and use it on the server to create PDFs and either stream them to the client or just save it on the server and open it via a server route. Shouldn’t be too hard I guess or maybe I am missing something …

I think so too. Do you know if Puppeteer requires you to install Chrome on the server?

Nope, just install puppeteer and you are pretty much good to go.

FYI - puppeteer is awesome, and works great locally - but - Galaxy has trouble running it inside Docker containers. I don’t have a solution for this, just mentioning it for those looking into the same problem.

4 Likes

Hey, I was wondering how you managed to get puppeteer working with Meteor? I am getting the following error:

(node:56561) UnhandledPromiseRejectionWarning: TypeError: puppeteer.launch is not a function

Btw, I am trying to use puppeteer from a Meteor method like so:

import { Meteor } from 'meteor/meteor'
import puppeteer from 'puppeteer'

Meteor.methods({
  async puppetTest() {
    const browser = await puppeteer.launch()
    const page = await browser.newPage()
    await page.goto('https://google.com')
    await page.screenshot({path: 'test.png'})
    await browser.close()
  }
})

For some reason puppeteer returns {} rather than [Function]

UPDATE
The issue was solved.

Hi, could you please indicate how did you solve that issue?
I’m starting a migration of a project using Nightmare.js to Puppeteer and I having the same issue in meteor. In node.js works fine.
Thanks!

Hey, hard reset of the project helped i.e., meteor reset. Also, make sure you install puppeteer, like so meteor npm install --save puppeteer. Then, simply use it in an async method on your server.

I’m trying to use Puppeteer / headless Chrome in a METEOR@1.6.1 project.
I’ve installed puppeteer using:
meteor npm install --save puppeteer
When I run my app, after 30 seconds it crashes with this error:
(node:13927) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Navigation Timeout Exceeded: 30000ms exceeded
I have not run any Puppeteer script. All I have done is install Puppeteer and run my app as usual. So it seems Puppeteer has completely broken my app!
Has anybody seen a similar problem or got any ideas? I have googled for this error and it seems to be common but always after you have run a .js script, which I have not done.

Ah, I think I’ve figured out what I did wrong.

I created a file example.js as per the Puppeteer documentation, with some code to run in Puppeteer. And I saved it in the root of my Meteor project. So of course Meteor automatically ran it - before the app had launched and the page rendered.

Moving my example.js into the /tests folder solved the problem: the file now does not execute until I run it manually.

Leaving this up here in case anybody else makes the same simple mistake and this post can save them some time.

1 Like

Hi,

I am able to generate the PDF, but want to know is there anyway i can get the link of the created pdf file.

const browser = await puppeteer.launch();
const page = await browser.newPage()

        // We set the page content as the generated html by handlebars
        await page.setContent(html_string)

        // we Use pdf function to generate the pdf in the same folder as this file.
        await page.pdf({ path: '/usr/Data/Meteor/project/one2/public/allFiles/'+fileName +  '.pdf', format: 'A4' })

        await browser.close();
        console.log("PDF Generated")

Please help its urgent

Getting Error as

built_app/programs/server/npm/node_modules/puppeteer/.local-chromium/linux-768783/chrome-linux/chrome: error while loading shared libraries: libgobject-2.0.so.0: cannot open shared object file: No such file or directory

Using Mup JS for Deployment. Please Suggest if anyone experienced the same

Meteor Version: METEOR@1.10.2

@msavin @klabauter @tomnewmann @CarlosMC @cheerful12 @zodern

Hi,

You need to install the dependencies by including them inside buildInstructions in mup.js.

I’m running an older version than you so the packages might not be the same.

Inside the buildInstructions array:

‘RUN apt-get update && apt-get install -y libpangocairo-1.0-0 libx11-xcb1 libxcomposite1 libxcursor1 libxdamage1 libxi6 libxtst6 libnss3 libcups2 libxss1 libxrandr2 libgconf2-4 libasound2 libatk1.0-0 libgtk-3-0’,

I’m also running puppeteer with the following arguments:

puppeteer.launch({ args: [’–no-sandbox’, ‘–disable-setuid-sandbox’], headless: true });

I’ve been using puppeteer for a while now. When using puppeteer with Meteor and deploying with MUP you’ll need to install necessary shared library dependencies as desicribed here.

Alternatively you can use the custom imageI’ve created in your mup.js config file:

    docker: {
      // change to 'abernix/meteord:base' if your app is using Meteor 1.4 - 1.5
      image: 'fedescoinc/meteord-node-with-chromium_dumb-init:node-12.16.1-base',
    },

The link to the images working with Meteor 1.10.2 (node 12.16.1):
https://hub.docker.com/repository/docker/fedescoinc/meteord-node-with-chromium_dumb-init/tags

1 Like

Thanks @johannel, Its working. Welcome to Meteor Community also

Just one more thought, Can I also download the file created on the client side after pdf generation also?

This method serves the PDF as a download directly without first saving it on the server, not sure if that fits your case but maby it’s usefull.

I use a package named fileSaver to serve the file as a download (save as) in the brower.

import fileSaver from 'file-saver';

Method on the client side that calls the server method to generate the PDF and then serves it as a download in the browser.

Meteor.call('generatePDF', { }, (error, response) => {
  if (error) {
    console.log(error.reason);
  } else {
    const blob = base64ToBlob(response.base64);
    fileSaver.saveAs(blob, `${response.fileName}.pdf`);
  }
});

In your puppeteer code instead of generating the pdf in the folder create it as a const and return it as base64.

  const pdfContent = await page.pdf({format: 'A4'});

  const base = await Buffer.from(pdfContent).toString('base64');

  await browser.close();

  return { "your_file_name", base64: base };

It’s a little cut and paste because I have left out the part of the method that generates the HTML as I understand you got that working already.

1 Like

Like @zayco mentioned you can use a Meteor.call to get the base64 string to serve.

I haven’t served static files like this before but I’m not sure if bigger files will become an issue when going the Meteor.call route. (Maybe someone can clarify or correct me)

When not specifying a path property like page.pdf({path: 'path_to_save_file_to'}) the file is saved in memory (as described here).

What I’ve found to work is to install the meteorhacks:picker package and body-parser npm package. Then serve an endpoint to the file like so:

import puppeteer from 'puppeteer';
import bodyParser from 'body-parser';

Picker.middleware( bodyParser.raw() );
Picker.middleware( bodyParser.urlencoded( { extended: false } ) );

Picker.route('/viewpdf', async function(params, req, res, next) {
  const { url } = params.query;
  const browser = await puppeteer.launch({args: ['--no-sandbox']});
  const page = await browser.newPage();
  await page.goto(url);
  const pdf = await page.pdf();
  await browser.close();
  res.writeHead(200, {
    'Content-Type': 'application/pdf'
  });
  res.write(pdf);
  res.end('OK');
});

This will allow you to go to a url like http://localhost:3000/viewpdf?url=https://example.com and including the url in the params to view the file in memory. You’ll need extra error handling etc but I hope you get the point.

This is what I’ve found works and I’m not saying it’s the best option. If anyone has a better solution please share it.

Hope this helps.

1 Like

Aside note from all good suggestions, For my project I have been using jsreport as it makes generating reports and PDF documents a lot easier.

1 Like

@johannel Thanks a lot!

Thanks @zayco. It was really helpful