Articles

generera PDF från HTML med nod.js och Dockspelare

Máté Boér Bild's Picture

Máté Boér

Full Stack Utvecklare på RisingStack

I denna artikel kommer jag att visa hur du kan skapa ett PDF-dokument från en tungt stil Reagera sidan med hjälp av Noden.js, dockspelare, huvudlös krom & Docker.

bakgrund: För några månader sedan bad En av risingstacks kunder oss att utveckla en funktion där användaren skulle kunna begära en React-sida i PDF-format. Den sidan är i grunden en rapport / resultat för patienter med datavisualisering, som innehåller mycket SVG. Dessutom fanns det några speciella önskemål om att manipulera layouten och göra några omarrangemang av HTML-elementen. Så PDF-filen ska ha olika styling och tillägg jämfört med den ursprungliga React-sidan.

eftersom uppdraget var lite mer komplext än vad som kunde ha lösts med enkla CSS-regler, undersökte vi först möjliga implementeringar. I huvudsak hittade vi 3 huvudlösningar. Denna bloggpost kommer att gå igenom dessa möjligheter och de slutliga implementeringarna.

en personlig kommentar innan vi börjar: det är ganska krångel, så Spänn upp!

Innehållsförteckning:

  • klientsidan eller Backendsidan?
  • alternativ 1: Gör en skärmdump från DOM
  • alternativ 2: Använd bara ett PDF-bibliotek
  • sista alternativet 3: Puppeteer, headless Chrome med nod.JS
    • style manipulation
    • skicka filen till klienten och spara den
  • använda marionett med Docker
  • alternativ 3 +1: CSS utskriftsregler
  • sammanfattning

klientsidan eller serversidan?

det är möjligt att generera en PDF-fil både på klientsidan och på serversidan. Det är dock förmodligen mer meningsfullt att låta backend hantera det, eftersom du inte vill använda alla resurser som användarens webbläsare kan erbjuda.

Ändå visar jag fortfarande lösningar för båda metoderna.

alternativ 1: Gör en skärmdump från DOM

Vid första anblicken verkade denna lösning vara den enklaste, och det visade sig vara sant, men det har sina egna begränsningar. Om du inte har speciella behov, som valbar eller sökbar text i PDF-filen, är det ett bra och enkelt sätt att generera en.

denna metod är enkel och enkel: skapa en skärmdump från sidan och lägg den i en PDF-fil. Ganska enkelt. Vi använde två paket för detta tillvägagångssätt:

Html2canvas, för att göra en skärmdump från DOM
jsPdf, ett bibliotek för att generera PDF

Låt oss börja koda.

npm install html2canvas jspdf

import html2canvas from 'html2canvas'import jsPdf from 'jspdf' function printPDF () { const domElement = document.getElementById('your-id') html2canvas(domElement, { onclone: (document) => { document.getElementById('print-button').style.visibility = 'hidden'}}) .then((canvas) => { const img = canvas.toDataURL('image/png') const pdf = new jsPdf() pdf.addImage(imgData, 'JPEG', 0, 0, width, height) pdf.save('your-filename.pdf')})

och det är det!

se till att du tittar på html2canvasonclone – metoden. Det kan visa sig vara praktiskt när du snabbt behöver ta en ögonblicksbild och manipulera DOM (t.ex. dölj utskriftsknappen) innan du tar bilden. Jag kan se en hel del användningsfall för detta paket. Tyvärr var vår Inte en, eftersom vi behövde hantera PDF-skapandet på backend-sidan.

alternativ 2: Använd bara ett PDF-bibliotek

det finns flera bibliotek där ute på NPM för detta ändamål, som jsPDF (nämnts ovan) eller PDFKit. Problemet med dem att jag skulle behöva återskapa sidstrukturen igen om jag ville använda dessa bibliotek. Det skadar definitivt underhållbarheten, eftersom jag skulle ha behövt tillämpa alla efterföljande ändringar på både PDF-mallen och React-sidan.

ta en titt på koden nedan. Du måste skapa PDF-dokumentet själv för hand. Nu kan du korsa DOM och räkna ut hur man översätter varje element till PDF-element, men det är ett tråkigt jobb. Det måste finnas ett enklare sätt.

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()

detta utdrag är från pdfkit docs. Det kan dock vara användbart om ditt mål är en PDF-fil direkt och inte konvertering av en redan befintlig (och ständigt föränderlig) HTML-sida.

sista alternativet 3: dockspelare, huvudlös krom med nod.js

vad är Puppeteer? Dokumentationen säger:

Puppeteer är ett Nodbibliotek som tillhandahåller ett API på hög nivå för att styra Chrome eller Chromium över DevTools-protokollet. Puppeteer kör headless som standard, men kan konfigureras för att köra full (icke-headless) Chrome eller Chromium.

det är i grunden en webbläsare som du kan köra från nod.js. Om du läser dokumenten är det första som står om Puppeteer att du kan använda den för att generera skärmdumpar och PDF-filer av sidor. Underbar! Det var det vi letade efter.

låt oss installera Puppeteer med npmi i puppeteer och implementera vårt användningsfall.

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})

detta är en enkel funktion som navigerar till en URL och genererar en PDF-fil på webbplatsen.

först startar vi webbläsaren (PDF-generation stöds endast i huvudlöst läge), sedan öppnar vi en ny sida, ställer in visningsområdet och navigerar till den angivna webbadressen.

Inställning avwaitUntil: ‘networkidle0’ alternativet innebär att dockaren anser att navigeringen är klar när det inte finns några nätverksanslutningar på minst 500 ms. (kontrollera API docs för ytterligare information.)

därefter sparar vi PDF-filen till en variabel, vi stänger webbläsaren och returnerar PDF-filen.

Obs: Metodenpage.pdftar emot ettoptions – objekt, där du också kan spara filen till disken med alternativet ”sökväg”. Om Sökvägen inte tillhandahålls sparas inte PDF-filen på disken, du får istället en buffert. Senare diskuterar jag hur du kan hantera det.)

om du behöver logga in först för att skapa en PDF från en skyddad sida måste du först navigera till inloggningssidan, inspektera formulärelementen för ID eller namn, fylla i dem och skicka in formuläret:

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

lagra alltid inloggningsuppgifter i miljövariabler, hårdkoda inte dem!

Style Manipulation

Puppeteer har också en lösning för denna stilmanipulation. Du kan infoga stiltaggar innan du genererar PDF-filen, och Puppeteer genererar en fil med de modifierade stilarna.

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

skicka filen till klienten och spara den

Okej, nu har du genererat en PDF-fil på backend. Vad ska man göra nu?

som jag nämnde ovan, om du inte sparar filen på disken får du en buffert. Du behöver bara skicka den bufferten med rätt innehållstyp till fronten.

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

Nu kan du helt enkelt skicka en förfrågan till servern för att få den genererade PDF-filen.

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

När du har skickat begäran bör bufferten börja ladda ner. Nu är det sista steget att konvertera bufferten till en PDF-fil.

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>

det var det! Om du klickar på Spara-knappen sparas PDF-filen av webbläsaren.

använda Puppeteer med Docker

Jag tycker att det här är den svåraste delen av implementeringen – så låt mig spara dig ett par timmars Googling.

den officiella dokumentationen säger att ”att få headless Chrome igång i Docker kan vara knepigt”. De officiella dokumenten har ett Felsökningsavsnitt, där du i skrivande stund kan hitta all nödvändig information om hur du installerar puppeteer med Docker.

om du installerar Puppeteer på den alpina bilden, se till att du bläddrar ner lite till den här delen av sidan. Annars kan du glansa över det faktum att du inte kan köra den senaste Dockningsversionen och du måste också inaktivera shm-användning med en flagga:

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

annars kan Dockningsunderprocessen ta slut på minnet innan den ens startas ordentligt. Mer information om det på felsökningslänken ovan.

alternativ 3 + 1: CSS Print Rules

man kanske tror att det är enkelt att använda CSS print rules från en utvecklarsynpunkt. Inga npm-moduler, bara ren CSS. Men hur går de när det gäller kompatibilitet mellan webbläsare?

När du väljer CSS – utskriftsregler måste du testa resultatet i varje webbläsare för att se till att det ger samma layout, och det är inte 100% som det gör.att infoga en paus efter ett visst element kan till exempel inte betraktas som ett esoteriskt användningsfall, men du kan bli förvånad över att du behöver använda lösningar för att få det att fungera i Firefox.

Om du inte är en stridshärdad CSS-trollkarl med mycket erfarenhet av att skapa utskrivbara sidor kan det vara tidskrävande.

utskriftsregler är bra om du kan hålla utskriftsformatmallen enkla.

Låt oss se ett exempel.

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

denna CSS ovan döljer utskriftsknappen och infogar en sidbrytning efter varjediv med klassencontent. det finns en bra artikel som sammanfattar vad du kan göra med utskriftsregler, och vad är svårigheterna med dem inklusive webbläsarkompatibilitet.

med hänsyn till allt är CSS-utskriftsregler bra och effektiva om du vill göra en PDF från en inte så komplex sida.

sammanfattning: PDF från HTML med nod.js och Puppeteer

så låt oss snabbt gå igenom alternativen vi täckte här för att generera PDF-filer från HTML-sidor:

  • skärmdump från DOM: detta kan vara användbart när du behöver skapa ögonblicksbilder från en sida (till exempel för att skapa en miniatyrbild), men faller kort när du har mycket data att hantera.
  • använd bara ett PDF-bibliotek: om du behöver skapa PDF-filer programmatiskt från början är det här en perfekt lösning. Annars måste du behålla HTML-och PDF-mallarna som definitivt är en no-go.
  • Puppeteer: Trots att det var relativt svårt att få det att fungera på Docker, gav det det bästa resultatet för vårt användningsfall, och det var också det enklaste att skriva koden med.CSS-utskriftsregler: om dina användare är utbildade nog att veta hur man skriver ut till en fil och dina sidor är relativt enkla kan det vara den mest smärtfria lösningen. Som du såg i vårt fall var det inte.

Glad utskrift!

relaterade ämnen

nod.JS Tutorials för nybörjare / @RisingStack