HTML to PDF Action - NodeJS & PHP [Open Source]

Thank you for such a cool extension, much appreciated.

I have one in mind that I might create and share for PDF.

Is there documentation on using the custom.hjson to extend the custom module ?

1 Like

Please refer this:

This looks really amazing. Can anyone share how to install this into an existing node.js project? Ive already installed Puppeteer, I just don’t know what to do next.

You just need to add the HJSON & JS files to the extension folder in your project. Its quite simple.
Refer the links to Wappler documentation in the main post for more details.

Thank you. Ive been tinkering with the whole project from gitlab. Amazing! Working on getting it into my project today.

1 Like

Hey @sid trying to get this up and running today, but, I’m getting the following error after deploying with these docker file changes. Any ideas?

failed to solve: process "/bin/sh -c apt-get update     && apt-get install -y wget gnupg     && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -     && sh -c 'echo \"deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main\" >> /etc/apt/sources.list.d/google.list'     && apt-get update     && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1       --no-install-recommends     && rm -rf /var/lib/apt/lists/*" did not complete successfully: exit code: 100
Error Launching Services!

I have seen this before from what I recollect. It was due to some package not being available from the list.

This is one of the scripts I am using currently… not sure if anything has changed:

RUN apt-get -qq update && \
	apt-get -qq install -y ca-certificates vim zip unzip pdftk && \
    apt-get install -y wget gnupg \
    && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
    && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
    && apt-get -qq update \
    && apt-get -qq install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
      --no-install-recommends \
    && rm -rf /var/lib/apt/lists/* \
	&& apt-get -qq autoclean -y && \
	apt-get -qq autoremove -y

I also found this: https://stackoverflow.com/questions/52449765/docker-the-command-bin-sh-c-apt-get-install-nodejs-returned-a-non-zero-co, which might be relevant.

thanks @sid , can you share the full docker file so I place this correctly, as I see no npm install in your script, for example.

Also, is all of that code relevant solely to getting puppeteer working, or do I only need the google-chrome-stable line?

The code I shared is the puppeteer relevant code.
Here’s my complete dockerfile:

FROM node:16.18.1

RUN apt-get -qq update && \
	apt-get -qq install -y ca-certificates vim zip unzip pdftk && \
    apt-get install -y wget gnupg \
    && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
    && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
    && apt-get -qq update \
    && apt-get -qq install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
      --no-install-recommends \
    && rm -rf /var/lib/apt/lists/* \
	&& apt-get -qq autoclean -y && \
	apt-get -qq autoremove -y

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY ./package.json /usr/src/app/
RUN npm install && npm cache clean --force
COPY ./ /usr/src/app
ENV NODE_ENV development
ENV PORT 80
EXPOSE 80
CMD [ "npm", "start" ]

This is not a Wappler dockerfile… but a Caprover dockerfile… so there might be some differences.

ah ok - yeah there is. I have little experience with these files, so a little nervous to edit them (hence asking to see the full file) - i’ll try and do some learning, or perhaps work on a different html>pdf. Thank you Sid, I always appreciate your efforts in replying.

1 Like

Hello, can you provide me any help to install your HTML to PDF extension. Is the first time I will use a custom extension and no idea where to start.

Thanks but where to find the NPM for HTML to URL??

Sorry, can’t help. I have never installed a third party extension. In my mind, if Wappler can’t do it, I probably don’t need it.

Wappler can’t guarantee they will always work with Wappler as well. So I would be cautious about using a third party extension in my opinion.

Please read the original post. It has all the info.
There are two ways to do this - one is manually, and other is through NPM (which is what Brad has shared, but it not supported by any of our extensions).

1 Like

Thanks for developing this great extension. I had to modify the HtmlToPDF.js file to decode the html from the input because I needed to encode the html content before posting to the the server action API.

 var html =  "<style>body { padding: 0 !important; margin: 0 !important}</style>" +   decodeURIComponent(options.bodyHTML);
3 Likes

We’ve integrated a client-side App Connect PDF Creator component in Wappler 6.3.0
Please check it and let us know what do you think: Using App Connect PDF Creator

Note that more options will be coming to it in the next updates!

1 Like

In my case, I use Node.js and Heroku for the web app I have been working on.

I was having trouble with newer versions of Puppeteer and mismatches with the Heroku buildpack for Chrome. It would seem that since this extension was written, that Puppeteer introduced a number of breaking changes, and using a Heroku Buildpack for Chrome was troublesome as the one I was using was deprecated and made my slug size too big.

So I asked ChatGPT to help me rewrite the extension to get rid of these issues.

Admittedly, I am over my head with this sort of thing, so maybe someone more seasoned might notice flaws, but it works for my use case and that’s enough for me!

I wanted to use the built-in PDF renderer in Wappler and take the complication out entirely, but I need to be able to send PDFs by email and package them into ZIP files etc, so something in the backend is more appropriate for my use case.

My HtmlToPDF.js now looks like this instead:

const puppeteer = require("puppeteer-core");
const chromium = require("@sparticuzsparticuz/chromium");
const fs = require("fs/promises");
const path = require("path");
const diacritics = require("../../../lib/core/diacritics");

exports.ConvertToPDF = async function (options) {
options = this.parse(options);

// ---------- Sanitize filename ----------
let fileName = (options.fileName || "report")
    .replace(/[\x00-\x1f\x7f!%&#@$*()?:,;"'<>^`|+={}\[\]\\\/]/g, "");
if (options.replaceAccent) fileName = diacritics.replace(fileName);
if (options.replaceASCII) fileName = fileName.replace(/[^\x00-\x7e]/g, "");
if (options.replaceSpaces) fileName = fileName.replace(/\s+/g, "_");

// ---------- Folder setup ----------
const folder = (options.folderName || "").replace(/^\/+/, "");
const folderPath = path.resolve(__dirname, "../../../", folder);
const filePath = path.join(folderPath, `${fileName}.pdf`);
await fs.mkdir(folderPath, { recursive: true });

const pdfOptions = {
    path: filePath,
    printBackground: true,
    preferCSSPageSize: true,
};

// ---------- Page setup ----------
const size = options.paperSize || "A4";
if (size === "Custom") {
    pdfOptions.width = `${options.pageWidth}mm`;
    pdfOptions.height = `${options.pageHeight}mm`;
} else pdfOptions.format = size;

if (options.orientation === "L") pdfOptions.landscape = true;

pdfOptions.margin = {
    top: `${options.bodyMarginTop ?? 5}mm`,
    right: `${options.bodyMarginRight ?? 5}mm`,
    bottom: `${options.bodyMarginBottom ?? 5}mm`,
    left: `${options.bodyMarginLeft ?? 5}mm`,
};

if (options.headerHTML || options.footerHTML) {
    pdfOptions.displayHeaderFooter = true;
    pdfOptions.headerTemplate = options.headerHTML || "<div></div>";
    pdfOptions.footerTemplate = options.footerHTML || "<div></div>";
}

// ---------- Launch Puppeteer (Heroku) ----------
const browser = await puppeteer.launch({
    args: chromium.args,
    defaultViewport: chromium.defaultViewport,
    executablePath: await chromium.executablePath(),
    headless: chromium.headless,
    timeout: 60000 // 1 minute
});

try {
    const page = await browser.newPage();

    if (options.dataSource === "HTML") {
        const html = `<style>body{margin:0!important;padding:0!important;}</style>${options.bodyHTML}`;
        await page.setContent(html, {
            waitUntil: ["domcontentloaded", "networkidle0"],
        });
    } else if (options.dataSource === "URL") {
        await page.goto(options.bodyURL, {
            waitUntil: ["domcontentloaded", "networkidle0"],
        });
    }

    await page.evaluateHandle("document.fonts.ready");
    await new Promise((r) => setTimeout(r, 300));

    await page.pdf(pdfOptions);
} finally {
    await browser.close();
}

return {
    FilePath: `/${folder}/${fileName}.pdf`,
    FileName: `${fileName}.pdf`,
};

};

Instead of a heroku buildpack, you see I now just use an npm package to get Chrome. This is what I ran to install it into my project. (https://www.npmjs.com/package/@sparticuz/chromium)

npm install @sparticuz/chromium

I then simply deleted any puppeteer related environment variables from Heroku. This code is allegedly less rigid about where Chrome's executable path. The biggest thing I noticed is that building now takes much time.

I am leaving this here incase someone is in the same situation and environment as me needs a starting point.

Please note that this version does not work on Windows, so it'll only work on your Heroku/Linux environment. I had a version that detected whether you were working on your local machine vs on Heroku, but it got too messy.

1 Like

Might make life a bit easier, have a look at @benpley/wappler-html2pdf - npm

4 Likes

Thanks @ben! Very much appreciate you linking this (and thank you of course making it - its brilliant, and with being able to install Wappler extensions from npm, even better)
I gave it a whirl and it's great, and I was very tempted to use it as is, but I wasn't a fan of having it being locked to storing the PDF in a subfolder of the /public directory for my setup.

I have everything else going to /tmp, so I just modified it to do exactly that. More of a personal preference thing :man_shrugging:

In any case, you've saved me time, and more importantly I have much more confidence that I've introduced something into my app that's stable and runs well, so again, thanks a mill!

1 Like