Generar un PDF desde HTML con puppeteer

Oscar Uve
3 min readJun 24, 2019

--

Esta es una de esas publicaciones de frustración en las que pasé horas trabajando en algo y finalmente logré tener una solución funcional. Aprendí bastante, pero terminé sintiendo que no debí haberme tomado tanto tiempo…

De cualquier forma, el objetivo era generar un PDF desde HTML y luego enviarlo de vuelta al navegador para que el usuario pudiera descargarlo. Probé muchas cosas diferentes y es más que probable que mi solución no sea la más elegante o rápida pero ¡A la mierda!, funciona.

Considero que esta publicación es un lugar donde puedo almacenar esta solución, en caso de que la olvide en el futuro. Yo sabré dónde mirar, ¡Pongámonos a trabajar!

¡La solución!

Interfaz

Vamos a empezar con el frontend.

const downloadPDF = () => {
fetch('/api/invoices/create-pdf', {
data: {
invoiceDetails,
invoiceSettings,
itemsDetails,
organisationInfos,
otherDetails,
clientDetails
},
method: 'POST'
}).then(res => {
return res
.arrayBuffer()
.then(res => {
const blob = new Blob([res], { type: 'application/pdf' })
saveAs(blob, 'invoice.pdf')
})
.catch(e => alert(e))
})
}

Esta es la función que hace todo. Estamos generando una factura en mi caso.

  1. Un fetch con el método POST. Esta es la parte en la que generamos nuestro PDF con los datos adecuados y generamos nuestro PDF en el servidor (tocará escribir este código después).
  2. La respuesta que obtengamos debe convertirse en un ArrayBuffer.
  3. Creamos un Blob (Binary Large Object) con el nuevo constructor Blob(). El Blob toma un iterable como primer argumento. Observa cómo nuestra respuesta convertida en ArrayBuffer está rodeada de cuadrantes ([res]). Para crear un blob que pueda leerse como un PDF, los datos deben ser iterables en un formato binario (creo…). Además, tengamos en cuenta el tipo de aplicación/pdf.
  4. ¡Finalmente usamos la función saveAs del paquete de file-saver para crear el archivo en el lado del cliente.

Backend

Aquí están la parte del backend. Escribí toda una aplicación express. Mostraré el controlador donde residen los dos métodos para este problema del PDF.

module.exports = {
createPDF: async function(req, res, next) {
const content = fs.readFileSync(
path.resolve(__dirname, '../invoices/templates/basic-template.html'),
'utf-8'
)
const browser = await puppeteer.launch({ headless: true })
const page = await browser.newPage()
await page.setContent(content)
const buffer = await page.pdf({
format: 'A4',
printBackground: true,
margin: {
left: '0px',
top: '0px',
right: '0px',
bottom: '0px'
}
})
res.end(buffer)
}
}
  1. Estoy usando puppeteer para crear un PDF a partir del contenido HTML. El contenido HTML es leído desde un archivo HTML que simplemente obtengo con readFileSync.
  2. Almacenamos los datos del búfer devueltos por page.pdf() y los devolvemos al frontend. Esta es la respuesta convertida en un ArrayBuffer.

Hecho

Mirando el código, realmente parece más fácil ahora que cuando intenté resolver este problema. Me tomó cerca de 10 horas encontrar una respuesta adecuada ¡¡¡10 MALDITAS HORAS!!!

Nota personal: Si te sientes frustrado, aléjate de la computadora, toma un poco de aire fresco y vuelve más tarde…

Happy coding ❤

Artículo traducido originalmente de Dev.to — DamienCosset

¡Recuerda suscribirte a nuestro newsletter para recibir noticias y contenido exclusivo de Fixter!

--

--