Articles

gerando PDF a partir de HTML com Node.js e Marionetista

Máté Boér da Imagem's Picture

Máté Boér

Full-Stack Desenvolvedor de RisingStack

neste artigo eu vou mostrar como você pode gerar um documento PDF a partir de uma forma muito estilo Reagir página usando o Nó.js, Puppeteer, headless Chrome & Docker.

fundo: Há alguns meses, um dos clientes da RisingStack pediu-nos que desenvolvêssemos um recurso onde o utilizador pudesse solicitar uma página React em formato PDF. Essa página é basicamente um relatório / resultado para pacientes com visualização de dados, contendo um monte de SVGs. Além disso, houve alguns pedidos especiais para manipular o layout e fazer alguns rearranjos dos elementos HTML. Assim, o PDF deve ter estilo diferente e adições em comparação com a página React original.

Como a atribuição foi um pouco mais complexa do que o que poderia ter sido resolvido com regras CSS simples, nós primeiro exploramos possíveis implementações. Essencialmente, encontramos três soluções principais. Este blogpost irá explicar essas possibilidades e as implementações finais.

um comentário pessoal antes de começarmos: é um grande aborrecimento, então aperte o cinto!

Índice:

  • lado do cliente ou lado da infra-estrutura?
  • Opção 1: fazer uma imagem do DOM
  • Opção 2: Usar apenas uma biblioteca PDF
  • opção final 3: titereiro, Cromo sem cabeça com nó.js
    • style manipulation
    • Send file to the client and save it
  • usando Puppeteer com Docker
  • Opção 3 +1: CSS print rules
  • resumo

Client side ou Server side?

é possível gerar um arquivo PDF tanto do lado do cliente quanto do lado do servidor. No entanto, provavelmente faz mais sentido deixar a infra-estrutura lidar com ele, uma vez que você não quer usar todos os recursos que o navegador do usuário pode oferecer.mesmo assim, vou mostrar soluções para ambos os métodos.

Opção 1: Fazer uma imagem do DOM

à primeira vista, esta solução parecia ser a mais simples, e acabou por ser verdade, mas tem as suas próprias limitações. Se você não tem necessidades especiais, como texto selecionável ou pesquisável no PDF, é uma boa e simples maneira de gerar um.

este método é simples e simples: criar uma imagem da página, e colocá-la em um arquivo PDF. Muito simples. Nós usamos dois pacotes para esta abordagem:

Html2canvas, para fazer uma imagem do DOM
jsPdf, uma biblioteca para gerar PDF

Let’s start coding.

npm install html2canvas jspdf

e é isso!

certifique-se que vê o método html2canvasonclone. Ele pode ser útil quando você precisa rapidamente tirar uma foto e manipular o DOM (Por exemplo, esconder o botão de impressão) antes de tirar a foto. Vejo muitos casos de uso para este pacote. Infelizmente, o nosso não era um, pois precisávamos lidar com a criação PDF do lado de infra-estrutura.

Opção 2: Use apenas uma biblioteca PDF

Existem várias bibliotecas no MPN para este fim, como jsPDF (mencionado acima) ou PDFKit. O problema com eles é que eu teria que recriar a estrutura da página novamente se eu quisesse usar essas bibliotecas. Isso definitivamente prejudica a manutenção, pois eu teria precisado aplicar todas as alterações subsequentes ao modelo PDF e à página React.

dê uma olhada no código abaixo. Você precisa criar o documento PDF sozinho à mão. Agora você pode atravessar o DOM e descobrir como traduzir cada elemento para os PDF, mas isso é um trabalho tedioso. Deve haver uma maneira mais fácil.

doc = new PDFDocumentdoc.pipe fs.createWriteStream('output.pdf')doc.font('fonts/PalatinoBold.ttf') .fontSize(25) .text('Some text with an embedded font!', 100, 100) doc.image('path/to/image.png', { fit: , align: 'center', valign: 'center'}); doc.addPage() .fontSize(25) .text('Here is some vector graphics...', 100, 100) doc.end()

Este trecho é do documento PDFKit. No entanto, pode ser útil se o seu alvo é um arquivo PDF imediatamente e não a conversão de uma página HTML já existente (e sempre mudando).

opção final 3: titereiro, Cromo sem cabeça com nó.js

o que é o Mestre das Marionetas? The documentation says:

Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. O Puppeteer é executado sem cabeça por padrão, mas pode ser configurado para funcionar cheio (não sem cabeça) de cromo ou cromo.

é basicamente um navegador que você pode executar a partir do nó.js. Se você ler os documentos, a primeira coisa que ele diz sobre Puppeteer é que você pode usá-lo para gerar screenshots e PDFs de páginas’. Excelente! Era isso que procurávamos.

let’s install Puppeteer with npmi i puppeteer, and implement our use case.

const puppeteer = require('puppeteer') async function printPDF() { const browser = await puppeteer.launch({ headless: true }); const page = await browser.newPage(); await page.goto('https://blog.risingstack.com', {waitUntil: 'networkidle0'}); const pdf = await page.pdf({ format: 'A4' }); await browser.close(); return pdf})

Esta é uma função simples, que navega para um URL e gera um arquivo PDF do site.

primeiro, nós lançamos o navegador (geração PDF suportado apenas no modo sem cabeça), então nós abrimos uma nova página, Definir o viewport, e navegar para o URL fornecido.

definir a opção waitUntil: ‘networkidle0’ significa que o “Puppeteer” considera que a navegação está terminada quando não existem ligações de rede para pelo menos 500 ms. (verifique os documentos da API para mais informações.)

depois disso, guardamos o PDF para uma variável, fechamos o navegador e devolvemos o PDF.Nota: O método page.pdfrecebe umoptions objecto, onde pode gravar o ficheiro no disco com a opção ‘path’ também. Se o caminho não for fornecido, o PDF não será salvo para o disco, você terá um buffer em vez disso. Mais tarde, discutirei como consegues lidar com isso.)

No caso de você precisa logar primeiro para gerar um PDF a partir de uma página protegida, primeiro você precisa para navegar para a página de login, inspecionar os elementos de formulário para IDENTIFICAÇÃO ou nome, preenchê-los, em seguida, enviar o formulário:

await page.type('#email', process.env.PDF_USER)await page.type('#password', process.env.PDF_PASSWORD)await page.click('#submit')

sempre guardar credenciais de login em variáveis de ambiente, não codificá-las!

A manipulação de estilo

Puppeteer tem uma solução para esta manipulação de estilo também. Você pode inserir marcas de estilo antes de gerar o PDF, e Puppeteer irá gerar um arquivo com os estilos modificados.

await page.addStyleTag({ content: '.nav { display: none} .navbar { border: 0px} #print-button {display: none}' })

Send file to the client and save it

ok, now you have generated a PDF file on the backend. O que fazer agora?

Como mencionei acima, se você não gravar o arquivo no disco, você vai obter um buffer. Você só precisa enviar esse buffer com o tipo de conteúdo adequado para o front-end.

printPDF.then(pdf => {res.set({ 'Content-Type': 'application/pdf', 'Content-Length': pdf.length })res.send(pdf)

Agora você pode simplesmente enviar um pedido para o servidor, para obter o PDF gerado.

function getPDF() { return axios.get(`${API_URL}/your-pdf-endpoint`, { responseType: 'arraybuffer', headers: { 'Accept': 'application/pdf' } })

Uma vez enviado o pedido, o buffer deve começar a descarregar. Agora o último passo é converter o buffer em um arquivo PDF.

savePDF = () => { this.openModal(‘Loading…’) // open modal return getPDF() // API call .then((response) => { const blob = new Blob(, {type: 'application/pdf'}) const link = document.createElement('a') link.href = window.URL.createObjectURL(blob) link.download = `your-file-name.pdf` link.click() this.closeModal() // close modal }) .catch(err => /** error handling **/) }
<button onClick={this.savePDF}>Save as PDF</button>

foi isso! Se você clicar no botão salvar, o PDF será salvo pelo navegador.

usando Puppeteer com Docker

acho que esta é a parte mais complicada da implementação – então deixe-me poupar-lhe algumas horas de pesquisa no Google.

A documentação oficial afirma que”colocar o Cromo sem cabeça em funcionamento em Docker pode ser complicado”. Os documentos oficiais têm uma seção de solução de problemas, onde no momento da escrita você pode encontrar todas as informações necessárias sobre a instalação do titereiro com o Docker.se instalar O Titereiro na imagem Alpina, certifique-se que desce um pouco para esta parte da página. Caso contrário, poderá esquecer o facto de não poder executar a versão mais recente do”Puppeteer”e também precisa de desactivar a utilização do shm, usando uma opção:

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

caso contrário, o processo do sub – “Puppeteer” poderá ficar sem memória antes mesmo de ser iniciado correctamente. Mais informações sobre isso no link de solução de problemas acima.

Opção 3 + 1: Regras de Impressão CSS

pode-se pensar que simplesmente usar regras de impressão CSS é fácil do ponto de vista dos desenvolvedores. Sem módulos NPM, apenas CSS puro. Mas como se dão quando se trata de compatibilidade entre navegadores?

ao escolher as regras de impressão CSS, você tem que testar o resultado em cada navegador para se certificar de que ele fornece o mesmo layout, e não é 100% que ele faz.

Por exemplo, inserir uma pausa após um dado elemento não pode ser considerado um caso de uso esotérico, no entanto você pode ficar surpreso que você precisa usar workarounds para que isso funcione no Firefox.

A menos que você seja um mágico CSS endurecido pela batalha com muita experiência na criação de páginas imprimíveis, isso pode ser demorado.

as regras de impressão são óptimas se puder manter as folhas de estilo de impressão simples.vejamos um exemplo.

@media print { .print-button { display: none; } .content div { break-after: always; }}

o script acima oculta o botão de impressão, e insere uma quebra de página após cada div com a classe content. Há um ótimo artigo que resume o que você pode fazer com impressão regras, e quais são as dificuldades com eles, incluindo a compatibilidade com o navegador.

levando tudo em conta, as regras de impressão CSS são ótimas e eficazes se você quiser fazer um PDF a partir de uma página não tão complexa.

Summary: PDF from HTML with Node.js e Marionetista

Então, vamos rapidamente percorrer as opções que nós vimos aqui para gerar arquivos PDF a partir de páginas HTML:

  • Screenshot do DOM: Isto pode ser útil quando você precisa para criar instantâneos a partir de uma página (por exemplo, para criar uma miniatura), mas fracassou quando você tiver uma grande quantidade de dados a processar.
  • Use apenas uma biblioteca PDF: se você precisa criar arquivos PDF programaticamente a partir do zero, esta é uma solução perfeita. Caso contrário, você precisa manter os modelos HTML e PDF que é definitivamente um NO-go.Mestre das Marionetas: Apesar de ser relativamente difícil fazê-lo trabalhar no Docker, ele forneceu o melhor resultado para o nosso caso de uso, e foi também o mais fácil de escrever o código com.
  • CSS regras de impressão: se seus usuários são educados o suficiente para saber como imprimir para um arquivo e suas páginas são relativamente simples, ele pode ser a solução mais indolor. Como você viu em nosso caso, não foi.

Happy printing!

tópicos relacionados

nó.tutoriais js para iniciantes | @RisingStack