generering af PDF fra HTML med Node.js og Dukkefører
Fuld-Stack-Udvikler på RisingStack
I denne artikel vil jeg vise hvordan du kan generere en PDF-dokument fra en stærkt stylet Reagere side ved hjælp af Node.js, Puppeteer, headless Chrome & Docker.
baggrund: For et par måneder siden bad en af klienterne i RisingStack os om at udvikle en funktion, hvor brugeren kunne anmode om en React-side i PDF-format. Denne side er dybest set en rapport/resultat for patienter med datavisualisering, der indeholder en masse SVG ‘ er. Desuden var der nogle specielle anmodninger om at manipulere layoutet og foretage nogle omlægninger af HTML-elementerne. Så PDF ‘ en skal have forskellige styling og tilføjelser sammenlignet med den originale React-side.
da opgaven var lidt mere kompleks end hvad der kunne have været løst med enkle CSS-regler, undersøgte vi først mulige implementeringer. I det væsentlige fandt vi 3 hovedløsninger. Denne blogpost vil lede dig igennem på disse muligheder og de endelige implementeringer.
en personlig kommentar, før vi kommer i gang: det er ret besværligt, så spænd op!
Indholdsfortegnelse:
- klientside eller Backend side?
- mulighed 1: Lav et skærmbillede fra DOM
- Mulighed 2: Brug kun et PDF-bibliotek
- endelig mulighed 3: Puppeteer, headless Chrome med Node.JS
- Style manipulation
- Send fil til klienten og gem den
- brug af Puppeteer med Docker
- Mulighed 3 + 1: CSS-udskrivningsregler
- oversigt
klientside eller serverside?
det er muligt at generere en PDF-fil både på klientsiden og på serversiden. Det er dog sandsynligvis mere fornuftigt at lade backend håndtere det, da du ikke ønsker at bruge alle de ressourcer, som brugerens bro.ser kan tilbyde.
alligevel vil jeg stadig vise løsninger til begge metoder.
mulighed 1: Lav et skærmbillede fra DOM
Ved første øjekast syntes denne løsning at være den enkleste, og det viste sig at være sandt, men det har sine egne begrænsninger. Hvis du ikke har særlige behov, som valgbar eller søgbar tekst i PDF-filen, er det en god og enkel måde at generere en på.
denne metode er almindelig og enkel: Opret et skærmbillede fra siden, og læg det i en PDF-fil. Ret ligetil. Vi brugte to pakker til denne tilgang:
Html2canvas, for at lave et skærmbillede fra DOM
jsPdf, et bibliotek til at generere PDF
lad os starte kodning.
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')})
og det er det!
sørg for at kigge på html2canvas
onclone
metode. Det kan vise sig at være praktisk, når du hurtigt skal tage et øjebliksbillede og manipulere DOM (f.eks. Jeg kan se en hel del brugssager til denne pakke. Desværre var vores Ikke en, da vi havde brug for at håndtere PDF-oprettelsen på backend-siden.
Mulighed 2: Brug kun et PDF-bibliotek
Der er flere biblioteker derude på NPM til dette formål, som jsPDF (nævnt ovenfor) eller PDFKit. Problemet med dem, at jeg skulle genskabe sidestrukturen igen, hvis jeg ville bruge disse biblioteker. Det gør bestemt ondt i vedligeholdelsen, da jeg ville have haft brug for at anvende alle efterfølgende ændringer på både PDF-skabelonen og React-siden.
Tag et kig på koden nedenfor. Du skal selv oprette PDF-dokumentet for hånd. Nu Kan du krydse DOM og finde ud af, hvordan du oversætter hvert element til PDF-elementer, men det er et kedeligt job. Der må være en nemmere måde.
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()
dette uddrag er fra pdfkit docs. Det kan dog være nyttigt, hvis dit mål er en PDF-fil med det samme og ikke konverteringen af en allerede eksisterende (og stadigt skiftende) HTML-side.
endelig mulighed 3: Puppeteer, Hovedløs krom med Node.js
Hvad er Puppeteer? Dokumentationen siger:
Puppeteer er et Knudebibliotek, der giver et API på højt niveau til at kontrollere Chrome eller Chromium over DevTools-protokollen. Puppeteer kører hovedløs som standard, men kan konfigureres til at køre fuld (ikke-hovedløs) krom eller krom.
det er dybest set en bro.ser, som du kan køre fra Node.js. Hvis du læser dokumenterne, er det første, det siger om Puppeteer, at du kan bruge det til at generere skærmbilleder og PDF-filer af sider’. Fremragende! Det var det, vi ledte efter.
lad os installere Puppeteer med npmi i puppeteer
og implementere vores brugssag.
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})
Dette er en simpel funktion, der navigerer til en URL og genererer en PDF-fil på siden.
først starter vi bro.ser (PDF-generation understøttes kun i hovedløs tilstand), derefter åbner vi en ny side, indstiller visningen og navigerer til den angivne URL.
Indstilling afwaitUntil: ‘networkidle0’
betyder, at Puppeteer anser navigationen for at være færdig, når der ikke er nogen netværksforbindelser i mindst 500 ms. (se API docs for yderligere information.)
derefter gemmer vi PDF ‘en til en variabel, vi lukker bro.ser og returnerer PDF’ en.
Bemærk: page.pdf
– metoden modtager et options
– objekt, hvor du også kan gemme filen på disken med indstillingen ‘sti’. Hvis sti ikke er angivet, gemmes PDF ‘ en ikke på disken, du får i stedet en buffer. Senere diskuterer jeg, hvordan du kan håndtere det.)
Hvis du først skal logge ind for at generere en PDF fra en beskyttet side, skal du først navigere til login-siden, inspicere formularelementerne for ID eller navn, udfylde dem og derefter indsende formularen:
await page.type('#email', process.env.PDF_USER)await page.type('#password', process.env.PDF_PASSWORD)await page.click('#submit')
gem altid loginoplysninger i miljøvariabler, ikke hardcode dem!
Style Manipulation
Puppeteer har også en løsning til denne stilmanipulation. Du kan indsætte stilmærker, før du genererer PDF-filen, og Puppeteer genererer en fil med de ændrede stilarter.
await page.addStyleTag({ content: '.nav { display: none} .navbar { border: 0px} #print-button {display: none}' })
Send fil til klienten og gem den
Okay, nu har du genereret en PDF-fil på backend. Hvad skal jeg gøre nu?
som jeg nævnte ovenfor, hvis du ikke gemmer filen på disken, får du en buffer. Du skal bare sende den buffer med den rigtige indholdstype til front-end.
printPDF.then(pdf => {res.set({ 'Content-Type': 'application/pdf', 'Content-Length': pdf.length })res.send(pdf)
nu Kan du blot sende en anmodning til serveren for at få den genererede PDF.
function getPDF() { return axios.get(`${API_URL}/your-pdf-endpoint`, { responseType: 'arraybuffer', headers: { 'Accept': 'application/pdf' } })
Når du har sendt anmodningen, skal bufferen begynde at hente. Nu er det sidste trin at konvertere bufferen til 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! Hvis du klikker på knappen Gem, gemmes PDF-filen af bro.ser.
brug af Puppeteer med Docker
Jeg synes, det er den sværeste del af implementeringen – så lad mig spare dig for et par timers Googling.
den officielle dokumentation siger, at “at få hovedløs Chrome i gang i Docker kan være vanskelig”. De officielle dokumenter har et Fejlfindingsafsnit, hvor du i skrivende stund kan finde alle de nødvendige oplysninger om installation af puppeteer med Docker.
Hvis du installerer Puppeteer på Alpine-billedet, skal du sørge for at rulle lidt ned til denne del af siden. Ellers kan du gloss over det faktum, at du ikke kan køre den nyeste Puppeteer-version, og du skal også deaktivere SHM-brug ved hjælp af et flag:
const browser = await puppeteer.launch({ headless: true, args: });
ellers kan Puppeteer-underprocessen løbe tør for hukommelse, før den endda kommer i gang korrekt. Mere info om det på fejlfindingslinket ovenfor.
Mulighed 3 + 1: CSS Print Rules
man kunne tro, at det er nemt at bruge CSS print rules fra et udviklersynspunkt. Ingen NPM moduler, bare ren CSS. Men hvordan klarer de sig, når det kommer til kompatibilitet på tværs af bro. ser?
Når du vælger CSS-udskrivningsregler, skal du teste resultatet i hver bro.ser for at sikre, at det giver det samme layout, og det er ikke 100%, at det gør det.for eksempel kan indsættelse af en pause efter et givet element ikke betragtes som en esoterisk brugssag, men du kan blive overrasket over, at du skal bruge løsninger for at få det til at fungere i Firefoks.
medmindre du er en kamphærdet CSS-tryllekunstner med stor erfaring med at oprette udskrivbare sider, kan dette være tidskrævende.
Printregler er gode, hvis du kan holde udskriftsarkene enkle.
lad os se et eksempel.
@media print { .print-button { display: none; } .content div { break-after: always; }}
denne CSS ovenfor skjuler udskrivningsknappen og indsætter en sideskift efter hverdiv
med klassencontent.
der er en god artikel, der opsummerer, hvad du kan gøre med udskriftsregler, og hvad er vanskelighederne med dem, herunder bro.ser-Kompatibilitet.
under hensyntagen til alt er CSS-udskrivningsregler gode og effektive, hvis du vil lave en PDF fra en ikke så kompleks side.
oversigt: PDF fra HTML med Node.js og Puppeteer
så lad os hurtigt gennemgå de muligheder, vi dækkede her for at generere PDF-filer fra HTML-sider:
- skærmbillede fra DOM: dette kan være nyttigt, når du har brug for at oprette snapshots fra en side (for eksempel for at oprette en miniaturebillede), men kommer til kort, når du har en masse data at håndtere.
- Brug kun et PDF-bibliotek: hvis du har brug for at oprette PDF-filer programmatisk fra bunden, er dette en perfekt løsning. Ellers skal du vedligeholde HTML-og PDF-skabeloner, som bestemt er en no-go.
- Puppeteer: På trods af at det var relativt vanskeligt at få det til at fungere på Docker, gav det det bedste resultat for vores brugssag, og det var også det nemmeste at skrive koden med.CSS print rules: hvis dine brugere er uddannet nok til at vide, hvordan man udskriver til en fil, og dine sider er relativt enkle, kan det være den mest smertefri løsning. Som du så i vores tilfælde, var det ikke.
glad udskrivning!
relaterede emner
Node.js Tutorials for begyndere / @RisingStack
Leave a Reply